From abe3e41a17981449d778a7b5c2fb728702f76f3c Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Thu, 30 Nov 2023 10:25:22 +0530 Subject: [PATCH] fix: incorrect requested quantity for the subcontracting order (cherry picked from commit 691e3bb24fe62624cd2c39f88e7663ff690b0f1c) --- .../doctype/purchase_order/purchase_order.py | 33 +++++------ .../controllers/subcontracting_controller.py | 17 ++++++ .../subcontracting_order.py | 23 ++++++++ .../test_subcontracting_order.py | 56 +++++++++++++++++++ .../subcontracting_order_item.json | 42 +++++++++++++- .../subcontracting_order_service_item.json | 33 ++++++++++- 6 files changed, 179 insertions(+), 25 deletions(-) diff --git a/erpnext/buying/doctype/purchase_order/purchase_order.py b/erpnext/buying/doctype/purchase_order/purchase_order.py index 5f19657a05e2..595722d1eea8 100644 --- a/erpnext/buying/doctype/purchase_order/purchase_order.py +++ b/erpnext/buying/doctype/purchase_order/purchase_order.py @@ -221,6 +221,10 @@ def validate(self): self.reset_default_field_value("set_warehouse", "items", "warehouse") def validate_with_previous_doc(self): + mri_compare_fields = [["project", "="], ["item_code", "="]] + if self.is_subcontracted: + mri_compare_fields = [["project", "="]] + super(PurchaseOrder, self).validate_with_previous_doc( { "Supplier Quotation": { @@ -243,7 +247,7 @@ def validate_with_previous_doc(self): }, "Material Request Item": { "ref_dn_field": "material_request_item", - "compare_fields": [["project", "="], ["item_code", "="]], + "compare_fields": mri_compare_fields, "is_child_table": True, }, } @@ -417,23 +421,6 @@ def check_on_hold_or_closed_status(self): check_list.append(d.material_request) check_on_hold_or_closed_status("Material Request", d.material_request) - def update_requested_qty(self): - material_request_map = {} - for d in self.get("items"): - if d.material_request_item: - material_request_map.setdefault(d.material_request, []).append(d.material_request_item) - - 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), frappe.InvalidStatusError - ) - - mr_obj.update_requested_qty(mr_item_rows) - def update_ordered_qty(self, po_item_rows=None): """update requested qty (before ordered_qty is updated)""" item_wh_list = [] @@ -475,7 +462,9 @@ def on_submit(self): self.update_status_updater() self.update_prevdoc_status() - self.update_requested_qty() + if not self.is_subcontracted or self.is_old_subcontracting_flow: + self.update_requested_qty() + self.update_ordered_qty() self.validate_budget() self.update_reserved_qty_for_subcontract() @@ -509,7 +498,9 @@ def on_cancel(self): # Must be called after updating ordered qty in Material Request # bin uses Material Request Items to recalculate & update - self.update_requested_qty() + if not self.is_subcontracted or self.is_old_subcontracting_flow: + self.update_requested_qty() + self.update_ordered_qty() self.update_blanket_order() @@ -875,6 +866,8 @@ def get_mapped_subcontracting_order(source_name, target_doc=None): "doctype": "Subcontracting Order Service Item", "field_map": { "name": "purchase_order_item", + "material_request": "material_request", + "material_request_item": "material_request_item", }, "field_no_map": [], }, diff --git a/erpnext/controllers/subcontracting_controller.py b/erpnext/controllers/subcontracting_controller.py index 3d55a087bd8d..b52fbad1f0a8 100644 --- a/erpnext/controllers/subcontracting_controller.py +++ b/erpnext/controllers/subcontracting_controller.py @@ -952,6 +952,23 @@ def sub_contracted_items(self): return self._sub_contracted_items + def update_requested_qty(self): + material_request_map = {} + for d in self.get("items"): + if d.material_request_item: + material_request_map.setdefault(d.material_request, []).append(d.material_request_item) + + 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), frappe.InvalidStatusError + ) + + mr_obj.update_requested_qty(mr_item_rows) + def get_item_details(items): item = frappe.qb.DocType("Item") diff --git a/erpnext/subcontracting/doctype/subcontracting_order/subcontracting_order.py b/erpnext/subcontracting/doctype/subcontracting_order/subcontracting_order.py index 68d98fc73428..6c187f836843 100644 --- a/erpnext/subcontracting/doctype/subcontracting_order/subcontracting_order.py +++ b/erpnext/subcontracting/doctype/subcontracting_order/subcontracting_order.py @@ -80,6 +80,23 @@ class SubcontractingOrder(SubcontractingController): transaction_date: DF.Date # end: auto-generated types + def __init__(self, *args, **kwargs): + super(SubcontractingOrder, self).__init__(*args, **kwargs) + + self.status_updater = [ + { + "source_dt": "Subcontracting Order Item", + "target_dt": "Material Request Item", + "join_field": "material_request_item", + "target_field": "ordered_qty", + "target_parent_dt": "Material Request", + "target_parent_field": "per_ordered", + "target_ref_field": "stock_qty", + "source_field": "qty", + "percent_join_field": "material_request", + } + ] + def before_validate(self): super(SubcontractingOrder, self).before_validate() @@ -93,11 +110,15 @@ def validate(self): self.reset_default_field_value("set_warehouse", "items", "warehouse") def on_submit(self): + self.update_prevdoc_status() + self.update_requested_qty() self.update_ordered_qty_for_subcontracting() self.update_reserved_qty_for_subcontracting() self.update_status() def on_cancel(self): + self.update_prevdoc_status() + self.update_requested_qty() self.update_ordered_qty_for_subcontracting() self.update_reserved_qty_for_subcontracting() self.update_status() @@ -237,6 +258,8 @@ def populate_items_table(self): "stock_uom": item.stock_uom, "bom": bom, "purchase_order_item": si.purchase_order_item, + "material_request": si.material_request, + "material_request_item": si.material_request_item, } ) else: diff --git a/erpnext/subcontracting/doctype/subcontracting_order/test_subcontracting_order.py b/erpnext/subcontracting/doctype/subcontracting_order/test_subcontracting_order.py index 355785893513..37dabf1bfbe7 100644 --- a/erpnext/subcontracting/doctype/subcontracting_order/test_subcontracting_order.py +++ b/erpnext/subcontracting/doctype/subcontracting_order/test_subcontracting_order.py @@ -628,6 +628,62 @@ def test_ordered_qty_for_subcontracting_order(self): self.assertEqual(ordered_qty + 10, new_ordered_qty) + def test_requested_qty_for_subcontracting_order(self): + from erpnext.stock.doctype.material_request.material_request import make_purchase_order + from erpnext.stock.doctype.material_request.test_material_request import make_material_request + + requested_qty = frappe.db.get_value( + "Bin", + filters={"warehouse": "_Test Warehouse - _TC", "item_code": "Subcontracted Item SA8"}, + fieldname="indented_qty", + ) + requested_qty = flt(requested_qty) + + mr = make_material_request( + item_code="Subcontracted Item SA8", + material_request_type="Purchase", + qty=10, + ) + + self.assertTrue(mr.docstatus == 1) + + new_requested_qty = frappe.db.get_value( + "Bin", + filters={"warehouse": "_Test Warehouse - _TC", "item_code": "Subcontracted Item SA8"}, + fieldname="indented_qty", + ) + new_requested_qty = flt(new_requested_qty) + + self.assertEqual(requested_qty + 10, new_requested_qty) + + po = make_purchase_order(mr.name) + po.is_subcontracted = 1 + po.supplier = "_Test Supplier" + po.items[0].fg_item = "Subcontracted Item SA8" + po.items[0].fg_item_qty = 10 + po.items[0].item_code = "Subcontracted Service Item 8" + po.items[0].item_name = "Subcontracted Service Item 8" + po.items[0].qty = 10 + po.supplier_warehouse = "_Test Warehouse 1 - _TC" + po.save() + po.submit() + + self.assertTrue(po.items[0].material_request) + self.assertTrue(po.items[0].material_request_item) + + sco = create_subcontracting_order(po_name=po.name) + self.assertTrue(sco.items[0].material_request) + self.assertTrue(sco.items[0].material_request_item) + + new_requested_qty = frappe.db.get_value( + "Bin", + filters={"warehouse": "_Test Warehouse - _TC", "item_code": "Subcontracted Item SA8"}, + fieldname="indented_qty", + ) + new_requested_qty = flt(new_requested_qty) + + self.assertEqual(requested_qty, new_requested_qty) + def create_subcontracting_order(**args): args = frappe._dict(args) diff --git a/erpnext/subcontracting/doctype/subcontracting_order_item/subcontracting_order_item.json b/erpnext/subcontracting/doctype/subcontracting_order_item/subcontracting_order_item.json index 911e903d23f7..1ca90c31654d 100644 --- a/erpnext/subcontracting/doctype/subcontracting_order_item/subcontracting_order_item.json +++ b/erpnext/subcontracting/doctype/subcontracting_order_item/subcontracting_order_item.json @@ -40,13 +40,18 @@ "manufacture_section", "manufacturer", "manufacturer_part_no", + "column_break_impp", + "reference_section", + "material_request", + "column_break_fpyl", + "material_request_item", "accounting_dimensions_section", "cost_center", "dimension_col_break", "project", "section_break_34", - "page_break", - "purchase_order_item" + "purchase_order_item", + "page_break" ], "fields": [ { @@ -334,6 +339,37 @@ "label": "Project", "options": "Project" }, + { + "fieldname": "column_break_impp", + "fieldtype": "Column Break" + }, + { + "fieldname": "material_request", + "fieldtype": "Link", + "label": "Material Request", + "no_copy": 1, + "options": "Material Request", + "read_only": 1, + "search_index": 1 + }, + { + "fieldname": "material_request_item", + "fieldtype": "Data", + "label": "Material Request Item", + "no_copy": 1, + "read_only": 1, + "search_index": 1 + }, + { + "collapsible": 1, + "fieldname": "reference_section", + "fieldtype": "Section Break", + "label": "Reference" + }, + { + "fieldname": "column_break_fpyl", + "fieldtype": "Column Break" + }, { "fieldname": "purchase_order_item", "fieldtype": "Data", @@ -348,7 +384,7 @@ "index_web_pages_for_search": 1, "istable": 1, "links": [], - "modified": "2023-11-23 16:56:22.182698", + "modified": "2023-11-30 15:29:43.744618", "modified_by": "Administrator", "module": "Subcontracting", "name": "Subcontracting Order Item", diff --git a/erpnext/subcontracting/doctype/subcontracting_order_service_item/subcontracting_order_service_item.json b/erpnext/subcontracting/doctype/subcontracting_order_service_item/subcontracting_order_service_item.json index dc18543b0416..f1e94e125a98 100644 --- a/erpnext/subcontracting/doctype/subcontracting_order_service_item/subcontracting_order_service_item.json +++ b/erpnext/subcontracting/doctype/subcontracting_order_service_item/subcontracting_order_service_item.json @@ -19,7 +19,11 @@ "fg_item", "column_break_12", "fg_item_qty", - "purchase_order_item" + "purchase_order_item", + "section_break_kphn", + "material_request", + "column_break_piqi", + "material_request_item" ], "fields": [ { @@ -122,11 +126,36 @@ "no_copy": 1, "read_only": 1, "search_index": 1 + }, + { + "collapsible": 1, + "fieldname": "section_break_kphn", + "fieldtype": "Section Break", + "label": "Reference" + }, + { + "fieldname": "material_request", + "fieldtype": "Link", + "label": "Material Request", + "no_copy": 1, + "options": "Material Request", + "read_only": 1 + }, + { + "fieldname": "column_break_piqi", + "fieldtype": "Column Break" + }, + { + "fieldname": "material_request_item", + "fieldtype": "Data", + "label": "Material Request Item", + "no_copy": 1, + "read_only": 1 } ], "istable": 1, "links": [], - "modified": "2023-11-23 17:05:04.561948", + "modified": "2023-11-30 13:29:31.017440", "modified_by": "Administrator", "module": "Subcontracting", "name": "Subcontracting Order Service Item",