From 3e4d16062619b5934bff0d697ac59cf1beb3eead Mon Sep 17 00:00:00 2001 From: iamkhanraheel Date: Sat, 21 Jun 2025 01:14:26 +0530 Subject: [PATCH] fix: disassemble qty calculation & max calculation to be allowed to create it --- .../doctype/work_order/work_order.js | 2 +- .../doctype/work_order/work_order.json | 11 +++++- .../doctype/work_order/work_order.py | 15 +++++++- .../stock/doctype/stock_entry/stock_entry.py | 34 +++++++++++++++---- 4 files changed, 52 insertions(+), 10 deletions(-) diff --git a/erpnext/manufacturing/doctype/work_order/work_order.js b/erpnext/manufacturing/doctype/work_order/work_order.js index 609670bd3f1..ef959027701 100644 --- a/erpnext/manufacturing/doctype/work_order/work_order.js +++ b/erpnext/manufacturing/doctype/work_order/work_order.js @@ -877,7 +877,7 @@ erpnext.work_order = { get_max_transferable_qty: (frm, purpose) => { let max = 0; if (purpose === "Disassemble") { - return flt(frm.doc.produced_qty); + return flt(frm.doc.produced_qty - frm.doc.disassembled_qty); } if (frm.doc.skip_transfer) { diff --git a/erpnext/manufacturing/doctype/work_order/work_order.json b/erpnext/manufacturing/doctype/work_order/work_order.json index 45184af641d..7927973289c 100644 --- a/erpnext/manufacturing/doctype/work_order/work_order.json +++ b/erpnext/manufacturing/doctype/work_order/work_order.json @@ -20,6 +20,7 @@ "qty", "material_transferred_for_manufacturing", "produced_qty", + "disassembled_qty", "process_loss_qty", "project", "track_semi_finished_goods", @@ -592,6 +593,14 @@ "fieldname": "reserve_stock", "fieldtype": "Check", "label": " Reserve Stock" + }, + { + "depends_on": "eval:doc.docstatus==1", + "fieldname": "disassembled_qty", + "fieldtype": "Float", + "label": "Disassembled Qty", + "no_copy": 1, + "read_only": 1 } ], "grid_page_length": 50, @@ -600,7 +609,7 @@ "image_field": "image", "is_submittable": 1, "links": [], - "modified": "2025-04-25 11:46:38.739588", + "modified": "2025-06-21 00:55:45.916224", "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 b40dc3792c0..2f8c07f6238 100644 --- a/erpnext/manufacturing/doctype/work_order/work_order.py +++ b/erpnext/manufacturing/doctype/work_order/work_order.py @@ -89,6 +89,7 @@ class WorkOrder(Document): company: DF.Link corrective_operation_cost: DF.Currency description: DF.SmallText | None + disassembled_qty: DF.Float expected_delivery_date: DF.Date | None fg_warehouse: DF.Link | None from_wip_warehouse: DF.Check @@ -477,6 +478,18 @@ class WorkOrder(Document): self.set_produced_qty_for_sub_assembly_item() self.update_production_plan_status() + def update_disassembled_qty(self, qty, is_cancel=False): + if is_cancel: + self.disassembled_qty = max(0, self.disassembled_qty - qty) + else: + if self.docstatus == 1: + self.disassembled_qty += qty + + if not is_cancel and self.disassembled_qty > self.produced_qty: + frappe.throw(_("Cannot disassemble more than produced quantity.")) + + self.db_set("disassembled_qty", self.disassembled_qty) + def get_transferred_or_manufactured_qty(self, purpose): table = frappe.qb.DocType("Stock Entry") query = frappe.qb.from_(table).where( @@ -1964,7 +1977,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.get_items() + stock_entry.get_items(qty, work_order.production_item) if purpose != "Disassemble": stock_entry.set_serial_no_batch_for_finished_good() diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.py b/erpnext/stock/doctype/stock_entry/stock_entry.py index fa823294d0b..79d547f6655 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.py +++ b/erpnext/stock/doctype/stock_entry/stock_entry.py @@ -27,6 +27,7 @@ from erpnext.buying.utils import check_on_hold_or_closed_status from erpnext.controllers.taxes_and_totals import init_landed_taxes_and_totals from erpnext.manufacturing.doctype.bom.bom import ( add_additional_cost, + get_bom_items_as_dict, get_op_cost_from_sub_assemblies, get_scrap_items_from_sub_assemblies, validate_bom_no, @@ -248,6 +249,7 @@ class StockEntry(StockController): self.validate_closed_subcontracting_order() self.make_bundle_using_old_serial_batch_fields() self.update_work_order() + self.update_disassembled_order() self.update_stock_ledger() self.make_stock_reserve_for_wip_and_fg() @@ -278,6 +280,7 @@ class StockEntry(StockController): self.validate_work_order_status() self.update_work_order() + self.update_disassembled_order(is_cancel=True) self.update_stock_ledger() self.ignore_linked_doctypes = ( @@ -1650,6 +1653,13 @@ class StockEntry(StockController): if not pro_doc.operations: pro_doc.set_actual_dates() + def update_disassembled_order(self, is_cancel=False): + if not self.work_order: + return + if self.purpose == "Disassemble" and self.fg_completed_qty: + pro_doc = frappe.get_doc("Work Order", self.work_order) + pro_doc.run_method("update_disassembled_qty", self.fg_completed_qty, is_cancel) + def make_stock_reserve_for_wip_and_fg(self): if self.is_stock_reserve_for_work_order(): pro_doc = frappe.get_doc("Work Order", self.work_order) @@ -1826,7 +1836,7 @@ class StockEntry(StockController): }, ) - def get_items_for_disassembly(self): + def get_items_for_disassembly(self, disassemble_qty, production_item): """Get items for Disassembly Order""" if not self.work_order: @@ -1834,9 +1844,9 @@ class StockEntry(StockController): items = self.get_items_from_manufacture_entry() - s_warehouse = "" - if self.work_order: - s_warehouse = frappe.db.get_value("Work Order", self.work_order, "fg_warehouse") + s_warehouse = frappe.db.get_value("Work Order", self.work_order, "fg_warehouse") + + items_dict = get_bom_items_as_dict(self.bom_no, self.company, disassemble_qty) for row in items: child_row = self.append("items", {}) @@ -1844,6 +1854,15 @@ class StockEntry(StockController): if value is not None: child_row.set(field, value) + # update qty and amount from BOM items + bom_items = items_dict.get(row.item_code) + if bom_items: + child_row.qty = bom_items.get("qty", child_row.qty) + child_row.amount = bom_items.get("amount", child_row.amount) + + if row.item_code == production_item: + child_row.qty = disassemble_qty + child_row.s_warehouse = (self.from_warehouse or s_warehouse) if row.is_finished_item else "" child_row.t_warehouse = self.to_warehouse if not row.is_finished_item else "" child_row.is_finished_item = 0 if row.is_finished_item else 1 @@ -1876,12 +1895,13 @@ class StockEntry(StockController): ) @frappe.whitelist() - def get_items(self): + def get_items(self, qty, production_item): self.set("items", []) self.validate_work_order() + # print(qty, 'qty\n\n') - if self.purpose == "Disassemble": - return self.get_items_for_disassembly() + if self.purpose == "Disassemble" and qty is not None: + return self.get_items_for_disassembly(qty, production_item) if not self.posting_date or not self.posting_time: frappe.throw(_("Posting date and posting time is mandatory"))