From 09bcadfbb9c992fe60c31b6c0cae095197a2fc2f Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Tue, 23 Dec 2025 12:46:14 +0530 Subject: [PATCH] fix: scrap items in job card --- erpnext/manufacturing/doctype/bom/bom.py | 4 +++ .../doctype/job_card/job_card.py | 29 +++++++++++++++++-- .../doctype/work_order/work_order.py | 2 ++ .../stock/doctype/stock_entry/stock_entry.py | 15 ++++++++-- 4 files changed, 46 insertions(+), 4 deletions(-) diff --git a/erpnext/manufacturing/doctype/bom/bom.py b/erpnext/manufacturing/doctype/bom/bom.py index 56b415e534d..8af92b8d02e 100644 --- a/erpnext/manufacturing/doctype/bom/bom.py +++ b/erpnext/manufacturing/doctype/bom/bom.py @@ -1280,6 +1280,10 @@ def get_bom_items_as_dict( fetch_exploded = 0 group_by_cond = "group by item_code, operation_row_id, stock_uom" + if fetch_scrap_items: + fetch_exploded = 0 + group_by_cond = "group by item_code" + # Did not use qty_consumed_per_unit in the query, as it leads to rounding loss query = """select bom_item.item_code, diff --git a/erpnext/manufacturing/doctype/job_card/job_card.py b/erpnext/manufacturing/doctype/job_card/job_card.py index e15f33de6e9..d086da7051c 100644 --- a/erpnext/manufacturing/doctype/job_card/job_card.py +++ b/erpnext/manufacturing/doctype/job_card/job_card.py @@ -23,7 +23,7 @@ from frappe.utils import ( time_diff_in_hours, ) -from erpnext.manufacturing.doctype.bom.bom import add_additional_cost +from erpnext.manufacturing.doctype.bom.bom import add_additional_cost, get_bom_items_as_dict from erpnext.manufacturing.doctype.manufacturing_settings.manufacturing_settings import ( get_mins_between_operations, ) @@ -240,6 +240,26 @@ class JobCard(Document): row.sub_operation = row.operation self.append("sub_operations", row) + def set_scrap_items(self): + if not self.semi_fg_bom: + return + + items_dict = get_bom_items_as_dict( + self.semi_fg_bom, self.company, qty=self.for_quantity, fetch_exploded=0, fetch_scrap_items=1 + ) + for item_code, values in items_dict.items(): + values = frappe._dict(values) + + self.append( + "scrap_items", + { + "item_code": item_code, + "stock_qty": values.qty, + "item_name": values.item_name, + "stock_uom": values.stock_uom, + }, + ) + def validate_time_logs(self, save=False): self.total_time_in_mins = 0.0 self.total_completed_qty = 0.0 @@ -1361,10 +1381,15 @@ class JobCard(Document): wo_doc = frappe.get_doc("Work Order", self.work_order) add_additional_cost(ste.stock_entry, wo_doc, self) - ste.stock_entry.save() + ste.stock_entry.set_scrap_items() + for row in ste.stock_entry.items: + if row.is_scrap_item and not row.t_warehouse: + row.t_warehouse = self.target_warehouse if auto_submit: ste.stock_entry.submit() + else: + ste.stock_entry.save() frappe.msgprint( _("Stock Entry {0} has created").format(get_link_to_form("Stock Entry", ste.stock_entry.name)) diff --git a/erpnext/manufacturing/doctype/work_order/work_order.py b/erpnext/manufacturing/doctype/work_order/work_order.py index f331ada8e49..a602fba4dbe 100644 --- a/erpnext/manufacturing/doctype/work_order/work_order.py +++ b/erpnext/manufacturing/doctype/work_order/work_order.py @@ -2565,6 +2565,8 @@ def create_job_card(work_order, row, enable_capacity_planning=False, auto_create work_order.transfer_material_against == "Job Card" and not work_order.skip_transfer ): doc.get_required_items() + if work_order.track_semi_finished_goods: + doc.set_scrap_items() if auto_create: doc.flags.ignore_mandatory = True diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.py b/erpnext/stock/doctype/stock_entry/stock_entry.py index 8a4430fd169..f11dcec52b4 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.py +++ b/erpnext/stock/doctype/stock_entry/stock_entry.py @@ -2716,6 +2716,9 @@ class StockEntry(StockController, SubcontractingInwardController): return item_dict def get_scrap_items_from_job_card(self): + if not hasattr(self, "pro_doc"): + self.pro_doc = None + if not self.pro_doc: self.set_work_order_details() @@ -2742,9 +2745,17 @@ class StockEntry(StockController, SubcontractingInwardController): & (job_card.docstatus == 1) ) .groupby(job_card_scrap_item.item_code) - ).run(as_dict=1) + ) - pending_qty = flt(self.get_completed_job_card_qty()) - flt(self.pro_doc.produced_qty) + if self.job_card: + scrap_items = scrap_items.where(job_card.name == self.job_card) + + scrap_items = scrap_items.run(as_dict=1) + + if self.job_card: + pending_qty = flt(self.fg_completed_qty) + else: + pending_qty = flt(self.get_completed_job_card_qty()) - flt(self.pro_doc.produced_qty) used_scrap_items = self.get_used_scrap_items() for row in scrap_items: