From 4acd431b9279452eaa83dfdf4a079cbfb488a93e Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Tue, 4 Nov 2014 15:32:31 +0530 Subject: [PATCH] Requested qty calculation logic --- erpnext/hooks.py | 4 +- .../material_request/material_request.py | 111 ++++++------------ .../material_request/test_material_request.py | 69 +++++++---- erpnext/utilities/repost_stock.py | 10 +- 4 files changed, 92 insertions(+), 102 deletions(-) diff --git a/erpnext/hooks.py b/erpnext/hooks.py index 25be39d76c1..2d7d4e52ca5 100644 --- a/erpnext/hooks.py +++ b/erpnext/hooks.py @@ -47,8 +47,8 @@ doc_events = { "on_update": "erpnext.home.make_comment_feed" }, "Stock Entry": { - "on_submit": "erpnext.stock.doctype.material_request.material_request.update_completed_qty", - "on_cancel": "erpnext.stock.doctype.material_request.material_request.update_completed_qty" + "on_submit": "erpnext.stock.doctype.material_request.material_request.update_completed_and_requested_qty", + "on_cancel": "erpnext.stock.doctype.material_request.material_request.update_completed_and_requested_qty" }, "User": { "validate": "erpnext.hr.doctype.employee.employee.validate_employee_role", diff --git a/erpnext/stock/doctype/material_request/material_request.py b/erpnext/stock/doctype/material_request/material_request.py index eeda2ce6133..cb9552d1fcd 100644 --- a/erpnext/stock/doctype/material_request/material_request.py +++ b/erpnext/stock/doctype/material_request/material_request.py @@ -79,30 +79,9 @@ class MaterialRequest(BuyingController): # NOTE: Since Item BOM and FG quantities are combined, using current data, it cannot be validated # Though the creation of Material Request from a Production Plan can be rethought to fix this - def update_bin(self, is_submit, is_stopped): - """ Update Quantity Requested for Purchase in Bin for Material Request of type 'Purchase'""" - - from erpnext.stock.utils import update_bin - for d in self.get('indent_details'): - if frappe.db.get_value("Item", d.item_code, "is_stock_item") == "Yes": - if not d.warehouse: - frappe.throw(_("Warehouse required for stock Item {0}").format(d.item_code)) - - qty =flt(d.qty) - if is_stopped: - qty = (d.qty > d.ordered_qty) and flt(flt(d.qty) - flt(d.ordered_qty)) or 0 - - args = { - "item_code": d.item_code, - "warehouse": d.warehouse, - "indented_qty": (is_submit and 1 or -1) * flt(qty), - "posting_date": self.transaction_date - } - update_bin(args) - def on_submit(self): frappe.db.set(self, 'status', 'Submitted') - self.update_bin(is_submit = 1, is_stopped = 0) + self.update_requested_qty() def check_modified_date(self): mod_db = frappe.db.sql("""select modified from `tabMaterial Request` where name = %s""", @@ -115,23 +94,18 @@ class MaterialRequest(BuyingController): def update_status(self, status): self.check_modified_date() - self.update_bin(is_submit = (status == 'Submitted') and 1 or 0, is_stopped = 1) + self.update_requested_qty() frappe.db.set(self, 'status', cstr(status)) frappe.msgprint(_("Status updated to {0}").format(_(status))) def on_cancel(self): - # Step 1:=> Get Purchase Common Obj pc_obj = frappe.get_doc('Purchase Common') - # Step 2:=> Check for stopped status pc_obj.check_for_stopped_status(self.doctype, self.name) - - # Step 3:=> Check if Purchase Order has been submitted against current Material Request pc_obj.check_docstatus(check = 'Next', doctype = 'Purchase Order', docname = self.name, detail_doctype = 'Purchase Order Item') - # Step 4:=> Update Bin - self.update_bin(is_submit = 0, is_stopped = (cstr(self.status) == 'Stopped') and 1 or 0) - # Step 5:=> Set Status + self.update_requested_qty() + frappe.db.set(self,'status','Cancelled') def update_completed_qty(self, mr_items=None): @@ -162,56 +136,47 @@ class MaterialRequest(BuyingController): self.per_ordered = flt((per_ordered / flt(len(item_doclist))) * 100.0, 2) frappe.db.set_value(self.doctype, self.name, "per_ordered", self.per_ordered) -def update_completed_qty(doc, method): - if doc.doctype == "Stock Entry": + def update_requested_qty(self, mr_item_rows=None): + """update requested qty (before ordered_qty is updated)""" + from erpnext.stock.utils import get_bin + + def _update_requested_qty(item_code, warehouse): + requested_qty = frappe.db.sql("""select sum(mr_item.qty - ifnull(mr_item.ordered_qty, 0)) + from `tabMaterial Request Item` mr_item, `tabMaterial Request` mr + where mr_item.item_code=%s and mr_item.warehouse=%s + and mr_item.qty > ifnull(mr_item.ordered_qty, 0) and mr_item.parent=mr.name + and mr.status!='Stopped' and mr.docstatus=1""", (item_code, warehouse)) + + bin_doc = get_bin(item_code, warehouse) + bin_doc.indented_qty = flt(requested_qty[0][0]) if requested_qty else 0 + bin_doc.save() + + item_wh_list = [] + for d in self.get("indent_details"): + if (not mr_item_rows or d.name in mr_item_rows) and [d.item_code, d.warehouse] not in item_wh_list \ + and frappe.db.get_value("Item", d.item_code, "is_stock_item") == "Yes" and d.warehouse: + item_wh_list.append([d.item_code, d.warehouse]) + + for item_code, warehouse in item_wh_list: + _update_requested_qty(item_code, warehouse) + +def update_completed_and_requested_qty(stock_entry, method): + if stock_entry.doctype == "Stock Entry": material_request_map = {} - for d in doc.get("mtn_details"): + for d in stock_entry.get("mtn_details"): if d.material_request: material_request_map.setdefault(d.material_request, []).append(d.material_request_item) - for mr_name, mr_items in material_request_map.items(): - mr_obj = frappe.get_doc("Material Request", mr_name) + for mr, mr_item_rows in material_request_map.items(): + if mr and mr_item_rows: + mr_obj = frappe.get_doc("Material Request", mr) - if mr_obj.status in ["Stopped", "Cancelled"]: - frappe.throw(_("Material Request {0} is cancelled or stopped").format(mr_obj.name), - frappe.InvalidStatusError) + if mr_obj.status in ["Stopped", "Cancelled"]: + frappe.throw(_("Material Request {0} is cancelled or stopped").format(mr), frappe.InvalidStatusError) - _update_requested_qty(doc, mr_obj, mr_items) - - # update ordered percentage and qty - mr_obj.update_completed_qty(mr_items) - -def _update_requested_qty(doc, mr_obj, mr_items): - """update requested qty (before ordered_qty is updated)""" - from erpnext.stock.utils import update_bin - for mr_item_name in mr_items: - mr_item = mr_obj.get("indent_details", {"name": mr_item_name}) - se_detail = doc.get("mtn_details", {"material_request": mr_obj.name, - "material_request_item": mr_item_name}) - - if mr_item and se_detail: - mr_item = mr_item[0] - se_detail = se_detail[0] - mr_item.ordered_qty = flt(mr_item.ordered_qty) - mr_item.qty = flt(mr_item.qty) - se_detail.transfer_qty = flt(se_detail.transfer_qty) - - if se_detail.docstatus == 2 and mr_item.ordered_qty > mr_item.qty \ - and se_detail.transfer_qty == mr_item.ordered_qty: - add_indented_qty = mr_item.qty - elif se_detail.docstatus == 1 and \ - mr_item.ordered_qty + se_detail.transfer_qty > mr_item.qty: - add_indented_qty = mr_item.qty - mr_item.ordered_qty - else: - add_indented_qty = se_detail.transfer_qty - - update_bin({ - "item_code": se_detail.item_code, - "warehouse": se_detail.t_warehouse, - "indented_qty": (se_detail.docstatus==2 and 1 or -1) * add_indented_qty, - "posting_date": doc.posting_date, - }) + mr_obj.update_completed_qty(mr_item_rows) + mr_obj.update_requested_qty(mr_item_rows) def set_missing_values(source, target_doc): target_doc.run_method("set_missing_values") diff --git a/erpnext/stock/doctype/material_request/test_material_request.py b/erpnext/stock/doctype/material_request/test_material_request.py index 04b7793b994..ab1d3ccd95e 100644 --- a/erpnext/stock/doctype/material_request/test_material_request.py +++ b/erpnext/stock/doctype/material_request/test_material_request.py @@ -58,12 +58,6 @@ class TestMaterialRequest(unittest.TestCase): self.assertEquals(se.doctype, "Stock Entry") self.assertEquals(len(se.get("mtn_details")), len(mr.get("indent_details"))) - def _test_requested_qty(self, qty1, qty2): - self.assertEqual(flt(frappe.db.get_value("Bin", {"item_code": "_Test Item Home Desktop 100", - "warehouse": "_Test Warehouse - _TC"}, "indented_qty")), qty1) - self.assertEqual(flt(frappe.db.get_value("Bin", {"item_code": "_Test Item Home Desktop 200", - "warehouse": "_Test Warehouse - _TC"}, "indented_qty")), qty2) - def _insert_stock_entry(self, qty1, qty2): se = frappe.get_doc({ "company": "_Test Company", @@ -103,7 +97,8 @@ class TestMaterialRequest(unittest.TestCase): se.submit() def test_completed_qty_for_purchase(self): - frappe.db.sql("""delete from `tabBin`""") + existing_requested_qty_item1 = self._get_requested_qty("_Test Item Home Desktop 100", "_Test Warehouse - _TC") + existing_requested_qty_item2 = self._get_requested_qty("_Test Item Home Desktop 200", "_Test Warehouse - _TC") # submit material request of type Purchase mr = frappe.copy_doc(test_records[0]) @@ -115,8 +110,6 @@ class TestMaterialRequest(unittest.TestCase): self.assertEquals(mr.get("indent_details")[0].ordered_qty, 0) self.assertEquals(mr.get("indent_details")[1].ordered_qty, 0) - self._test_requested_qty(54.0, 3.0) - # map a purchase order from erpnext.stock.doctype.material_request.material_request import make_purchase_order po_doc = make_purchase_order(mr.name) @@ -149,7 +142,12 @@ class TestMaterialRequest(unittest.TestCase): self.assertEquals(mr.per_ordered, 50) self.assertEquals(mr.get("indent_details")[0].ordered_qty, 27.0) self.assertEquals(mr.get("indent_details")[1].ordered_qty, 1.5) - self._test_requested_qty(27.0, 1.5) + + current_requested_qty_item1 = self._get_requested_qty("_Test Item Home Desktop 100", "_Test Warehouse - _TC") + current_requested_qty_item2 = self._get_requested_qty("_Test Item Home Desktop 200", "_Test Warehouse - _TC") + + self.assertEquals(current_requested_qty_item1, existing_requested_qty_item1 + 27.0) + self.assertEquals(current_requested_qty_item2, existing_requested_qty_item2 + 1.5) po.cancel() # check if per complete is as expected @@ -158,11 +156,15 @@ class TestMaterialRequest(unittest.TestCase): self.assertEquals(mr.get("indent_details")[0].ordered_qty, None) self.assertEquals(mr.get("indent_details")[1].ordered_qty, None) - self._test_requested_qty(54.0, 3.0) + current_requested_qty_item1 = self._get_requested_qty("_Test Item Home Desktop 100", "_Test Warehouse - _TC") + current_requested_qty_item2 = self._get_requested_qty("_Test Item Home Desktop 200", "_Test Warehouse - _TC") + + self.assertEquals(current_requested_qty_item1, existing_requested_qty_item1 + 54.0) + self.assertEquals(current_requested_qty_item2, existing_requested_qty_item2 + 3.0) def test_completed_qty_for_transfer(self): - frappe.db.sql("""delete from `tabBin`""") - frappe.db.sql("""delete from `tabStock Ledger Entry`""") + existing_requested_qty_item1 = self._get_requested_qty("_Test Item Home Desktop 100", "_Test Warehouse - _TC") + existing_requested_qty_item2 = self._get_requested_qty("_Test Item Home Desktop 200", "_Test Warehouse - _TC") # submit material request of type Purchase mr = frappe.copy_doc(test_records[0]) @@ -175,7 +177,11 @@ class TestMaterialRequest(unittest.TestCase): self.assertEquals(mr.get("indent_details")[0].ordered_qty, 0) self.assertEquals(mr.get("indent_details")[1].ordered_qty, 0) - self._test_requested_qty(54.0, 3.0) + current_requested_qty_item1 = self._get_requested_qty("_Test Item Home Desktop 100", "_Test Warehouse - _TC") + current_requested_qty_item2 = self._get_requested_qty("_Test Item Home Desktop 200", "_Test Warehouse - _TC") + + self.assertEquals(current_requested_qty_item1, existing_requested_qty_item1 + 54.0) + self.assertEquals(current_requested_qty_item2, existing_requested_qty_item2 + 3.0) from erpnext.stock.doctype.material_request.material_request import make_stock_entry @@ -226,7 +232,11 @@ class TestMaterialRequest(unittest.TestCase): self.assertEquals(mr.get("indent_details")[0].ordered_qty, 27.0) self.assertEquals(mr.get("indent_details")[1].ordered_qty, 1.5) - self._test_requested_qty(27.0, 1.5) + current_requested_qty_item1 = self._get_requested_qty("_Test Item Home Desktop 100", "_Test Warehouse - _TC") + current_requested_qty_item2 = self._get_requested_qty("_Test Item Home Desktop 200", "_Test Warehouse - _TC") + + self.assertEquals(current_requested_qty_item1, existing_requested_qty_item1 + 27.0) + self.assertEquals(current_requested_qty_item2, existing_requested_qty_item2 + 1.5) # check if per complete is as expected for Stock Entry cancelled se.cancel() @@ -235,11 +245,15 @@ class TestMaterialRequest(unittest.TestCase): self.assertEquals(mr.get("indent_details")[0].ordered_qty, 0) self.assertEquals(mr.get("indent_details")[1].ordered_qty, 0) - self._test_requested_qty(54.0, 3.0) + current_requested_qty_item1 = self._get_requested_qty("_Test Item Home Desktop 100", "_Test Warehouse - _TC") + current_requested_qty_item2 = self._get_requested_qty("_Test Item Home Desktop 200", "_Test Warehouse - _TC") + + self.assertEquals(current_requested_qty_item1, existing_requested_qty_item1 + 54.0) + self.assertEquals(current_requested_qty_item2, existing_requested_qty_item2 + 3.0) def test_completed_qty_for_over_transfer(self): - frappe.db.sql("""delete from `tabBin`""") - frappe.db.sql("""delete from `tabStock Ledger Entry`""") + existing_requested_qty_item1 = self._get_requested_qty("_Test Item Home Desktop 100", "_Test Warehouse - _TC") + existing_requested_qty_item2 = self._get_requested_qty("_Test Item Home Desktop 200", "_Test Warehouse - _TC") # submit material request of type Purchase mr = frappe.copy_doc(test_records[0]) @@ -252,8 +266,6 @@ class TestMaterialRequest(unittest.TestCase): self.assertEquals(mr.get("indent_details")[0].ordered_qty, 0) self.assertEquals(mr.get("indent_details")[1].ordered_qty, 0) - self._test_requested_qty(54.0, 3.0) - # map a stock entry from erpnext.stock.doctype.material_request.material_request import make_stock_entry @@ -297,7 +309,12 @@ class TestMaterialRequest(unittest.TestCase): self.assertEquals(mr.per_ordered, 100) self.assertEquals(mr.get("indent_details")[0].ordered_qty, 60.0) self.assertEquals(mr.get("indent_details")[1].ordered_qty, 3.0) - self._test_requested_qty(0.0, 0.0) + + current_requested_qty_item1 = self._get_requested_qty("_Test Item Home Desktop 100", "_Test Warehouse - _TC") + current_requested_qty_item2 = self._get_requested_qty("_Test Item Home Desktop 200", "_Test Warehouse - _TC") + + self.assertEquals(current_requested_qty_item1, existing_requested_qty_item1) + self.assertEquals(current_requested_qty_item2, existing_requested_qty_item2) # check if per complete is as expected for Stock Entry cancelled se.cancel() @@ -306,7 +323,11 @@ class TestMaterialRequest(unittest.TestCase): self.assertEquals(mr.get("indent_details")[0].ordered_qty, 0) self.assertEquals(mr.get("indent_details")[1].ordered_qty, 0) - self._test_requested_qty(54.0, 3.0) + current_requested_qty_item1 = self._get_requested_qty("_Test Item Home Desktop 100", "_Test Warehouse - _TC") + current_requested_qty_item2 = self._get_requested_qty("_Test Item Home Desktop 200", "_Test Warehouse - _TC") + + self.assertEquals(current_requested_qty_item1, existing_requested_qty_item1 + 54.0) + self.assertEquals(current_requested_qty_item2, existing_requested_qty_item2 + 3.0) def test_incorrect_mapping_of_stock_entry(self): # submit material request of type Purchase @@ -348,5 +369,9 @@ class TestMaterialRequest(unittest.TestCase): mr.company = "_Test Company 1" self.assertRaises(InvalidWarehouseCompany, mr.insert) + def _get_requested_qty(self, item_code, warehouse): + return flt(frappe.db.get_value("Bin", {"item_code": item_code, "warehouse": warehouse}, "indented_qty")) + + test_dependencies = ["Currency Exchange"] test_records = frappe.get_test_records('Material Request') diff --git a/erpnext/utilities/repost_stock.py b/erpnext/utilities/repost_stock.py index f1ba1798cee..b63493e1307 100644 --- a/erpnext/utilities/repost_stock.py +++ b/erpnext/utilities/repost_stock.py @@ -93,11 +93,11 @@ def get_reserved_qty(item_code, warehouse): return flt(reserved_qty[0][0]) if reserved_qty else 0 def get_indented_qty(item_code, warehouse): - indented_qty = frappe.db.sql("""select sum(pr_item.qty - ifnull(pr_item.ordered_qty, 0)) - from `tabMaterial Request Item` pr_item, `tabMaterial Request` pr - where pr_item.item_code=%s and pr_item.warehouse=%s - and pr_item.qty > ifnull(pr_item.ordered_qty, 0) and pr_item.parent=pr.name - and pr.status!='Stopped' and pr.docstatus=1""", (item_code, warehouse)) + indented_qty = frappe.db.sql("""select sum(mr_item.qty - ifnull(mr_item.ordered_qty, 0)) + from `tabMaterial Request Item` mr_item, `tabMaterial Request` mr + where mr_item.item_code=%s and mr_item.warehouse=%s + and mr_item.qty > ifnull(mr_item.ordered_qty, 0) and mr_item.parent=mr.name + and mr.status!='Stopped' and mr.docstatus=1""", (item_code, warehouse)) return flt(indented_qty[0][0]) if indented_qty else 0