From c807a7be7b8e812d5a812b7fac551927ee8889e6 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Thu, 4 Dec 2025 13:52:56 +0530 Subject: [PATCH] fix: backflush based on for job card --- .../doctype/job_card/job_card.py | 30 +++++++++++++++++++ .../doctype/job_card_item/job_card_item.json | 13 ++++++-- .../doctype/job_card_item/job_card_item.py | 1 + .../stock/doctype/stock_entry/stock_entry.js | 5 ++++ .../stock/doctype/stock_entry/stock_entry.py | 1 + .../stock_entry_type/stock_entry_type.py | 26 ++++++++++------ 6 files changed, 65 insertions(+), 11 deletions(-) diff --git a/erpnext/manufacturing/doctype/job_card/job_card.py b/erpnext/manufacturing/doctype/job_card/job_card.py index 31a44817e44..6ea80cf273a 100644 --- a/erpnext/manufacturing/doctype/job_card/job_card.py +++ b/erpnext/manufacturing/doctype/job_card/job_card.py @@ -940,6 +940,36 @@ class JobCard(Document): }, ) + def set_consumed_qty_in_job_card_item(self, ste_doc): + jc_item_names = [row.job_card_item for row in ste_doc.get("items") if row.get("job_card_item")] + + if not jc_item_names: + return + + se = frappe.qb.DocType("Stock Entry") + sed = frappe.qb.DocType("Stock Entry Detail") + + query = ( + frappe.qb.from_(sed) + .join(se) + .on(sed.parent == se.name) + .select(sed.job_card_item, Sum(sed.qty)) + .where( + (sed.job_card_item.isin(jc_item_names)) & (se.docstatus == 1) & (se.purpose == "Manufacture") + ) + .groupby(sed.job_card_item) + ) + + itemwise_consumed_qty = frappe._dict(query.run(as_list=True)) + + for row in ste_doc.items: + if not row.get("job_card_item"): + continue + + consumed_qty = flt(itemwise_consumed_qty.get(row.job_card_item, 0.0)) + + frappe.db.set_value("Job Card Item", row.job_card_item, "consumed_qty", consumed_qty) + def set_transferred_qty_in_job_card_item(self, ste_doc): def _get_job_card_items_transferred_qty(ste_doc): from frappe.query_builder.functions import Sum diff --git a/erpnext/manufacturing/doctype/job_card_item/job_card_item.json b/erpnext/manufacturing/doctype/job_card_item/job_card_item.json index 8a1bd8e898c..69a7428f218 100644 --- a/erpnext/manufacturing/doctype/job_card_item/job_card_item.json +++ b/erpnext/manufacturing/doctype/job_card_item/job_card_item.json @@ -17,6 +17,7 @@ "required_qty", "column_break_9", "transferred_qty", + "consumed_qty", "allow_alternative_item" ], "fields": [ @@ -100,20 +101,28 @@ "no_copy": 1, "print_hide": 1, "read_only": 1 + }, + { + "fieldname": "consumed_qty", + "fieldtype": "Float", + "label": "Consumed Qty", + "no_copy": 1, + "read_only": 1 } ], "index_web_pages_for_search": 1, "istable": 1, "links": [], - "modified": "2024-03-27 13:09:56.943741", + "modified": "2025-12-04 14:30:19.472294", "modified_by": "Administrator", "module": "Manufacturing", "name": "Job Card Item", "owner": "Administrator", "permissions": [], "quick_entry": 1, + "row_format": "Dynamic", "sort_field": "creation", "sort_order": "DESC", "states": [], "track_changes": 1 -} \ No newline at end of file +} diff --git a/erpnext/manufacturing/doctype/job_card_item/job_card_item.py b/erpnext/manufacturing/doctype/job_card_item/job_card_item.py index ecf082392a3..b997e2e935f 100644 --- a/erpnext/manufacturing/doctype/job_card_item/job_card_item.py +++ b/erpnext/manufacturing/doctype/job_card_item/job_card_item.py @@ -15,6 +15,7 @@ class JobCardItem(Document): from frappe.types import DF allow_alternative_item: DF.Check + consumed_qty: DF.Float description: DF.Text | None item_code: DF.Link | None item_group: DF.Link | None diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.js b/erpnext/stock/doctype/stock_entry/stock_entry.js index 5ec3b2215fa..10cdde6e37a 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.js +++ b/erpnext/stock/doctype/stock_entry/stock_entry.js @@ -162,6 +162,11 @@ frappe.ui.form.on("Stock Entry", { }; }); } + + if (frm.doc.job_card && frm.doc.purpose === "Manufacture") { + frm.set_df_property("fg_completed_qty", "read_only", 1); + frm.set_df_property("get_items", "hidden", 1); + } }, setup_quality_inspection: function (frm) { diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.py b/erpnext/stock/doctype/stock_entry/stock_entry.py index 67016b81f3e..b4d8e8cdd80 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.py +++ b/erpnext/stock/doctype/stock_entry/stock_entry.py @@ -1920,6 +1920,7 @@ class StockEntry(StockController, SubcontractingInwardController): job_doc.set_transferred_qty(update_status=True) job_doc.set_transferred_qty_in_job_card_item(self) else: + job_doc.set_consumed_qty_in_job_card_item(self) job_doc.set_manufactured_qty() if self.work_order: diff --git a/erpnext/stock/doctype/stock_entry_type/stock_entry_type.py b/erpnext/stock/doctype/stock_entry_type/stock_entry_type.py index 1e05b680fb1..29c0cd7e05c 100644 --- a/erpnext/stock/doctype/stock_entry_type/stock_entry_type.py +++ b/erpnext/stock/doctype/stock_entry_type/stock_entry_type.py @@ -3,6 +3,7 @@ import frappe +from frappe import _ from frappe.model.document import Document from frappe.utils import flt @@ -99,22 +100,26 @@ class ManufactureEntry: def add_raw_materials(self): if self.job_card: item_dict = {} - # if self.bom_no: - # item_dict = get_bom_items_as_dict( - # self.bom_no, - # self.company, - # qty=self.qty_to_manufacture, - # fetch_exploded=False, - # fetch_qty_in_stock_uom=False, - # ) - if not item_dict: item_dict = self.get_items_from_job_card() + backflush_based_on = frappe.db.get_single_value( + "Manufacturing Settings", "backflush_raw_materials_based_on" + ) + for item_code, _dict in item_dict.items(): _dict.from_warehouse = self.source_wh.get(item_code) or self.wip_warehouse _dict.to_warehouse = "" + if backflush_based_on != "BOM": + calculated_qty = flt(_dict.transferred_qty) - flt(_dict.consumed_qty) + if calculated_qty < 0: + frappe.throw( + _("Consumed quantity of item {0} exceeds transferred quantity.").format(item_code) + ) + + _dict.qty = calculated_qty + self.stock_entry.add_to_stock_entry_detail(item_dict) def get_items_from_job_card(self): @@ -122,9 +127,12 @@ class ManufactureEntry: items = frappe.get_all( "Job Card Item", fields=[ + "name as job_card_item", "item_code", "source_warehouse", "required_qty as qty", + "transferred_qty", + "consumed_qty", "item_name", "uom", "stock_uom",