From 3c03c94f1a3733883e9253f0ed2a083f7a13f5bc Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Mon, 29 Sep 2025 15:20:01 +0530 Subject: [PATCH] fix: additional material transfer --- .../doctype/work_order/work_order.js | 25 +++++++++--- .../doctype/work_order/work_order.json | 23 ++++++++--- .../doctype/work_order/work_order.py | 39 +++++++++++++++++-- .../doctype/stock_entry/stock_entry.json | 12 +++++- .../stock/doctype/stock_entry/stock_entry.py | 1 + 5 files changed, 86 insertions(+), 14 deletions(-) diff --git a/erpnext/manufacturing/doctype/work_order/work_order.js b/erpnext/manufacturing/doctype/work_order/work_order.js index 2add6488d33..aa4fa8b0d0b 100644 --- a/erpnext/manufacturing/doctype/work_order/work_order.js +++ b/erpnext/manufacturing/doctype/work_order/work_order.js @@ -756,12 +756,26 @@ erpnext.work_order = { }); start_btn.addClass("btn-primary"); } else if (transfer_extra_materials && allowed_qty) { - let qty = allowed_qty - flt(frm.doc.material_transferred_for_manufacturing); + let qty = + allowed_qty - + flt( + flt(frm.doc.material_transferred_for_manufacturing) + + flt(frm.doc.additional_transferred_qty) + ); if (qty > 0) { - frm.add_custom_button(__("Transfer Extra Material"), function () { - erpnext.work_order.make_se(frm, "Material Transfer for Manufacture", qty); - }); + frm.add_custom_button( + __("Additional Transfer"), + function () { + erpnext.work_order.make_se( + frm, + "Material Transfer for Manufacture", + qty, + 1 + ); + }, + __("Make") + ); } } } @@ -988,13 +1002,14 @@ erpnext.work_order = { }); }, - make_se: function (frm, purpose, qty) { + make_se: function (frm, purpose, qty, is_additional_transfer_entry) { if (qty) { frappe .xcall("erpnext.manufacturing.doctype.work_order.work_order.make_stock_entry", { work_order_id: frm.doc.name, purpose: purpose, qty: qty, + is_additional_transfer_entry: is_additional_transfer_entry || 0, }) .then((stock_entry) => { frappe.model.sync(stock_entry); diff --git a/erpnext/manufacturing/doctype/work_order/work_order.json b/erpnext/manufacturing/doctype/work_order/work_order.json index d1aa9278889..7c019cbdc05 100644 --- a/erpnext/manufacturing/doctype/work_order/work_order.json +++ b/erpnext/manufacturing/doctype/work_order/work_order.json @@ -19,13 +19,15 @@ "column_break1", "company", "qty", - "material_transferred_for_manufacturing", - "produced_qty", - "disassembled_qty", - "process_loss_qty", "project", "track_semi_finished_goods", "reserve_stock", + "column_break_agjv", + "material_transferred_for_manufacturing", + "additional_transferred_qty", + "produced_qty", + "disassembled_qty", + "process_loss_qty", "warehouses", "source_warehouse", "wip_warehouse", @@ -609,6 +611,17 @@ "label": "MPS", "options": "Master Production Schedule", "read_only": 1 + }, + { + "fieldname": "column_break_agjv", + "fieldtype": "Column Break" + }, + { + "fieldname": "additional_transferred_qty", + "fieldtype": "Float", + "label": "Additional Transferred Qty", + "no_copy": 1, + "read_only": 1 } ], "grid_page_length": 50, @@ -617,7 +630,7 @@ "image_field": "image", "is_submittable": 1, "links": [], - "modified": "2025-08-28 11:01:48.719824", + "modified": "2025-09-29 15:57:47.022616", "modified_by": "Administrator", "module": "Manufacturing", "name": "Work Order", diff --git a/erpnext/manufacturing/doctype/work_order/work_order.py b/erpnext/manufacturing/doctype/work_order/work_order.py index 5aac8b2775c..f6674602ded 100644 --- a/erpnext/manufacturing/doctype/work_order/work_order.py +++ b/erpnext/manufacturing/doctype/work_order/work_order.py @@ -82,6 +82,7 @@ class WorkOrder(Document): actual_operating_cost: DF.Currency actual_start_date: DF.Datetime | None additional_operating_cost: DF.Currency + additional_transferred_qty: DF.Float allow_alternative_item: DF.Check amended_from: DF.Link | None batch_size: DF.Float @@ -481,6 +482,7 @@ class WorkOrder(Document): for purpose, fieldname in ( ("Manufacture", "produced_qty"), ("Material Transfer for Manufacture", "material_transferred_for_manufacturing"), + ("Material Transfer for Manufacture", "additional_transferred_qty"), ): if ( purpose == "Material Transfer for Manufacture" @@ -489,7 +491,7 @@ class WorkOrder(Document): ): continue - qty = self.get_transferred_or_manufactured_qty(purpose) + qty = self.get_transferred_or_manufactured_qty(purpose, fieldname) if not allowance_percentage and purpose == "Material Transfer for Manufacture": allowance_percentage = flt( @@ -519,6 +521,30 @@ class WorkOrder(Document): self.set_produced_qty_for_sub_assembly_item() self.update_production_plan_status() + if self.additional_transferred_qty: + self.validate_additional_transferred_qty() + + def validate_additional_transferred_qty(self): + transfer_extra_materials_percentage = frappe.db.get_single_value( + "Manufacturing Settings", "transfer_extra_materials_percentage" + ) + + allowed_qty = flt(self.qty) + flt(flt(self.qty) * flt(transfer_extra_materials_percentage) / 100) + + actual_qty = flt(self.material_transferred_for_manufacturing) + flt(self.additional_transferred_qty) + + precision = frappe.get_precision("Work Order", "qty") + if flt(allowed_qty - actual_qty, precision) < 0: + frappe.throw( + _( + """Additional Transferred Qty {0} + cannot be greater than {1}. + To fix this, increase the percentage value + of the field 'Transfer Extra Raw Materials to WIP' + in Manufacturing Settings.""" + ).format(actual_qty, allowed_qty), + ) + def update_disassembled_qty(self, qty, is_cancel=False): if is_cancel: self.disassembled_qty = max(0, self.disassembled_qty - qty) @@ -531,7 +557,7 @@ class WorkOrder(Document): self.db_set("disassembled_qty", self.disassembled_qty) - def get_transferred_or_manufactured_qty(self, purpose): + def get_transferred_or_manufactured_qty(self, purpose, fieldname): table = frappe.qb.DocType("Stock Entry") query = frappe.qb.from_(table).where( (table.work_order == self.name) & (table.docstatus == 1) & (table.purpose == purpose) @@ -542,6 +568,10 @@ class WorkOrder(Document): else: query = query.select(Sum(table.fg_completed_qty)) + query = query.where( + table.is_additional_transfer_entry == cint(fieldname == "additional_transferred_qty") + ) + return flt(query.run()[0][0]) def set_process_loss_qty(self): @@ -1991,7 +2021,9 @@ def set_work_order_ops(name): @frappe.whitelist() -def make_stock_entry(work_order_id, purpose, qty=None, target_warehouse=None): +def make_stock_entry( + work_order_id, purpose, qty=None, target_warehouse=None, is_additional_transfer_entry=False +): work_order = frappe.get_doc("Work Order", work_order_id) if not frappe.db.get_value("Warehouse", work_order.wip_warehouse, "is_group"): wip_warehouse = work_order.wip_warehouse @@ -2030,6 +2062,7 @@ def make_stock_entry(work_order_id, purpose, qty=None, target_warehouse=None): stock_entry.to_warehouse = target_warehouse or work_order.source_warehouse stock_entry.set_stock_entry_type() + stock_entry.is_additional_transfer_entry = is_additional_transfer_entry stock_entry.get_items(qty, work_order.production_item) if purpose != "Disassemble": diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.json b/erpnext/stock/doctype/stock_entry/stock_entry.json index 3952af82c34..c2c17416c3e 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.json +++ b/erpnext/stock/doctype/stock_entry/stock_entry.json @@ -30,6 +30,7 @@ "set_posting_time", "inspection_required", "apply_putaway_rule", + "is_additional_transfer_entry", "bom_info_section", "from_bom", "use_multi_level_bom", @@ -699,6 +700,15 @@ "fieldtype": "Data", "is_virtual": 1, "label": "Last Scanned Warehouse" + }, + { + "default": "0", + "depends_on": "eval:doc.purpose == \"Material Transfer for Manufacture\"", + "fieldname": "is_additional_transfer_entry", + "fieldtype": "Check", + "hidden": 1, + "label": "Is Additional Transfer Entry", + "read_only": 1 } ], "icon": "fa fa-file-text", @@ -706,7 +716,7 @@ "index_web_pages_for_search": 1, "is_submittable": 1, "links": [], - "modified": "2025-08-04 19:21:03.338958", + "modified": "2025-09-29 15:56:21.344296", "modified_by": "Administrator", "module": "Stock", "name": "Stock Entry", diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.py b/erpnext/stock/doctype/stock_entry/stock_entry.py index 09233013cc6..297ca8e331d 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.py +++ b/erpnext/stock/doctype/stock_entry/stock_entry.py @@ -110,6 +110,7 @@ class StockEntry(StockController): from_bom: DF.Check from_warehouse: DF.Link | None inspection_required: DF.Check + is_additional_transfer_entry: DF.Check is_opening: DF.Literal["No", "Yes"] is_return: DF.Check items: DF.Table[StockEntryDetail]