diff --git a/erpnext/selling/doctype/sales_order/sales_order.py b/erpnext/selling/doctype/sales_order/sales_order.py index 5bdf8da738a..81ab746fc0c 100755 --- a/erpnext/selling/doctype/sales_order/sales_order.py +++ b/erpnext/selling/doctype/sales_order/sales_order.py @@ -995,12 +995,15 @@ def get_requested_item_qty(sales_order): filters={"docstatus": 1, "sales_order": sales_order}, fields=[ "sales_order_item", + "packed_item", {"SUM": "qty", "as": "qty"}, {"SUM": "received_qty", "as": "received_qty"}, ], - group_by="sales_order_item", + group_by="sales_order_item, packed_item", ): - result[d.sales_order_item] = frappe._dict({"qty": d.qty, "received_qty": d.received_qty}) + result[d.sales_order_item or d.packed_item] = frappe._dict( + {"qty": d.qty, "received_qty": d.received_qty} + ) return result @@ -1025,10 +1028,36 @@ def make_material_request(source_name, target_doc=None): ) ) + def get_remaining_packed_item_qty(so_item): + delivered_qty = frappe.db.get_value( + "Sales Order Item", {"name": so_item.parent_detail_docname}, ["delivered_qty"] + ) + + bundle_item_qty = frappe.db.get_value( + "Product Bundle Item", {"parent": so_item.parent_item, "item_code": so_item.item_code}, ["qty"] + ) + + return flt( + ( + flt(so_item.qty) + - flt(requested_item_qty.get(so_item.name, {}).get("qty")) + - max( + flt(delivered_qty) * flt(bundle_item_qty) + - flt(requested_item_qty.get(so_item.name, {}).get("received_qty")), + 0, + ) + ) + * bundle_item_qty + ) + def update_item(source, target, source_parent): # qty is for packed items, because packed items don't have stock_qty field target.project = source_parent.project - target.qty = get_remaining_qty(source) + target.qty = ( + get_remaining_packed_item_qty(source) + if source.parentfield == "packed_items" + else get_remaining_qty(source) + ) target.stock_qty = flt(target.qty) * flt(target.conversion_factor) target.actual_qty = get_bin_details( target.item_code, target.warehouse, source_parent.company, True @@ -1058,7 +1087,8 @@ def make_material_request(source_name, target_doc=None): "Sales Order": {"doctype": "Material Request", "validation": {"docstatus": ["=", 1]}}, "Packed Item": { "doctype": "Material Request Item", - "field_map": {"parent": "sales_order", "uom": "stock_uom"}, + "field_map": {"parent": "sales_order", "uom": "stock_uom", "name": "packed_item"}, + "condition": lambda item: get_remaining_packed_item_qty(item) > 0, "postprocess": update_item, }, "Sales Order Item": { diff --git a/erpnext/stock/doctype/material_request/test_material_request.py b/erpnext/stock/doctype/material_request/test_material_request.py index 34ff6c46208..93f27d1c382 100644 --- a/erpnext/stock/doctype/material_request/test_material_request.py +++ b/erpnext/stock/doctype/material_request/test_material_request.py @@ -973,6 +973,45 @@ class TestMaterialRequest(IntegrationTestCase): self.assertRaises(OverAllowanceError, mr.submit) + def test_get_remaining_qty_from_sales_order(self): + from frappe.utils import add_to_date, today + + from erpnext.selling.doctype.product_bundle.test_product_bundle import make_product_bundle + from erpnext.selling.doctype.sales_order.sales_order import make_material_request + from erpnext.selling.doctype.sales_order.test_sales_order import make_sales_order + + sub_item_a = "_Test Bundle ItemA" + create_item(sub_item_a, is_customer_provided_item=1, customer="_Test Customer", is_purchase_item=0) + + sub_item_b = "_Test Bundle ItemB" + create_item(sub_item_b, is_customer_provided_item=1, customer="_Test Customer", is_purchase_item=0) + + bundle_item = "_Test Bundle" + create_item( + bundle_item, + is_stock_item=0, + is_customer_provided_item=1, + customer="_Test Customer", + is_purchase_item=0, + ) + + make_product_bundle(parent=bundle_item, items=[sub_item_a, sub_item_b]) + + so = make_sales_order(item_code=bundle_item) + so.submit() + + mr = make_material_request(so.name) + mr.schedule_date = add_to_date(today(), days=1, as_string=True) + mr.get("items")[0].qty = 5 + mr.get("items")[1].qty = 5 + mr.insert() + mr.submit() + + mr = make_material_request(so.name) + + self.assertEqual(mr.items[0].qty, 5) + self.assertEqual(mr.items[1].qty, 5) + def test_pending_qty_in_pick_list(self): """Test for pick list mapped doc qty from partially received Material Request Transfer""" import json diff --git a/erpnext/stock/doctype/material_request_item/material_request_item.json b/erpnext/stock/doctype/material_request_item/material_request_item.json index d8db1d9ce01..1440059c87c 100644 --- a/erpnext/stock/doctype/material_request_item/material_request_item.json +++ b/erpnext/stock/doctype/material_request_item/material_request_item.json @@ -56,6 +56,7 @@ "lead_time_date", "sales_order", "sales_order_item", + "packed_item", "col_break4", "production_plan", "material_request_plan_item", @@ -528,6 +529,16 @@ "no_copy": 1, "non_negative": 1, "read_only": 1 + }, + { + "fieldname": "packed_item", + "fieldtype": "Data", + "hidden": 1, + "label": "Packed Item", + "no_copy": 1, + "print_hide": 1, + "read_only": 1, + "search_index": 1 } ], "idx": 1, diff --git a/erpnext/stock/doctype/material_request_item/material_request_item.py b/erpnext/stock/doctype/material_request_item/material_request_item.py index d801cf5a246..3bc0c566c9d 100644 --- a/erpnext/stock/doctype/material_request_item/material_request_item.py +++ b/erpnext/stock/doctype/material_request_item/material_request_item.py @@ -37,6 +37,7 @@ class MaterialRequestItem(Document): material_request_plan_item: DF.Data | None min_order_qty: DF.Float ordered_qty: DF.Float + packed_item: DF.Data | None page_break: DF.Check parent: DF.Data parentfield: DF.Data