diff --git a/erpnext/manufacturing/doctype/work_order/work_order.js b/erpnext/manufacturing/doctype/work_order/work_order.js index b6206cefcbb..b6f3f740e55 100644 --- a/erpnext/manufacturing/doctype/work_order/work_order.js +++ b/erpnext/manufacturing/doctype/work_order/work_order.js @@ -401,7 +401,7 @@ frappe.ui.form.on("Work Order", { make_disassembly_order(frm) { erpnext.work_order - .show_prompt_for_qty_input(frm, "Disassemble") + .show_disassembly_prompt(frm) .then((data) => { if (flt(data.qty) <= 0) { frappe.msgprint(__("Disassemble Qty cannot be less than or equal to 0.")); @@ -411,11 +411,14 @@ frappe.ui.form.on("Work Order", { work_order_id: frm.doc.name, purpose: "Disassemble", qty: data.qty, + source_stock_entry: data.source_stock_entry, }); }) .then((stock_entry) => { - frappe.model.sync(stock_entry); - frappe.set_route("Form", stock_entry.doctype, stock_entry.name); + if (stock_entry) { + frappe.model.sync(stock_entry); + frappe.set_route("Form", stock_entry.doctype, stock_entry.name); + } }); }, @@ -865,8 +868,67 @@ erpnext.work_order = { return flt(max, precision("qty")); }, +<<<<<<< HEAD show_prompt_for_qty_input: function (frm, purpose) { let max = this.get_max_transferable_qty(frm, purpose); +======= + show_disassembly_prompt: function (frm) { + let max_qty = flt(frm.doc.produced_qty - frm.doc.disassembled_qty); + + let fields = [ + { + fieldtype: "Link", + label: __("Source Manufacture Entry"), + fieldname: "source_stock_entry", + options: "Stock Entry", + description: __("Optional. Select a specific manufacture entry to reverse."), + get_query: () => { + return { + filters: { + work_order: frm.doc.name, + purpose: "Manufacture", + docstatus: 1, + }, + }; + }, + onchange: async function () { + if (!frm.disassembly_prompt) return; + + let se_name = this.value; + let qty = max_qty; + if (se_name) { + qty = await frappe.xcall( + "erpnext.manufacturing.doctype.work_order.work_order.get_disassembly_available_qty", + { stock_entry_name: se_name } + ); + } + + frm.disassembly_prompt.set_value("qty", qty); + frm.disassembly_prompt.fields_dict.qty.set_description(__("Max: {0}", [qty])); + }, + }, + { + fieldtype: "Float", + label: __("Qty for {0}", [__("Disassemble")]), + fieldname: "qty", + description: __("Max: {0}", [max_qty]), + default: max_qty, + }, + ]; + + return new Promise((resolve, reject) => { + frm.disassembly_prompt = frappe.prompt( + fields, + (data) => resolve(data), + __("Disassemble"), + __("Create") + ); + }); + }, + + show_prompt_for_qty_input: function (frm, purpose, qty, additional_transfer_entry) { + let max = !additional_transfer_entry ? this.get_max_transferable_qty(frm, purpose) : qty; +>>>>>>> 68e97808c5 (fix: disassembly prompt with source stock entry field) let fields = [ { diff --git a/erpnext/manufacturing/doctype/work_order/work_order.py b/erpnext/manufacturing/doctype/work_order/work_order.py index bc3def1186f..7f8afa49ab3 100644 --- a/erpnext/manufacturing/doctype/work_order/work_order.py +++ b/erpnext/manufacturing/doctype/work_order/work_order.py @@ -1480,7 +1480,18 @@ def set_work_order_ops(name): @frappe.whitelist() +<<<<<<< HEAD def make_stock_entry(work_order_id, purpose, qty=None, target_warehouse=None): +======= +def make_stock_entry( + work_order_id: str, + purpose: str, + qty: float | None = None, + target_warehouse: str | None = None, + is_additional_transfer_entry: bool = False, + source_stock_entry: str | None = None, +): +>>>>>>> 68e97808c5 (fix: disassembly prompt with source stock entry field) 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 @@ -1517,6 +1528,8 @@ def make_stock_entry(work_order_id, purpose, qty=None, target_warehouse=None): if purpose == "Disassemble": stock_entry.from_warehouse = work_order.fg_warehouse stock_entry.to_warehouse = target_warehouse or work_order.source_warehouse + if source_stock_entry: + stock_entry.source_stock_entry = source_stock_entry stock_entry.set_stock_entry_type() stock_entry.get_items() @@ -1528,9 +1541,37 @@ def make_stock_entry(work_order_id, purpose, qty=None, target_warehouse=None): @frappe.whitelist() +<<<<<<< HEAD def get_default_warehouse(): doc = frappe.get_cached_doc("Manufacturing Settings") +======= +def get_disassembly_available_qty(stock_entry_name: str) -> float: + se = frappe.db.get_value("Stock Entry", stock_entry_name, ["fg_completed_qty"], as_dict=True) + if not se: + return 0.0 + + already_disassembled = flt( + frappe.db.get_value( + "Stock Entry", + { + "source_stock_entry": stock_entry_name, + "purpose": "Disassemble", + "docstatus": 1, + }, + [{"SUM": "fg_completed_qty"}], + ) + ) + + return flt(se.fg_completed_qty) - already_disassembled + + +@frappe.whitelist() +def get_default_warehouse(company: str): + wip, fg, scrap = frappe.get_cached_value( + "Company", company, ["default_wip_warehouse", "default_fg_warehouse", "default_scrap_warehouse"] + ) +>>>>>>> 68e97808c5 (fix: disassembly prompt with source stock entry field) return { "wip_warehouse": doc.default_wip_warehouse, "fg_warehouse": doc.default_fg_warehouse,