Merge pull request #16392 from nabinhait/mr_against_so

fix: Make material request from SO and raw material's rate as per UOM in BOM
This commit is contained in:
Nabin Hait 2019-01-16 17:47:07 +05:30 committed by GitHub
commit 0e0f7c0d32
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 314 additions and 679 deletions

View File

@ -678,7 +678,9 @@ class BuyingController(StockController):
frappe.db.sql("delete from `tabSerial No` where purchase_document_no=%s", self.name)
def validate_schedule_date(self):
if not self.schedule_date and self.get("items"):
if not self.get("items"):
return
if not self.schedule_date:
self.schedule_date = min([d.schedule_date for d in self.get("items")])
if self.schedule_date:

View File

@ -121,9 +121,11 @@ frappe.ui.form.on("BOM", {
freeze: true,
args: {
update_parent: true,
from_child_bom:false
from_child_bom:false,
save: false
},
callback: function(r) {
refresh_field("items");
if(!r.exc) frm.refresh_fields();
}
});

View File

@ -8,6 +8,7 @@ from frappe import _
from erpnext.setup.utils import get_exchange_rate
from frappe.website.website_generator import WebsiteGenerator
from erpnext.stock.get_item_details import get_conversion_factor
from erpnext.stock.get_item_details import get_price_list_rate
import functools
@ -109,7 +110,11 @@ class BOM(WebsiteGenerator):
"item_name": item.item_name,
"bom_no": item.bom_no,
"stock_qty": item.stock_qty,
"include_item_in_manufacturing": item.include_item_in_manufacturing
"include_item_in_manufacturing": item.include_item_in_manufacturing,
"qty": item.qty,
"uom": item.uom,
"stock_uom": item.stock_uom,
"conversion_factor": item.conversion_factor
})
for r in ret:
if not item.get(r):
@ -141,7 +146,7 @@ class BOM(WebsiteGenerator):
'uom' : item and args['stock_uom'] or '',
'conversion_factor': 1,
'bom_no' : args['bom_no'],
'rate' : rate / self.conversion_rate if self.conversion_rate else rate,
'rate' : rate,
'qty' : args.get("qty") or args.get("stock_qty") or 1,
'stock_qty' : args.get("qty") or args.get("stock_qty") or 1,
'base_rate' : rate,
@ -173,35 +178,56 @@ class BOM(WebsiteGenerator):
elif self.rm_cost_as_per == "Price List":
if not self.buying_price_list:
frappe.throw(_("Please select Price List"))
rate = frappe.db.get_value("Item Price", {"price_list": self.buying_price_list,
"item_code": arg["item_code"]}, "price_list_rate") or 0.0
price_list_currency = frappe.db.get_value("Price List",
self.buying_price_list, "currency")
if price_list_currency != self.company_currency():
rate = flt(rate * self.conversion_rate)
args = frappe._dict({
"doctype": "BOM",
"price_list": self.buying_price_list,
"qty": arg.get("qty"),
"uom": arg.get("uom") or arg.get("stock_uom"),
"stock_uom": arg.get("stock_uom"),
"transaction_type": "buying",
"company": self.company,
"currency": self.currency,
"conversion_rate": self.conversion_rate or 1,
"conversion_factor": arg.get("conversion_factor") or 1,
"plc_conversion_rate": 1
})
item_doc = frappe.get_doc("Item", arg.get("item_code"))
out = frappe._dict()
get_price_list_rate(args, item_doc, out)
rate = out.price_list_rate
if not rate:
frappe.msgprint(_("{0} not found for Item {1}")
.format(self.rm_cost_as_per, arg["item_code"]), alert=True)
if self.rm_cost_as_per == "Price List":
frappe.msgprint(_("Price not found for item {0} and price list {1}")
.format(arg["item_code"], self.buying_price_list), alert=True)
else:
frappe.msgprint(_("{0} not found for item {1}")
.format(self.rm_cost_as_per, arg["item_code"]), alert=True)
return flt(rate)
def update_cost(self, update_parent=True, from_child_bom=False):
def update_cost(self, update_parent=True, from_child_bom=False, save=True):
if self.docstatus == 2:
return
existing_bom_cost = self.total_cost
for d in self.get("items"):
rate = self.get_rm_rate({"item_code": d.item_code, "bom_no": d.bom_no})
if rate:
d.rate = rate * flt(d.conversion_factor) / flt(self.conversion_rate)
d.rate = self.get_rm_rate({
"item_code": d.item_code,
"bom_no": d.bom_no,
"qty": d.qty,
"uom": d.uom,
"stock_uom": d.stock_uom,
"conversion_factor": d.conversion_factor
})
d.amount = flt(d.rate) * flt(d.qty)
if self.docstatus == 1:
self.flags.ignore_validate_update_after_submit = True
self.calculate_cost()
self.save()
if save:
self.save()
self.update_exploded_items()
# update parent BOMs

View File

@ -76,7 +76,7 @@ class TestBOM(unittest.TestCase):
# update cost of all BOMs based on latest valuation rate
update_cost()
# check if new valuation rate updated in all BOMs
for d in frappe.db.sql("""select rate from `tabBOM Item`
where item_code='_Test Item 2' and docstatus=1 and parenttype='BOM'""", as_dict=1):
@ -97,6 +97,7 @@ class TestBOM(unittest.TestCase):
self.assertEqual(bom.base_total_cost, 486000)
def test_bom_cost_multi_uom_multi_currency(self):
frappe.db.set_value("Price List", "_Test Price List", "price_not_uom_dependant", 1)
for item_code, rate in (("_Test Item", 3600), ("_Test Item Home Desktop Manufactured", 3000)):
frappe.db.sql("delete from `tabItem Price` where price_list='_Test Price List' and item_code=%s",
item_code)
@ -105,7 +106,7 @@ class TestBOM(unittest.TestCase):
item_price.item_code = item_code
item_price.price_list_rate = rate
item_price.insert()
bom = frappe.copy_doc(test_records[2])
bom.set_rate_of_sub_assembly_item_based_on_bom = 0
bom.rm_cost_as_per = "Price List"

View File

@ -1023,6 +1023,71 @@
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "operation",
"fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Item operation",
"length": 0,
"no_copy": 0,
"options": "Operation",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "allow_alternative_item",
"fieldtype": "Check",
"hidden": 1,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Allow Alternative Item",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
}
],
"has_web_view": 0,
@ -1035,7 +1100,7 @@
"issingle": 0,
"istable": 1,
"max_attachments": 0,
"modified": "2018-12-28 16:38:56.529079",
"modified": "2018-11-23 15:05:55.187136",
"modified_by": "Administrator",
"module": "Manufacturing",
"name": "BOM Item",

