Requested qty calculation logic
diff --git a/erpnext/hooks.py b/erpnext/hooks.py
index 25be39d..2d7d4e5 100644
--- a/erpnext/hooks.py
+++ b/erpnext/hooks.py
@@ -47,8 +47,8 @@
"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 eeda2ce..cb9552d 100644
--- a/erpnext/stock/doctype/material_request/material_request.py
+++ b/erpnext/stock/doctype/material_request/material_request.py
@@ -79,30 +79,9 @@
# 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 @@
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 @@
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 04b7793..ab1d3cc 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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 f1ba179..b63493e 100644
--- a/erpnext/utilities/repost_stock.py
+++ b/erpnext/utilities/repost_stock.py
@@ -93,11 +93,11 @@
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