Merge pull request #51163 from aerele/mr-product-bundle

fix(material-request): get remaining qty on partial transaction with product bundle
This commit is contained in:
rohitwaghchaure
2026-01-06 18:16:43 +05:30
committed by GitHub
4 changed files with 85 additions and 4 deletions

View File

@@ -995,12 +995,15 @@ def get_requested_item_qty(sales_order):
filters={"docstatus": 1, "sales_order": sales_order}, filters={"docstatus": 1, "sales_order": sales_order},
fields=[ fields=[
"sales_order_item", "sales_order_item",
"packed_item",
{"SUM": "qty", "as": "qty"}, {"SUM": "qty", "as": "qty"},
{"SUM": "received_qty", "as": "received_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 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): def update_item(source, target, source_parent):
# qty is for packed items, because packed items don't have stock_qty field # qty is for packed items, because packed items don't have stock_qty field
target.project = source_parent.project 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.stock_qty = flt(target.qty) * flt(target.conversion_factor)
target.actual_qty = get_bin_details( target.actual_qty = get_bin_details(
target.item_code, target.warehouse, source_parent.company, True 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]}}, "Sales Order": {"doctype": "Material Request", "validation": {"docstatus": ["=", 1]}},
"Packed Item": { "Packed Item": {
"doctype": "Material Request 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, "postprocess": update_item,
}, },
"Sales Order Item": { "Sales Order Item": {

View File

@@ -973,6 +973,45 @@ class TestMaterialRequest(IntegrationTestCase):
self.assertRaises(OverAllowanceError, mr.submit) 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): def test_pending_qty_in_pick_list(self):
"""Test for pick list mapped doc qty from partially received Material Request Transfer""" """Test for pick list mapped doc qty from partially received Material Request Transfer"""
import json import json

View File

@@ -56,6 +56,7 @@
"lead_time_date", "lead_time_date",
"sales_order", "sales_order",
"sales_order_item", "sales_order_item",
"packed_item",
"col_break4", "col_break4",
"production_plan", "production_plan",
"material_request_plan_item", "material_request_plan_item",
@@ -528,6 +529,16 @@
"no_copy": 1, "no_copy": 1,
"non_negative": 1, "non_negative": 1,
"read_only": 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, "idx": 1,

View File

@@ -37,6 +37,7 @@ class MaterialRequestItem(Document):
material_request_plan_item: DF.Data | None material_request_plan_item: DF.Data | None
min_order_qty: DF.Float min_order_qty: DF.Float
ordered_qty: DF.Float ordered_qty: DF.Float
packed_item: DF.Data | None
page_break: DF.Check page_break: DF.Check
parent: DF.Data parent: DF.Data
parentfield: DF.Data parentfield: DF.Data