View File

@ -7,9 +7,9 @@ import frappe, json
from frappe import msgprint, _
from frappe.model.document import Document
from erpnext.manufacturing.doctype.bom.bom import validate_bom_no
from frappe.utils import cstr, flt, cint, nowdate, add_days, comma_and, now_datetime
from frappe.utils import cstr, flt, cint, nowdate, add_days, comma_and, now_datetime, ceil
from erpnext.manufacturing.doctype.work_order.work_order import get_item_details
from six import string_types
from six import string_types, iteritems
from erpnext.setup.doctype.item_group.item_group import get_item_group_defaults
class ProductionPlan(Document):
@ -372,40 +372,46 @@ class ProductionPlan(Document):
else :
msgprint(_("No material request created"))
def get_exploded_items(bom_wise_item_details, company, bom_no, include_non_stock_items):
def get_exploded_items(item_details, company, bom_no, include_non_stock_items, planned_qty=1):
for d in frappe.db.sql("""select bei.item_code, item.default_bom as bom,
ifnull(sum(bei.stock_qty/ifnull(bom.quantity, 1)), 0) as qty, item.item_name,
ifnull(sum(bei.stock_qty/ifnull(bom.quantity, 1)), 0)*%s as qty, item.item_name,
bei.description, bei.stock_uom, item.min_order_qty, bei.source_warehouse,
item.default_material_request_type, item.min_order_qty, item_default.default_warehouse
item.default_material_request_type, item.min_order_qty, item_default.default_warehouse,
item.purchase_uom, item_uom.conversion_factor
from
`tabBOM Explosion Item` bei
JOIN `tabBOM` bom ON bom.name = bei.parent
JOIN `tabItem` item ON item.name = bei.item_code
LEFT JOIN `tabItem Default` item_default
ON item_default.parent = item.name and item_default.company=%s
LEFT JOIN `tabUOM Conversion Detail` item_uom
ON item.name = item_uom.parent and item_uom.uom = item.purchase_uom
where
bei.docstatus < 2
and bom.name=%s and item.is_stock_item in (1, {0})
group by bei.item_code, bei.stock_uom""".format(0 if include_non_stock_items else 1),
(company, bom_no), as_dict=1):
bom_wise_item_details.setdefault(d.get('item_code'), d)
return bom_wise_item_details
(planned_qty, company, bom_no), as_dict=1):
item_details.setdefault(d.get('item_code'), d)
return item_details
def get_subitems(doc, data, bom_wise_item_details, bom_no, company, include_non_stock_items, include_subcontracted_items, parent_qty):
def get_subitems(doc, data, item_details, bom_no, company, include_non_stock_items,
include_subcontracted_items, parent_qty, planned_qty=1):
items = frappe.db.sql("""
SELECT
bom_item.item_code, default_material_request_type, item.item_name,
ifnull(%(parent_qty)s * sum(bom_item.stock_qty/ifnull(bom.quantity, 1)), 0) as qty,
ifnull(%(parent_qty)s * sum(bom_item.stock_qty/ifnull(bom.quantity, 1)) * %(planned_qty)s, 0) as qty,
item.is_sub_contracted_item as is_sub_contracted, bom_item.source_warehouse,
item.default_bom as default_bom, bom_item.description as description,
bom_item.stock_uom as stock_uom, item.min_order_qty as min_order_qty,
item_default.default_warehouse
item_default.default_warehouse, item.purchase_uom, item_uom.conversion_factor
FROM
`tabBOM Item` bom_item
JOIN `tabBOM` bom ON bom.name = bom_item.parent
JOIN tabItem item ON bom_item.item_code = item.name
LEFT JOIN `tabItem Default` item_default
ON item.name = item_default.parent and item_default.company = %(company)s
LEFT JOIN `tabUOM Conversion Detail` item_uom
ON item.name = item_uom.parent and item_uom.uom = item.purchase_uom
where
bom.name = %(bom)s
and bom_item.docstatus < 2
@ -413,45 +419,61 @@ def get_subitems(doc, data, bom_wise_item_details, bom_no, company, include_non_
group by bom_item.item_code""".format(0 if include_non_stock_items else 1),{
'bom': bom_no,
'parent_qty': parent_qty,
'planned_qty': planned_qty,
'company': company
}, as_dict=1)
for d in items:
if not data.get('include_exploded_items') or not d.default_bom:
if d.item_code in bom_wise_item_details:
bom_wise_item_details[d.item_code].qty = bom_wise_item_details[d.item_code].qty + d.qty
if d.item_code in item_details:
item_details[d.item_code].qty = item_details[d.item_code].qty + d.qty
else:
bom_wise_item_details[d.item_code] = d
item_details[d.item_code] = d
if data.get('include_exploded_items') and d.default_bom:
if ((d.default_material_request_type in ["Manufacture", "Purchase"] and
not d.is_sub_contracted) or (d.is_sub_contracted and include_subcontracted_items)):
if d.qty > 0:
get_subitems(doc, data, bom_wise_item_details, d.default_bom, company, include_non_stock_items, include_subcontracted_items, d.qty)
return bom_wise_item_details
get_subitems(doc, data, item_details, d.default_bom, company,
include_non_stock_items, include_subcontracted_items, d.qty)
return item_details
def add_item_in_material_request_items(doc, planned_qty, ignore_existing_ordered_qty, item, row, data, warehouse, company):
total_qty = row.qty * planned_qty
def get_material_request_items(row, sales_order, company, ignore_existing_ordered_qty, warehouse):
total_qty = row['qty']
projected_qty, actual_qty = get_bin_details(row)
requested_qty = 0
if ignore_existing_ordered_qty:
requested_qty = total_qty
else:
elif total_qty > projected_qty:
requested_qty = total_qty - projected_qty
if requested_qty > 0 and requested_qty < row.min_order_qty:
requested_qty = row.min_order_qty
item_group_defaults = get_item_group_defaults(item, company)
if requested_qty > 0 and requested_qty < row['min_order_qty']:
requested_qty = row['min_order_qty']
item_group_defaults = get_item_group_defaults(row.item_code, company)
if not row['purchase_uom']:
row['purchase_uom'] = row['stock_uom']
if row['purchase_uom'] != row['stock_uom']:
if not row['conversion_factor']:
frappe.throw(_("UOM Conversion factor ({0} -> {1}) not found for item: {2}")
.format(row['purchase_uom'], row['stock_uom'], row.item_code))
requested_qty = requested_qty / row['conversion_factor']
if frappe.db.get_value("UOM", row['purchase_uom'], "must_be_whole_number"):
requested_qty = ceil(requested_qty)
if requested_qty > 0:
doc.setdefault('mr_items', []).append({
'item_code': item,
return {
'item_code': row.item_code,
'item_name': row.item_name,
'quantity': requested_qty,
'warehouse': warehouse or row.source_warehouse or row.default_warehouse or item_group_defaults.get("default_warehouse"),
'warehouse': warehouse or row.get('source_warehouse') \
or row.get('default_warehouse') or item_group_defaults.get("default_warehouse"),
'actual_qty': actual_qty,
'min_order_qty': row.min_order_qty,
'sales_order': data.get('sales_order')
})
'min_order_qty': row['min_order_qty'],
'sales_order': sales_order
}
def get_sales_orders(self):
so_filter = item_filter = ""
@ -487,8 +509,8 @@ def get_sales_orders(self):
"project": self.project,
"item": self.item_code,
"company": self.company
}, as_dict=1)
}, as_dict=1)
return open_so
@frappe.whitelist()
@ -497,7 +519,7 @@ def get_bin_details(row):
row = frappe._dict(json.loads(row))
conditions = ""
warehouse = row.source_warehouse or row.default_warehouse or row.warehouse
warehouse = row.get('source_warehouse') or row.get('default_warehouse')
if warehouse:
lft, rgt = frappe.db.get_value("Warehouse", warehouse, ["lft", "rgt"])
conditions = " and exists(select name from `tabWarehouse` where lft >= {0} and rgt <= {1} and name=`tabBin`.warehouse)".format(lft, rgt)
@ -505,49 +527,88 @@ def get_bin_details(row):
item_projected_qty = frappe.db.sql(""" select ifnull(sum(projected_qty),0) as projected_qty,
ifnull(sum(actual_qty),0) as actual_qty from `tabBin`
where item_code = %(item_code)s {conditions}
""".format(conditions=conditions), { "item_code": row.item_code }, as_list=1)
""".format(conditions=conditions), { "item_code": row['item_code'] }, as_list=1)
return item_projected_qty and item_projected_qty[0] or (0,0)
@frappe.whitelist()
def get_items_for_material_requests(doc, company=None):
def get_items_for_material_requests(doc, sales_order=None, company=None):
if isinstance(doc, string_types):
doc = frappe._dict(json.loads(doc))
doc['mr_items'] = []
po_items = doc.get('po_items') if doc.get('po_items') else doc.get('items')
company = doc.get('company')
so_item_details = frappe._dict()
for data in po_items:
warehouse = None
bom_wise_item_details = {}
if data.get('required_qty'):
planned_qty = data.get('required_qty')
bom_no = data.get('bom')
ignore_existing_ordered_qty = data.get('ignore_existing_ordered_qty')
include_non_stock_items = 1
warehouse = data.get('for_warehouse')
if data.get('include_exploded_items'):
include_subcontracted_items = 1
warehouse = data.get('for_warehouse')
ignore_existing_ordered_qty = data.get('ignore_existing_ordered_qty') or doc.get('ignore_existing_ordered_qty')
planned_qty = data.get('required_qty') or data.get('planned_qty')
item_details = {}
if data.get("bom") or data.get("bom_no"):
if data.get('required_qty'):
bom_no = data.get('bom')
include_non_stock_items = 1
include_subcontracted_items = 1 if data.get('include_exploded_items') else 0
else:
include_subcontracted_items = 0
else:
planned_qty = data.get('planned_qty')
bom_no = data.get('bom_no')
include_subcontracted_items = doc.get('include_subcontracted_items')
company = doc.get('company')
include_non_stock_items = doc.get('include_non_stock_items')
ignore_existing_ordered_qty = doc.get('ignore_existing_ordered_qty')
if not planned_qty:
frappe.throw(_("For row {0}: Enter Planned Qty").format(data.get('idx')))
bom_no = data.get('bom_no')
include_subcontracted_items = doc.get('include_subcontracted_items')
include_non_stock_items = doc.get('include_non_stock_items')
if data.get('include_exploded_items') and bom_no and include_subcontracted_items:
# fetch exploded items from BOM
bom_wise_item_details = get_exploded_items(bom_wise_item_details, company, bom_no, include_non_stock_items)
else:
bom_wise_item_details = get_subitems(doc, data, bom_wise_item_details, bom_no, company, include_non_stock_items, include_subcontracted_items, 1)
for item, item_details in bom_wise_item_details.items():
if item_details.qty > 0:
add_item_in_material_request_items(doc, planned_qty, ignore_existing_ordered_qty, item, item_details, data, warehouse, company)
if not planned_qty:
frappe.throw(_("For row {0}: Enter Planned Qty").format(data.get('idx')))
return doc['mr_items']
if bom_no:
if data.get('include_exploded_items') and include_subcontracted_items:
# fetch exploded items from BOM
item_details = get_exploded_items(item_details,
company, bom_no, include_non_stock_items, planned_qty=planned_qty)
else:
item_details = get_subitems(doc, data, item_details, bom_no, company,
include_non_stock_items, include_subcontracted_items, 1, planned_qty=planned_qty)
else:
item_master = frappe.get_doc('Item', data['item_code']).as_dict()
purchase_uom = item_master.purchase_uom or item_master.stock_uom
conversion_factor = 0
for d in item_master.get("uoms"):
if d.uom == purchase_uom:
conversion_factor = d.conversion_factor
item_details[item_master.name] = frappe._dict(
{
'item_name' : item_master.item_name,
'default_bom' : doc.bom,
'purchase_uom' : purchase_uom,
'default_warehouse': item_master.default_warehouse,
'min_order_qty' : item_master.min_order_qty,
'default_material_request_type' : item_master.default_material_request_type,
'qty': planned_qty or 1,
'is_sub_contracted' : item_master.is_subcontracted_item,
'item_code' : item_master.name,
'description' : item_master.description,
'stock_uom' : item_master.stock_uom,
'conversion_factor' : conversion_factor,
}
)
if not sales_order:
sales_order = doc.get("sales_order")
for item_code, details in iteritems(item_details):
so_item_details.setdefault(sales_order, frappe._dict())
if item_code in so_item_details.get(sales_order, {}):
so_item_details[sales_order][item_code]['qty'] = so_item_details[sales_order][item_code].get("qty", 0) + flt(details.qty)
else:
so_item_details[sales_order][item_code] = details
mr_items = []
for sales_order, item_code in iteritems(so_item_details):
item_dict = so_item_details[sales_order]
for details in item_dict.values():
if details.qty > 0:
items = get_material_request_items(details, sales_order, company,
ignore_existing_ordered_qty, warehouse)
if items:
mr_items.append(items)
return mr_items

View File

@ -1,552 +0,0 @@
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
# License: GNU General Public License v3. See license.txt
from __future__ import unicode_literals
import frappe
from frappe.utils import cstr, flt, cint, nowdate, add_days, comma_and
from frappe import msgprint, _
from frappe.model.document import Document
from erpnext.manufacturing.doctype.bom.bom import validate_bom_no
from erpnext.manufacturing.doctype.work_order.work_order import get_item_details
class ProductionPlanningTool(Document):
def clear_table(self, table_name):
self.set(table_name, [])
def validate_company(self):
if not self.company:
frappe.throw(_("Please enter Company"))
def get_open_sales_orders(self):
""" Pull sales orders which are pending to deliver based on criteria selected"""
so_filter = item_filter = ""
if self.from_date:
so_filter += " and so.transaction_date >= %(from_date)s"
if self.to_date:
so_filter += " and so.transaction_date <= %(to_date)s"
if self.customer:
so_filter += " and so.customer = %(customer)s"
if self.project:
so_filter += " and so.project = %(project)s"
if self.fg_item:
item_filter += " and so_item.item_code = %(item)s"
open_so = frappe.db.sql("""
select distinct so.name, so.transaction_date, so.customer, so.base_grand_total
from `tabSales Order` so, `tabSales Order Item` so_item
where so_item.parent = so.name
and so.docstatus = 1 and so.status not in ("Stopped", "Closed")
and so.company = %(company)s
and so_item.qty > so_item.delivered_qty {0} {1}
and (exists (select name from `tabBOM` bom where bom.item=so_item.item_code
and bom.is_active = 1)
or exists (select name from `tabPacked Item` pi
where pi.parent = so.name and pi.parent_item = so_item.item_code
and exists (select name from `tabBOM` bom where bom.item=pi.item_code
and bom.is_active = 1)))
""".format(so_filter, item_filter), {
"from_date": self.from_date,
"to_date": self.to_date,
"customer": self.customer,
"project": self.project,
"item": self.fg_item,
"company": self.company
}, as_dict=1)
self.add_so_in_table(open_so)
def add_so_in_table(self, open_so):
""" Add sales orders in the table"""
self.clear_table("sales_orders")
so_list = []
for r in open_so:
if cstr(r['name']) not in so_list:
pp_so = self.append('sales_orders', {})
pp_so.sales_order = r['name']
pp_so.sales_order_date = cstr(r['transaction_date'])
pp_so.customer = cstr(r['customer'])
pp_so.grand_total = flt(r['base_grand_total'])
def get_pending_material_requests(self):
""" Pull Material Requests that are pending based on criteria selected"""
mr_filter = item_filter = ""
if self.from_date:
mr_filter += " and mr.transaction_date >= %(from_date)s"
if self.to_date:
mr_filter += " and mr.transaction_date <= %(to_date)s"
if self.warehouse:
mr_filter += " and mr_item.warehouse = %(warehouse)s"
if self.fg_item:
item_filter += " and mr_item.item_code = %(item)s"
pending_mr = frappe.db.sql("""
select distinct mr.name, mr.transaction_date
from `tabMaterial Request` mr, `tabMaterial Request Item` mr_item
where mr_item.parent = mr.name
and mr.material_request_type = "Manufacture"
and mr.docstatus = 1
and mr_item.qty > ifnull(mr_item.ordered_qty,0) {0} {1}
and (exists (select name from `tabBOM` bom where bom.item=mr_item.item_code
and bom.is_active = 1))
""".format(mr_filter, item_filter), {
"from_date": self.from_date,
"to_date": self.to_date,
"warehouse": self.warehouse,
"item": self.fg_item
}, as_dict=1)
self.add_mr_in_table(pending_mr)
def add_mr_in_table(self, pending_mr):
""" Add Material Requests in the table"""
self.clear_table("material_requests")
mr_list = []
for r in pending_mr:
if cstr(r['name']) not in mr_list:
mr = self.append('material_requests', {})
mr.material_request = r['name']
mr.material_request_date = cstr(r['transaction_date'])
def get_items(self):
if self.get_items_from == "Sales Order":
self.get_so_items()
elif self.get_items_from == "Material Request":
self.get_mr_items()
def get_so_items(self):
so_list = [d.sales_order for d in self.get('sales_orders') if d.sales_order]
if not so_list:
msgprint(_("Please enter Sales Orders in the above table"))
return []
item_condition = ""
if self.fg_item:
item_condition = ' and so_item.item_code = "{0}"'.format(frappe.db.escape(self.fg_item))
items = frappe.db.sql("""select distinct parent, item_code, warehouse,
(qty - delivered_qty)*conversion_factor as pending_qty
from `tabSales Order Item` so_item
where parent in (%s) and docstatus = 1 and qty > delivered_qty
and exists (select name from `tabBOM` bom where bom.item=so_item.item_code
and bom.is_active = 1) %s""" % \
(", ".join(["%s"] * len(so_list)), item_condition), tuple(so_list), as_dict=1)
if self.fg_item:
item_condition = ' and pi.item_code = "{0}"'.format(frappe.db.escape(self.fg_item))
packed_items = frappe.db.sql("""select distinct pi.parent, pi.item_code, pi.warehouse as warehouse,
(((so_item.qty - so_item.delivered_qty) * pi.qty) / so_item.qty)
as pending_qty
from `tabSales Order Item` so_item, `tabPacked Item` pi
where so_item.parent = pi.parent and so_item.docstatus = 1
and pi.parent_item = so_item.item_code
and so_item.parent in (%s) and so_item.qty > so_item.delivered_qty
and exists (select name from `tabBOM` bom where bom.item=pi.item_code
and bom.is_active = 1) %s""" % \
(", ".join(["%s"] * len(so_list)), item_condition), tuple(so_list), as_dict=1)
self.add_items(items + packed_items)
def get_mr_items(self):
mr_list = [d.material_request for d in self.get('material_requests') if d.material_request]
if not mr_list:
msgprint(_("Please enter Material Requests in the above table"))
return []
item_condition = ""
if self.fg_item:
item_condition = ' and mr_item.item_code = "' + frappe.db.escape(self.fg_item, percent=False) + '"'
items = frappe.db.sql("""select distinct parent, name, item_code, warehouse,
(qty - ordered_qty) as pending_qty
from `tabMaterial Request Item` mr_item
where parent in (%s) and docstatus = 1 and qty > ordered_qty
and exists (select name from `tabBOM` bom where bom.item=mr_item.item_code
and bom.is_active = 1) %s""" % \
(", ".join(["%s"] * len(mr_list)), item_condition), tuple(mr_list), as_dict=1)
self.add_items(items)
def add_items(self, items):
self.clear_table("items")
for p in items:
item_details = get_item_details(p['item_code'])
pi = self.append('items', {})
pi.warehouse = p['warehouse']
pi.item_code = p['item_code']
pi.description = item_details and item_details.description or ''
pi.stock_uom = item_details and item_details.stock_uom or ''
pi.bom_no = item_details and item_details.bom_no or ''
pi.planned_qty = flt(p['pending_qty'])
pi.pending_qty = flt(p['pending_qty'])
if self.get_items_from == "Sales Order":
pi.sales_order = p['parent']
elif self.get_items_from == "Material Request":
pi.material_request = p['parent']
pi.material_request_item = p['name']
def validate_data(self):
self.validate_company()
for d in self.get('items'):
if not d.bom_no:
frappe.throw(_("Please select BOM for Item in Row {0}".format(d.idx)))
else:
validate_bom_no(d.item_code, d.bom_no)
if not flt(d.planned_qty):
frappe.throw(_("Please enter Planned Qty for Item {0} at row {1}").format(d.item_code, d.idx))
def raise_work_orders(self):
"""It will raise work order (Draft) for all distinct FG items"""
self.validate_data()
from erpnext.utilities.transaction_base import validate_uom_is_integer
validate_uom_is_integer(self, "stock_uom", "planned_qty")
items = self.get_production_items()
wo_list = []
frappe.flags.mute_messages = True
for key in items:
work_order = self.create_work_order(items[key])
if work_order:
wo_list.append(work_order)
frappe.flags.mute_messages = False
if wo_list:
wo_list = ["""<a href="#Form/Work Order/%s" target="_blank">%s</a>""" % \
(p, p) for p in wo_list]
msgprint(_("{0} created").format(comma_and(wo_list)))
else :
msgprint(_("No Work Orders created"))
def get_production_items(self):
item_dict = {}
for d in self.get("items"):
item_details= {
"production_item" : d.item_code,
"sales_order" : d.sales_order,
"material_request" : d.material_request,
"material_request_item" : d.material_request_item,
"bom_no" : d.bom_no,
"description" : d.description,
"stock_uom" : d.stock_uom,
"company" : self.company,
"wip_warehouse" : "",
"fg_warehouse" : d.warehouse,
"status" : "Draft",
"project" : frappe.db.get_value("Sales Order", d.sales_order, "project")
}
""" Club similar BOM and item for processing in case of Sales Orders """
if self.get_items_from == "Material Request":
item_details.update({
"qty": d.planned_qty
})
item_dict[(d.item_code, d.material_request_item, d.warehouse)] = item_details
else:
item_details.update({
"qty":flt(item_dict.get((d.item_code, d.sales_order, d.warehouse),{})
.get("qty")) + flt(d.planned_qty)
})
item_dict[(d.item_code, d.sales_order, d.warehouse)] = item_details
return item_dict
def create_work_order(self, item_dict):
"""Create work order. Called from Production Planning Tool"""
from erpnext.manufacturing.doctype.work_order.work_order import OverProductionError, get_default_warehouse
warehouse = get_default_warehouse()
wo = frappe.new_doc("Work Order")
wo.update(item_dict)
wo.set_work_order_operations()
if warehouse:
wo.wip_warehouse = warehouse.get('wip_warehouse')
if not wo.fg_warehouse:
wo.fg_warehouse = warehouse.get('fg_warehouse')
try:
wo.insert()
return wo.name
except OverProductionError:
pass
def get_so_wise_planned_qty(self):
"""
bom_dict {
bom_no: ['sales_order', 'qty']
}
"""
bom_dict = {}
for d in self.get("items"):
if self.get_items_from == "Material Request":
bom_dict.setdefault(d.bom_no, []).append([d.material_request_item, flt(d.planned_qty)])
else:
bom_dict.setdefault(d.bom_no, []).append([d.sales_order, flt(d.planned_qty)])
return bom_dict
def download_raw_materials(self):
""" Create csv data for required raw material to produce finished goods"""
self.validate_data()
bom_dict = self.get_so_wise_planned_qty()
self.get_raw_materials(bom_dict)
return self.get_csv()
def get_raw_materials(self, bom_dict,non_stock_item=0):
""" Get raw materials considering sub-assembly items
{
"item_code": [qty_required, description, stock_uom, min_order_qty]
}
"""
item_list = []
precision = frappe.get_precision("BOM Item", "stock_qty")
for bom, so_wise_qty in bom_dict.items():
bom_wise_item_details = {}
if self.use_multi_level_bom and self.only_raw_materials and self.include_subcontracted:
# get all raw materials with sub assembly childs
# Did not use qty_consumed_per_unit in the query, as it leads to rounding loss
for d in frappe.db.sql("""select fb.item_code,
ifnull(sum(fb.stock_qty/ifnull(bom.quantity, 1)), 0) as qty,
fb.description, fb.stock_uom, item.min_order_qty
from `tabBOM Explosion Item` fb, `tabBOM` bom, `tabItem` item
where bom.name = fb.parent and item.name = fb.item_code
and (item.is_sub_contracted_item = 0 or ifnull(item.default_bom, "")="")
""" + ("and item.is_stock_item = 1","")[non_stock_item] + """
and fb.docstatus<2 and bom.name=%(bom)s
group by fb.item_code, fb.stock_uom""", {"bom":bom}, as_dict=1):
bom_wise_item_details.setdefault(d.item_code, d)
else:
# Get all raw materials considering SA items as raw materials,
# so no childs of SA items
bom_wise_item_details = self.get_subitems(bom_wise_item_details, bom,1, \
self.use_multi_level_bom,self.only_raw_materials, self.include_subcontracted,non_stock_item)
for item, item_details in bom_wise_item_details.items():
for so_qty in so_wise_qty:
item_list.append([item, flt(flt(item_details.qty) * so_qty[1], precision),
item_details.description, item_details.stock_uom, item_details.min_order_qty,
so_qty[0]])
self.make_items_dict(item_list)
def get_subitems(self,bom_wise_item_details, bom, parent_qty, include_sublevel, only_raw, supply_subs,non_stock_item=0):
items = frappe.db.sql("""
SELECT
bom_item.item_code,
default_material_request_type,
ifnull(%(parent_qty)s * sum(bom_item.stock_qty/ifnull(bom.quantity, 1)), 0) as qty,
item.is_sub_contracted_item as is_sub_contracted,
item.default_bom as default_bom,
bom_item.description as description,
bom_item.stock_uom as stock_uom,
item.min_order_qty as min_order_qty
FROM
`tabBOM Item` bom_item,
`tabBOM` bom,
tabItem item
where
bom.name = bom_item.parent
and bom.name = %(bom)s
and bom_item.docstatus < 2
and bom_item.item_code = item.name
""" + ("and item.is_stock_item = 1", "")[non_stock_item] + """
group by bom_item.item_code""", {"bom": bom, "parent_qty": parent_qty}, as_dict=1)
for d in items:
if ((d.default_material_request_type == "Purchase"
and not (d.is_sub_contracted and only_raw and include_sublevel))
or (d.default_material_request_type == "Manufacture" and not only_raw)):
if d.item_code in bom_wise_item_details:
bom_wise_item_details[d.item_code].qty = bom_wise_item_details[d.item_code].qty + d.qty
else:
bom_wise_item_details[d.item_code] = d
if include_sublevel and d.default_bom:
if ((d.default_material_request_type == "Purchase" and d.is_sub_contracted and supply_subs)
or (d.default_material_request_type == "Manufacture")):
my_qty = 0
projected_qty = self.get_item_projected_qty(d.item_code)
if self.create_material_requests_for_all_required_qty:
my_qty = d.qty
else:
total_required_qty = flt(bom_wise_item_details.get(d.item_code, frappe._dict()).qty)
if (total_required_qty - d.qty) < projected_qty:
my_qty = total_required_qty - projected_qty
else:
my_qty = d.qty
if my_qty > 0:
self.get_subitems(bom_wise_item_details,
d.default_bom, my_qty, include_sublevel, only_raw, supply_subs)
return bom_wise_item_details
def make_items_dict(self, item_list):
if not getattr(self, "item_dict", None):
self.item_dict = {}
for i in item_list:
self.item_dict.setdefault(i[0], []).append([flt(i[1]), i[2], i[3], i[4], i[5]])
def get_csv(self):
item_list = [['Item Code', 'Description', 'Stock UOM', 'Required Qty', 'Warehouse',
'Quantity Requested for Purchase', 'Ordered Qty', 'Actual Qty']]
for item in self.item_dict:
total_qty = sum([flt(d[0]) for d in self.item_dict[item]])
item_list.append([item, self.item_dict[item][0][1], self.item_dict[item][0][2], total_qty])
item_qty = frappe.db.sql("""select warehouse, indented_qty, ordered_qty, actual_qty
from `tabBin` where item_code = %s""", item, as_dict=1)
i_qty, o_qty, a_qty = 0, 0, 0
for w in item_qty:
i_qty, o_qty, a_qty = i_qty + flt(w.indented_qty), o_qty + \
flt(w.ordered_qty), a_qty + flt(w.actual_qty)
item_list.append(['', '', '', '', w.warehouse, flt(w.indented_qty),
flt(w.ordered_qty), flt(w.actual_qty)])
if item_qty:
item_list.append(['', '', '', '', 'Total', i_qty, o_qty, a_qty])
else:
item_list.append(['', '', '', '', 'Total', 0, 0, 0])
return item_list
def raise_material_requests(self):
"""
Raise Material Request if projected qty is less than qty required
Requested qty should be shortage qty considering minimum order qty
"""
self.validate_data()
if not self.purchase_request_for_warehouse:
frappe.throw(_("Please enter Warehouse for which Material Request will be raised"))
bom_dict = self.get_so_wise_planned_qty()
self.get_raw_materials(bom_dict,self.create_material_requests_non_stock_request)
if self.item_dict:
self.create_material_request()
def get_requested_items(self):
items_to_be_requested = frappe._dict()
if not self.create_material_requests_for_all_required_qty:
item_projected_qty = self.get_projected_qty()
for item, so_item_qty in self.item_dict.items():
total_qty = sum([flt(d[0]) for d in so_item_qty])
requested_qty = 0
if self.create_material_requests_for_all_required_qty:
requested_qty = total_qty
elif total_qty > item_projected_qty.get(item, 0):
# shortage
requested_qty = total_qty - flt(item_projected_qty.get(item))
# consider minimum order qty
if requested_qty and requested_qty < flt(so_item_qty[0][3]):
requested_qty = flt(so_item_qty[0][3])
# distribute requested qty SO wise
for item_details in so_item_qty:
if requested_qty:
sales_order = item_details[4] or "No Sales Order"
if self.get_items_from == "Material Request":
sales_order = "No Sales Order"
if requested_qty <= item_details[0]:
adjusted_qty = requested_qty
else:
adjusted_qty = item_details[0]
items_to_be_requested.setdefault(item, {}).setdefault(sales_order, 0)
items_to_be_requested[item][sales_order] += adjusted_qty
requested_qty -= adjusted_qty
else:
break
# requested qty >= total so qty, due to minimum order qty
if requested_qty:
items_to_be_requested.setdefault(item, {}).setdefault("No Sales Order", 0)
items_to_be_requested[item]["No Sales Order"] += requested_qty
return items_to_be_requested
def get_item_projected_qty(self,item):
conditions = ""
if self.purchase_request_for_warehouse:
conditions = " and warehouse='{0}'".format(frappe.db.escape(self.purchase_request_for_warehouse))
item_projected_qty = frappe.db.sql("""
select ifnull(sum(projected_qty),0) as qty
from `tabBin`
where item_code = %(item_code)s {conditions}
""".format(conditions=conditions), { "item_code": item }, as_dict=1)
return item_projected_qty[0].qty
def get_projected_qty(self):
items = self.item_dict.keys()
item_projected_qty = frappe.db.sql("""select item_code, sum(projected_qty)
from `tabBin` where item_code in (%s) and warehouse=%s group by item_code""" %
(", ".join(["%s"]*len(items)), '%s'), tuple(items + [self.purchase_request_for_warehouse]))
return dict(item_projected_qty)
def create_material_request(self):
items_to_be_requested = self.get_requested_items()
material_request_list = []
if items_to_be_requested:
for item in items_to_be_requested:
item_wrapper = frappe.get_doc("Item", item)
material_request = frappe.new_doc("Material Request")
material_request.update({
"transaction_date": nowdate(),
"status": "Draft",
"company": self.company,
"requested_by": frappe.session.user,
"schedule_date": add_days(nowdate(), cint(item_wrapper.lead_time_days)),
})
material_request.update({"material_request_type": item_wrapper.default_material_request_type})
for sales_order, requested_qty in items_to_be_requested[item].items():
material_request.append("items", {
"doctype": "Material Request Item",
"__islocal": 1,
"item_code": item,
"item_name": item_wrapper.item_name,
"description": item_wrapper.description,
"uom": item_wrapper.stock_uom,
"item_group": item_wrapper.item_group,
"brand": item_wrapper.brand,
"qty": requested_qty,
"schedule_date": add_days(nowdate(), cint(item_wrapper.lead_time_days)),
"warehouse": self.purchase_request_for_warehouse,
"sales_order": sales_order if sales_order!="No Sales Order" else None,
"project": frappe.db.get_value("Sales Order", sales_order, "project") \
if sales_order!="No Sales Order" else None
})
material_request.flags.ignore_permissions = 1
material_request.submit()
material_request_list.append(material_request.name)
if material_request_list:
message = ["""<a href="#Form/Material Request/%s" target="_blank">%s</a>""" % \
(p, p) for p in material_request_list]
msgprint(_("Material Requests {0} created").format(comma_and(message)))
else:
msgprint(_("Nothing to request"))

View File

@ -441,6 +441,7 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({
weight_per_unit: item.weight_per_unit,
weight_uom: item.weight_uom,
uom : item.uom,
stock_uom: item.stock_uom,
pos_profile: me.frm.doc.doctype == 'Sales Invoice' ? me.frm.doc.pos_profile : '',
cost_center: item.cost_center
}

View File

@ -11,7 +11,7 @@ def validate_gstin_for_india(doc, method):
if not hasattr(doc, 'gstin'):
return
doc.gstin = doc.gstin.upper().strip()
doc.gstin = doc.gstin.upper().strip() if doc.gstin else ""
if not doc.gstin or doc.gstin == 'NA':
return

View File

@ -120,7 +120,7 @@ class Gstr1Report(object):
and customer in ('{0}')""".format("', '".join([frappe.db.escape(c.name) for c in customers]))
if self.filters.get("type_of_business") in ("B2C Large", "B2C Small"):
b2c_limit = frappe.db.get_single_value('GSt Settings', 'b2c_limit')
b2c_limit = frappe.db.get_single_value('GST Settings', 'b2c_limit')
if not b2c_limit:
frappe.throw(_("Please set B2C Limit in GST Settings."))
@ -201,7 +201,7 @@ class Gstr1Report(object):
if unidentified_gst_accounts:
frappe.msgprint(_("Following accounts might be selected in GST Settings:")
+ "<br>" + "<br>".join(unidentified_gst_accounts), alert=True)
# Build itemised tax for export invoices where tax table is blank
for invoice, items in iteritems(self.invoice_items):
if invoice not in self.items_based_on_tax_rate \

