mirror of
https://github.com/frappe/erpnext.git
synced 2026-06-06 21:59:13 +00:00
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:
@@ -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": {
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user