fix: disassemble qty calculation & max calculation to be allowed to create it

(cherry picked from commit 3e4d160626)

# Conflicts:
#	erpnext/manufacturing/doctype/work_order/work_order.json
#	erpnext/stock/doctype/stock_entry/stock_entry.py
This commit is contained in:
iamkhanraheel
2025-06-21 01:14:26 +05:30
committed by Mergify
parent e628a37b99
commit bf78f6173c
4 changed files with 115 additions and 9 deletions

View File

@@ -803,7 +803,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) {

View File

@@ -20,6 +20,7 @@
"qty",
"material_transferred_for_manufacturing",
"produced_qty",
"disassembled_qty",
"process_loss_qty",
"project",
"section_break_ndpq",
@@ -585,7 +586,34 @@
},
{
"fieldname": "section_break_ndpq",
<<<<<<< HEAD
"fieldtype": "Section Break"
=======
"fieldtype": "Section Break",
"label": "Required Items"
},
{
"default": "0",
"fetch_from": "bom_no.track_semi_finished_goods",
"fieldname": "track_semi_finished_goods",
"fieldtype": "Check",
"label": "Track Semi Finished Goods",
"read_only": 1
},
{
"default": "0",
"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
>>>>>>> 3e4d160626 (fix: disassemble qty calculation & max calculation to be allowed to create it)
}
],
"icon": "fa fa-cogs",
@@ -593,7 +621,11 @@
"image_field": "image",
"is_submittable": 1,
"links": [],
<<<<<<< HEAD
"modified": "2024-02-11 15:47:13.454422",
=======
"modified": "2025-06-21 00:55:45.916224",
>>>>>>> 3e4d160626 (fix: disassemble qty calculation & max calculation to be allowed to create it)
"modified_by": "Administrator",
"module": "Manufacturing",
"name": "Work Order",

View File

@@ -88,6 +88,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
from_wip_warehouse: DF.Check
@@ -406,6 +407,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(
@@ -1475,7 +1488,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()

View File

@@ -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,
@@ -243,6 +244,11 @@ class StockEntry(StockController):
def on_submit(self):
self.validate_closed_subcontracting_order()
self.make_bundle_using_old_serial_batch_fields()
<<<<<<< HEAD
=======
self.update_work_order()
self.update_disassembled_order()
>>>>>>> 3e4d160626 (fix: disassemble qty calculation & max calculation to be allowed to create it)
self.update_stock_ledger()
self.update_work_order()
self.validate_subcontract_order()
@@ -271,6 +277,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 = (
@@ -1617,6 +1624,50 @@ class StockEntry(StockController):
if not pro_doc.operations:
pro_doc.set_actual_dates()
<<<<<<< HEAD
=======
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)
if (
self.purpose == "Manufacture"
and not pro_doc.sales_order
and not pro_doc.production_plan_sub_assembly_item
):
return
pro_doc.set_reserved_qty_for_wip_and_fg(self)
def cancel_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)
if (
self.purpose == "Manufacture"
and not pro_doc.sales_order
and not pro_doc.production_plan_sub_assembly_item
):
return
pro_doc.cancel_reserved_qty_for_wip_and_fg(self)
def is_stock_reserve_for_work_order(self):
if (
self.work_order
and self.stock_entry_type in ["Material Transfer for Manufacture", "Manufacture"]
and frappe.get_cached_value("Work Order", self.work_order, "reserve_stock")
):
return True
return False
>>>>>>> 3e4d160626 (fix: disassemble qty calculation & max calculation to be allowed to create it)
@frappe.whitelist()
def get_item_details(self, args=None, for_update=False):
item = frappe.qb.DocType("Item")
@@ -1759,7 +1810,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:
@@ -1767,16 +1818,25 @@ 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")
items_dict = get_bom_items_as_dict(self.bom_no, self.company, disassemble_qty)
for row in items:
child_row = self.append("items", {})
for field, value in row.items():
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
@@ -1809,12 +1869,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"))