View File

@ -367,7 +367,7 @@ erpnext.selling.SalesOrderController = erpnext.selling.SellingController.extend(
}
]
var d = new frappe.ui.Dialog({
title: __("Select from Items having BOM"),
title: __("Items for Raw Material Request"),
fields: fields,
primary_action: function() {
var data = d.get_values();

View File

@ -341,11 +341,11 @@ class SalesOrder(SellingController):
delivered_qty += item.delivered_qty
tot_qty += item.qty
if tot_qty != 0:
self.db_set("per_delivered", flt(delivered_qty/tot_qty) * 100,
update_modified=False)
def set_indicator(self):
"""Set indicator for portal"""
@ -372,20 +372,19 @@ class SalesOrder(SellingController):
def get_work_order_items(self, for_raw_material_request=0):
'''Returns items with BOM that already do not have a linked work order'''
items = []
for table in [self.items, self.packed_items]:
for i in table:
bom = get_default_bom_item(i.item_code)
if bom:
stock_qty = i.qty if i.doctype == 'Packed Item' else i.stock_qty
if not for_raw_material_request:
total_work_order_qty = flt(frappe.db.sql('''select sum(qty) from `tabWork Order`
where production_item=%s and sales_order=%s and sales_order_item = %s and docstatus<2''', (i.item_code, self.name, i.name))[0][0])
pending_qty = stock_qty - total_work_order_qty
else:
pending_qty = stock_qty
stock_qty = i.qty if i.doctype == 'Packed Item' else i.stock_qty
if not for_raw_material_request:
total_work_order_qty = flt(frappe.db.sql('''select sum(qty) from `tabWork Order`
where production_item=%s and sales_order=%s and sales_order_item = %s and docstatus<2''', (i.item_code, self.name, i.name))[0][0])
pending_qty = stock_qty - total_work_order_qty
else:
pending_qty = stock_qty
if pending_qty:
if pending_qty:
if bom:
items.append(dict(
name= i.name,
item_code= i.item_code,
@ -395,6 +394,16 @@ class SalesOrder(SellingController):
required_qty = pending_qty if for_raw_material_request else 0,
sales_order_item = i.name
))
else:
items.append(dict(
name= i.name,
item_code= i.item_code,
bom = '',
warehouse = i.warehouse,
pending_qty = pending_qty,
required_qty = pending_qty if for_raw_material_request else 0,
sales_order_item = i.name
))
return items
def on_recurring(self, reference_doc, auto_repeat_doc):
@ -923,10 +932,12 @@ def make_raw_material_request(items, company, sales_order, project=None):
for item in items.get('items'):
item["include_exploded_items"] = items.get('include_exploded_items')
item["ignore_existing_ordered_qty"] = items.get('ignore_existing_ordered_qty')
item["include_raw_materials_from_sales_order"] = items.get('include_raw_materials_from_sales_order')
raw_materials = get_items_for_material_requests(items, company)
raw_materials = get_items_for_material_requests(items, sales_order, company)
if not raw_materials:
frappe.msgprint(_("Material Request not created, as quantity for Raw Materials already available."))
return
material_request = frappe.new_doc('Material Request')
material_request.update(dict(
@ -951,4 +962,4 @@ def make_raw_material_request(items, company, sales_order, project=None):
material_request.flags.ignore_permissions = 1
material_request.run_method("set_missing_values")
material_request.submit()
return material_request
return material_request

View File

@ -378,7 +378,7 @@ def make_item_variant():
test_records = frappe.get_test_records('Item')
def create_item(item_code, is_stock_item=None, valuation_rate=0, warehouse=None):
def create_item(item_code, is_stock_item=None, valuation_rate=0, warehouse=None, opening_stock=None):
if not frappe.db.exists("Item", item_code):
item = frappe.new_doc("Item")
item.item_code = item_code
@ -386,6 +386,7 @@ def create_item(item_code, is_stock_item=None, valuation_rate=0, warehouse=None)
item.description = item_code
item.item_group = "All Item Groups"
item.is_stock_item = is_stock_item or 1
item.opening_stock = opening_stock or 0
item.valuation_rate = valuation_rate or 0.0
item.append("item_defaults", {
"default_warehouse": warehouse or '_Test Warehouse - _TC',

View File

@ -271,27 +271,39 @@ class StockReconciliation(StockController):
@frappe.whitelist()
def get_items(warehouse, posting_date, posting_time, company):
items = frappe.db.sql('''select i.name, i.item_name from `tabItem` i, `tabBin` bin where i.name=bin.item_code
and i.disabled=0 and bin.warehouse=%s''', (warehouse), as_dict=True)
lft, rgt = frappe.db.get_value("Warehouse", warehouse, ["lft", "rgt"])
items = frappe.db.sql("""
select i.name, i.item_name, bin.warehouse
from tabBin bin, tabItem i
where i.name=bin.item_code and i.disabled=0
and exists(select name from `tabWarehouse` where lft >= %s and rgt <= %s and name=bin.warehouse)
""", (lft, rgt))
items += frappe.db.sql('''select i.name, i.item_name from `tabItem` i, `tabItem Default` id where i.name = id.parent
and i.is_stock_item=1 and i.has_serial_no=0 and i.has_batch_no=0 and i.has_variants=0 and i.disabled=0
and id.default_warehouse=%s and id.company=%s group by i.name''', (warehouse, company), as_dict=True)
items += frappe.db.sql("""
select i.name, i.item_name, id.default_warehouse
from tabItem i, `tabItem Default` id
where i.name = id.parent
and exists(select name from `tabWarehouse` where lft >= %s and rgt <= %s and name=id.default_warehouse)
and i.is_stock_item = 1 and i.has_serial_no = 0 and i.has_batch_no = 0
and i.has_variants = 0 and i.disabled = 0 and id.company=%s
group by i.name
""", (lft, rgt, company))
res = []
for item in items:
qty, rate = get_stock_balance(item.name, warehouse, posting_date, posting_time,
for d in set(items):
stock_bal = get_stock_balance(d[0], d[2], posting_date, posting_time,
with_valuation_rate=True)
res.append({
"item_code": item.name,
"warehouse": warehouse,
"qty": qty,
"item_name": item.item_name,
"valuation_rate": rate,
"current_qty": qty,
"current_valuation_rate": rate
})
if frappe.db.get_value("Item", d[0], "disabled") == 0:
res.append({
"item_code": d[0],
"warehouse": d[2],
"qty": stock_bal[0],
"item_name": d[1],
"valuation_rate": stock_bal[1],
"current_qty": stock_bal[0],
"current_valuation_rate": stock_bal[1]
})
return res

View File

@ -12,7 +12,7 @@ from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import set_per
from erpnext.stock.stock_ledger import get_previous_sle, update_entries_after
from erpnext.stock.doctype.stock_reconciliation.stock_reconciliation import EmptyStockReconciliationItemsError, get_items
from erpnext.stock.doctype.warehouse.test_warehouse import create_warehouse
from erpnext.stock.doctype.item.test_item import make_item
from erpnext.stock.doctype.item.test_item import create_item
class TestStockReconciliation(unittest.TestCase):
def setUp(self):
@ -83,11 +83,13 @@ class TestStockReconciliation(unittest.TestCase):
def test_get_items(self):
create_warehouse("_Test Warehouse Group 1", {"is_group": 1})
create_warehouse("_Test Warehouse Ledger 1", {"is_group": 0, "parent_warehouse": "_Test Warehouse Group 1 - _TC"})
make_item("_Test Stock Reco Item", {"default_warehouse": "_Test Warehouse Ledger 1 - _TC",
"is_stock_item": 1, "opening_stock": 100, "valuation_rate": 100})
create_warehouse("_Test Warehouse Ledger 1",
{"is_group": 0, "parent_warehouse": "_Test Warehouse Group 1 - _TC"})
items = get_items("_Test Warehouse Group 1 - _TC", nowdate(), nowtime())
create_item("_Test Stock Reco Item", is_stock_item=1, valuation_rate=100,
warehouse="_Test Warehouse Ledger 1 - _TC", opening_stock=100)
items = get_items("_Test Warehouse Group 1 - _TC", nowdate(), nowtime(), "_Test Company")
self.assertEqual(["_Test Stock Reco Item", "_Test Warehouse Ledger 1 - _TC", 100],
[items[0]["item_code"], items[0]["warehouse"], items[0]["qty"]])

View File

@ -370,6 +370,8 @@ def get_price_list_rate(args, item_doc, out):
meta = frappe.get_meta(args.parenttype or args.doctype)
if meta.get_field("currency") or args.get('currency'):
pl_details = get_price_list_currency_and_exchange_rate(args)
args.update(pl_details)
validate_price_list(args)
if meta.get_field("currency") and args.price_list:
validate_conversion_rate(args, meta)
@ -554,9 +556,10 @@ def validate_conversion_rate(args, meta):
validate_conversion_rate(args.price_list_currency, args.plc_conversion_rate,
meta.get_label("plc_conversion_rate"), args.company)
args.plc_conversion_rate = flt(args.plc_conversion_rate,
get_field_precision(meta.get_field("plc_conversion_rate"),
frappe._dict({"fields": args})))
if meta.get_field("plc_conversion_rate"):
args.plc_conversion_rate = flt(args.plc_conversion_rate,
get_field_precision(meta.get_field("plc_conversion_rate"),
frappe._dict({"fields": args})))
def get_party_item_code(args, item_doc, out):
if args.transaction_type=="selling" and args.customer:
@ -792,7 +795,7 @@ def get_price_list_uom_dependant(price_list):
if not result:
throw(_("Price List {0} is disabled or does not exist").format(price_list))
return result.price_not_uom_dependant
return not result.price_not_uom_dependant
def get_price_list_currency_and_exchange_rate(args):