diff --git a/erpnext/manufacturing/doctype/production_plan/production_plan.py b/erpnext/manufacturing/doctype/production_plan/production_plan.py index 11e048dffd0..c048ab40416 100644 --- a/erpnext/manufacturing/doctype/production_plan/production_plan.py +++ b/erpnext/manufacturing/doctype/production_plan/production_plan.py @@ -789,23 +789,10 @@ class ProductionPlan(Document): items_to_remove = defaultdict(list) for supplier, items in subcontracted_po.items(): for item in items: - table = frappe.qb.DocType("Purchase Order Item") - total_received_qty = ( - frappe.qb.from_(table) - .select( - Sum(table.received_qty / (table.received_qty / table.fg_item_qty)).as_( - "total_received_qty" - ) - ) - .where( - (table.production_plan_sub_assembly_item == item.name) & (table.docstatus == 1) - ) - ).run(as_dict=True)[0].total_received_qty or 0 - - if item.qty == total_received_qty: + if item.qty == item.received_qty: items_to_remove[supplier].append(item) - elif total_received_qty: - item.qty -= total_received_qty + elif item.received_qty: + item.qty -= item.received_qty subcontracted_po[supplier] = [item for item in items if item not in items_to_remove[supplier]] diff --git a/erpnext/manufacturing/doctype/production_plan_sub_assembly_item/production_plan_sub_assembly_item.json b/erpnext/manufacturing/doctype/production_plan_sub_assembly_item/production_plan_sub_assembly_item.json index 14b82403190..1f6824e8744 100644 --- a/erpnext/manufacturing/doctype/production_plan_sub_assembly_item/production_plan_sub_assembly_item.json +++ b/erpnext/manufacturing/doctype/production_plan_sub_assembly_item/production_plan_sub_assembly_item.json @@ -21,7 +21,6 @@ "purchase_order", "production_plan_item", "column_break_7", - "ordered_qty", "received_qty", "indent", "section_break_19", @@ -72,6 +71,7 @@ "read_only": 1 }, { + "allow_on_submit": 1, "fieldname": "received_qty", "fieldtype": "Float", "label": "Received Qty", @@ -205,20 +205,12 @@ "fieldtype": "Float", "label": "Produced Qty", "read_only": 1 - }, - { - "fieldname": "ordered_qty", - "fieldtype": "Float", - "hidden": 1, - "label": "Ordered Qty", - "non_negative": 1, - "read_only": 1 } ], "index_web_pages_for_search": 1, "istable": 1, "links": [], - "modified": "2024-12-20 17:00:15.335880", + "modified": "2025-01-01 14:27:52.956484", "modified_by": "Administrator", "module": "Manufacturing", "name": "Production Plan Sub Assembly Item", diff --git a/erpnext/manufacturing/doctype/production_plan_sub_assembly_item/production_plan_sub_assembly_item.py b/erpnext/manufacturing/doctype/production_plan_sub_assembly_item/production_plan_sub_assembly_item.py index 7e29675136c..ad1d655de8b 100644 --- a/erpnext/manufacturing/doctype/production_plan_sub_assembly_item/production_plan_sub_assembly_item.py +++ b/erpnext/manufacturing/doctype/production_plan_sub_assembly_item/production_plan_sub_assembly_item.py @@ -22,7 +22,6 @@ class ProductionPlanSubAssemblyItem(Document): fg_warehouse: DF.Link | None indent: DF.Int item_name: DF.Data | None - ordered_qty: DF.Float parent: DF.Data parent_item_code: DF.Link | None parentfield: DF.Data diff --git a/erpnext/manufacturing/report/production_plan_summary/production_plan_summary.py b/erpnext/manufacturing/report/production_plan_summary/production_plan_summary.py index 226677173b0..b6dc9397717 100644 --- a/erpnext/manufacturing/report/production_plan_summary/production_plan_summary.py +++ b/erpnext/manufacturing/report/production_plan_summary/production_plan_summary.py @@ -116,7 +116,7 @@ def get_production_plan_sub_assembly_item_details(filters, row, production_plan_ "pending_qty": flt(item.qty) - flt(order_details.get((docname, item.production_item), {}).get("produced_qty", 0)), } - if data[-1] and data[-1]["indent"] == data_to_append["indent"]: + if data[-1] and data[-1]["item_code"] == item.production_item: data_to_append["pending_qty"] = data[-1]["pending_qty"] - data_to_append["produced_qty"] data.append(data_to_append) @@ -124,7 +124,7 @@ def get_production_plan_sub_assembly_item_details(filters, row, production_plan_ def get_work_order_details(filters, order_details): for row in frappe.get_all( "Work Order", - filters={"production_plan": filters.get("production_plan")}, + filters={"production_plan": filters.get("production_plan"), "docstatus": 1}, fields=["name", "produced_qty", "production_plan", "production_item", "sales_order"], ): order_details.setdefault((row.name, row.production_item), row) @@ -133,11 +133,11 @@ def get_work_order_details(filters, order_details): def get_purchase_order_details(filters, order_details): for row in frappe.get_all( "Purchase Order Item", - filters={"production_plan": filters.get("production_plan")}, - fields=["parent", "received_qty as produced_qty", "item_code", "fg_item", "fg_item_qty"], + filters={"production_plan": filters.get("production_plan"), "docstatus": 1}, + fields=["parent", "qty", "received_qty as produced_qty", "item_code", "fg_item", "fg_item_qty"], ): if row.fg_item: - row.produced_qty /= row.produced_qty / row.fg_item_qty or 1 + row.produced_qty /= row.qty / row.fg_item_qty or 1 order_details.setdefault((row.parent, row.fg_item or row.item_code), row) diff --git a/erpnext/stock/doctype/material_request/material_request.py b/erpnext/stock/doctype/material_request/material_request.py index 84c36244b1e..18dabc4cafa 100644 --- a/erpnext/stock/doctype/material_request/material_request.py +++ b/erpnext/stock/doctype/material_request/material_request.py @@ -162,18 +162,23 @@ class MaterialRequest(BuyingController): self.validate_pp_qty() def validate_pp_qty(self): - for item in self.items: - if item.material_request_plan_item: - qty_from_pp = frappe.db.get_value( - "Material Request Plan Item", - item.material_request_plan_item, - ["quantity", "requested_qty"], - as_dict=1, - ) - if item.qty > (qty_from_pp.quantity - qty_from_pp.requested_qty): + items_from_pp = [item for item in self.items if item.material_request_plan_item] + if items_from_pp: + items_mr_plan_items = [item.material_request_plan_item for item in items_from_pp] + table = frappe.qb.DocType("Material Request Plan Item") + query = ( + frappe.qb.from_(table) + .select(table.name, (table.quantity - table.requested_qty).as_("available_qty")) + .where(table.name.isin(items_mr_plan_items)) + ) + result = query.run(as_dict=True) + + for item in items_from_pp: + row = next(r for r in result if r.name == item.material_request_plan_item) + if item.qty > row.available_qty: frappe.throw( _("Quantity cannot be greater than {0} for Item {1}").format( - qty_from_pp.quantity - qty_from_pp.requested_qty, item.item_code + row.available_qty, item.item_code ) ) diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py index 6425e8e2dd9..9a540f9291d 100644 --- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py +++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py @@ -383,6 +383,42 @@ class PurchaseReceipt(BuyingController): self.repost_future_sle_and_gle() self.set_consumed_qty_in_subcontract_order() self.reserve_stock_for_sales_order() + self.update_received_qty_if_from_pp() + + def update_received_qty_if_from_pp(self, cancel=False): + from frappe.query_builder.functions import Sum + + items_with_po_item = [item for item in self.items if item.purchase_order_item] + if items_with_po_item: + po_items = [item.purchase_order_item for item in items_with_po_item] + table = frappe.qb.DocType("Purchase Order Item") + query = ( + frappe.qb.from_(table) + .select( + table.name, + (table.qty / table.fg_item_qty).as_("sc_conversion_factor"), + table.production_plan_sub_assembly_item, + Sum(table.received_qty).as_("received_qty"), + ) + .where(table.name.isin(po_items)) + .groupby(table.name) + ) + result = query.run(as_dict=True) + + for item in items_with_po_item: + row = next(d for d in result if d.name == item.purchase_order_item) + if row.production_plan_sub_assembly_item: + received_qty = ( + (row.received_qty + (item.qty / row.sc_conversion_factor)) + if not cancel + else (row.received_qty - (item.qty / row.sc_conversion_factor)) + ) + frappe.set_value( + "Production Plan Sub Assembly Item", + row.production_plan_sub_assembly_item, + "received_qty", + received_qty, + ) def check_next_docstatus(self): submit_rv = frappe.db.sql( @@ -424,6 +460,7 @@ class PurchaseReceipt(BuyingController): ) self.delete_auto_created_batches() self.set_consumed_qty_in_subcontract_order() + self.update_received_qty_if_from_pp(cancel=True) def get_gl_entries(self, warehouse_account=None, via_landed_cost_voucher=False): from erpnext.accounts.general_ledger import process_gl_map