mirror of
https://github.com/frappe/erpnext.git
synced 2026-05-26 08:24:47 +00:00
feat: Adding requested qty in packed item (#53486)
* feat: Adding requested qty in packed item
* fix: correct import path
---------
Co-authored-by: Nishka Gosalia <nishkagosalia@Nishkas-MacBook-Air.local>
Co-authored-by: Mihir Kandoi <kandoimihir@gmail.com>
(cherry picked from commit 953f089c06)
# Conflicts:
# erpnext/controllers/status_updater.py
# erpnext/patches.txt
# erpnext/selling/doctype/sales_order/sales_order.py
This commit is contained in:
@@ -342,7 +342,14 @@ class StatusUpdater(Document):
|
|||||||
):
|
):
|
||||||
return
|
return
|
||||||
|
|
||||||
|
<<<<<<< HEAD
|
||||||
if args["target_dt"] != "Quotation Item":
|
if args["target_dt"] != "Quotation Item":
|
||||||
|
=======
|
||||||
|
if args["source_dt"] != "Pick List Item" and args["target_dt"] not in [
|
||||||
|
"Quotation Item",
|
||||||
|
"Packed Item",
|
||||||
|
]:
|
||||||
|
>>>>>>> 953f089c06 (feat: Adding requested qty in packed item (#53486))
|
||||||
if qty_or_amount == "qty":
|
if qty_or_amount == "qty":
|
||||||
action_msg = _(
|
action_msg = _(
|
||||||
'To allow over receipt / delivery, update "Over Receipt/Delivery Allowance" in Stock Settings or the Item.'
|
'To allow over receipt / delivery, update "Over Receipt/Delivery Allowance" in Stock Settings or the Item.'
|
||||||
|
|||||||
@@ -432,3 +432,10 @@ erpnext.patches.v16_0.set_ordered_qty_in_quotation_item
|
|||||||
erpnext.patches.v15_0.replace_http_with_https_in_sales_partner
|
erpnext.patches.v15_0.replace_http_with_https_in_sales_partner
|
||||||
erpnext.patches.v16_0.add_portal_redirects
|
erpnext.patches.v16_0.add_portal_redirects
|
||||||
erpnext.patches.v16_0.update_order_qty_and_requested_qty_based_on_mr_and_po
|
erpnext.patches.v16_0.update_order_qty_and_requested_qty_based_on_mr_and_po
|
||||||
|
<<<<<<< HEAD
|
||||||
|
=======
|
||||||
|
erpnext.patches.v16_0.complete_onboarding_steps_for_older_sites #2
|
||||||
|
erpnext.patches.v16_0.enable_serial_batch_setting
|
||||||
|
erpnext.patches.v16_0.co_by_product_patch
|
||||||
|
erpnext.patches.v16_0.update_requested_qty_packed_item
|
||||||
|
>>>>>>> 953f089c06 (feat: Adding requested qty in packed item (#53486))
|
||||||
|
|||||||
24
erpnext/patches/v16_0/update_requested_qty_packed_item.py
Normal file
24
erpnext/patches/v16_0/update_requested_qty_packed_item.py
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
import frappe
|
||||||
|
from frappe.query_builder.functions import Sum
|
||||||
|
|
||||||
|
|
||||||
|
def execute():
|
||||||
|
MaterialRequestItem = frappe.qb.DocType("Material Request Item")
|
||||||
|
|
||||||
|
mri_query = (
|
||||||
|
frappe.qb.from_(MaterialRequestItem)
|
||||||
|
.select(MaterialRequestItem.packed_item, Sum(MaterialRequestItem.qty))
|
||||||
|
.where((MaterialRequestItem.packed_item.isnotnull()) & (MaterialRequestItem.docstatus == 1))
|
||||||
|
.groupby(MaterialRequestItem.packed_item)
|
||||||
|
)
|
||||||
|
|
||||||
|
mri_data = mri_query.run()
|
||||||
|
|
||||||
|
if not mri_data:
|
||||||
|
return
|
||||||
|
|
||||||
|
updates_against_mr = {data[0]: {"requested_qty": data[1]} for data in mri_data}
|
||||||
|
|
||||||
|
frappe.db.auto_commit_on_many_writes = True
|
||||||
|
frappe.db.bulk_update("Packed Item", updates_against_mr)
|
||||||
|
frappe.db.auto_commit_on_many_writes = False
|
||||||
@@ -838,6 +838,7 @@ def close_or_unclose_sales_orders(names, status):
|
|||||||
|
|
||||||
def get_requested_item_qty(sales_order):
|
def get_requested_item_qty(sales_order):
|
||||||
result = {}
|
result = {}
|
||||||
|
<<<<<<< HEAD
|
||||||
for d in frappe.db.get_all(
|
for d in frappe.db.get_all(
|
||||||
"Material Request Item",
|
"Material Request Item",
|
||||||
filters={"docstatus": 1, "sales_order": sales_order},
|
filters={"docstatus": 1, "sales_order": sales_order},
|
||||||
@@ -845,6 +846,21 @@ def get_requested_item_qty(sales_order):
|
|||||||
group_by="sales_order_item",
|
group_by="sales_order_item",
|
||||||
):
|
):
|
||||||
result[d.sales_order_item] = frappe._dict({"qty": d.qty, "received_qty": d.received_qty})
|
result[d.sales_order_item] = frappe._dict({"qty": d.qty, "received_qty": d.received_qty})
|
||||||
|
=======
|
||||||
|
|
||||||
|
so = frappe.get_doc("Sales Order", sales_order)
|
||||||
|
|
||||||
|
for item in so.items:
|
||||||
|
if is_product_bundle(item.item_code):
|
||||||
|
for packed_item in so.get("packed_items"):
|
||||||
|
if (
|
||||||
|
packed_item.parent_item == item.item_code
|
||||||
|
and packed_item.parent_detail_docname == item.name
|
||||||
|
):
|
||||||
|
result[packed_item.name] = frappe._dict({"qty": packed_item.requested_qty})
|
||||||
|
else:
|
||||||
|
result[item.name] = frappe._dict({"qty": item.requested_qty})
|
||||||
|
>>>>>>> 953f089c06 (feat: Adding requested qty in packed item (#53486))
|
||||||
|
|
||||||
return result
|
return result
|
||||||
|
|
||||||
@@ -863,12 +879,32 @@ def make_material_request(source_name, target_doc=None):
|
|||||||
flt(so_item.qty)
|
flt(so_item.qty)
|
||||||
- flt(requested_item_qty.get(so_item.name, {}).get("qty"))
|
- flt(requested_item_qty.get(so_item.name, {}).get("qty"))
|
||||||
- max(
|
- max(
|
||||||
flt(so_item.get("delivered_qty"))
|
flt(so_item.get("delivered_qty")),
|
||||||
- flt(requested_item_qty.get(so_item.name, {}).get("received_qty")),
|
|
||||||
0,
|
0,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
<<<<<<< HEAD
|
||||||
|
=======
|
||||||
|
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),
|
||||||
|
0,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
>>>>>>> 953f089c06 (feat: Adding requested qty in packed item (#53486))
|
||||||
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
|
||||||
@@ -923,8 +959,10 @@ def make_material_request(source_name, target_doc=None):
|
|||||||
target_doc,
|
target_doc,
|
||||||
postprocess,
|
postprocess,
|
||||||
)
|
)
|
||||||
|
if doc and doc.items:
|
||||||
return doc
|
return doc
|
||||||
|
else:
|
||||||
|
frappe.throw(_("Material Request already created for the ordered quantity"))
|
||||||
|
|
||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
|
|||||||
@@ -57,6 +57,32 @@ class TestSalesOrder(AccountsTestMixin, FrappeTestCase):
|
|||||||
def tearDown(self):
|
def tearDown(self):
|
||||||
frappe.set_user("Administrator")
|
frappe.set_user("Administrator")
|
||||||
|
|
||||||
|
def test_sales_order_with_product_bundle_for_partial_material_request(self):
|
||||||
|
product_bundle = make_product_bundle(
|
||||||
|
"_Test Product Bundle Item", ["_Test Item", "_Test Item Home Desktop 100"]
|
||||||
|
)
|
||||||
|
so = make_sales_order(item_code=product_bundle.name, qty=2)
|
||||||
|
mr = make_material_request(so.name)
|
||||||
|
mr.items[0].qty = 4
|
||||||
|
mr.items[1].qty = 2
|
||||||
|
mr.items[0].schedule_date = today()
|
||||||
|
mr.items[1].schedule_date = today()
|
||||||
|
mr.save()
|
||||||
|
mr.submit()
|
||||||
|
mr.reload()
|
||||||
|
self.assertEqual(mr.items[0].qty, 4)
|
||||||
|
mr = make_material_request(so.name)
|
||||||
|
self.assertEqual(mr.items[0].qty, 6)
|
||||||
|
|
||||||
|
def test_sales_order_with_full_material_request(self):
|
||||||
|
so = make_sales_order(item_code="_Test Item", qty=5, do_not_submit=True)
|
||||||
|
so.submit()
|
||||||
|
mr = make_material_request(so.name)
|
||||||
|
mr.save()
|
||||||
|
mr.submit()
|
||||||
|
mr.reload()
|
||||||
|
self.assertRaises(frappe.ValidationError, make_material_request, so.name)
|
||||||
|
|
||||||
def test_sales_order_skip_delivery_note(self):
|
def test_sales_order_skip_delivery_note(self):
|
||||||
so = make_sales_order(do_not_submit=True)
|
so = make_sales_order(do_not_submit=True)
|
||||||
so.order_type = "Maintenance"
|
so.order_type = "Maintenance"
|
||||||
|
|||||||
@@ -87,7 +87,16 @@ class MaterialRequest(BuyingController):
|
|||||||
"join_field": "sales_order_item",
|
"join_field": "sales_order_item",
|
||||||
"target_ref_field": "stock_qty",
|
"target_ref_field": "stock_qty",
|
||||||
"source_field": "stock_qty",
|
"source_field": "stock_qty",
|
||||||
}
|
},
|
||||||
|
{
|
||||||
|
"source_dt": "Material Request Item",
|
||||||
|
"target_dt": "Packed Item",
|
||||||
|
"target_field": "requested_qty",
|
||||||
|
"target_parent_dt": "Sales Order",
|
||||||
|
"join_field": "packed_item",
|
||||||
|
"target_ref_field": "qty",
|
||||||
|
"source_field": "qty",
|
||||||
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
def check_if_already_pulled(self):
|
def check_if_already_pulled(self):
|
||||||
|
|||||||
@@ -34,6 +34,7 @@
|
|||||||
"projected_qty",
|
"projected_qty",
|
||||||
"ordered_qty",
|
"ordered_qty",
|
||||||
"packed_qty",
|
"packed_qty",
|
||||||
|
"requested_qty",
|
||||||
"column_break_16",
|
"column_break_16",
|
||||||
"incoming_rate",
|
"incoming_rate",
|
||||||
"picked_qty",
|
"picked_qty",
|
||||||
@@ -298,13 +299,22 @@
|
|||||||
"fieldtype": "Check",
|
"fieldtype": "Check",
|
||||||
"label": "Supplier delivers to Customer",
|
"label": "Supplier delivers to Customer",
|
||||||
"read_only": 1
|
"read_only": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"default": "0",
|
||||||
|
"fieldname": "requested_qty",
|
||||||
|
"fieldtype": "Float",
|
||||||
|
"label": "Requested Qty",
|
||||||
|
"non_negative": 1,
|
||||||
|
"print_hide": 1,
|
||||||
|
"read_only": 1
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"idx": 1,
|
"idx": 1,
|
||||||
"index_web_pages_for_search": 1,
|
"index_web_pages_for_search": 1,
|
||||||
"istable": 1,
|
"istable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2025-07-09 19:12:45.850219",
|
"modified": "2026-03-16 18:10:47.511381",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Stock",
|
"module": "Stock",
|
||||||
"name": "Packed Item",
|
"name": "Packed Item",
|
||||||
|
|||||||
@@ -45,6 +45,7 @@ class PackedItem(Document):
|
|||||||
projected_qty: DF.Float
|
projected_qty: DF.Float
|
||||||
qty: DF.Float
|
qty: DF.Float
|
||||||
rate: DF.Currency
|
rate: DF.Currency
|
||||||
|
requested_qty: DF.Float
|
||||||
serial_and_batch_bundle: DF.Link | None
|
serial_and_batch_bundle: DF.Link | None
|
||||||
serial_no: DF.Text | None
|
serial_no: DF.Text | None
|
||||||
target_warehouse: DF.Link | None
|
target_warehouse: DF.Link | None
|
||||||
|
|||||||
Reference in New Issue
Block a user