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

This commit is contained in:
iamkhanraheel
2025-06-21 01:14:26 +05:30
parent 2c54933e3e
commit 3e4d160626
4 changed files with 52 additions and 10 deletions

View File

@@ -877,7 +877,7 @@ erpnext.work_order = {
get_max_transferable_qty: (frm, purpose) => { get_max_transferable_qty: (frm, purpose) => {
let max = 0; let max = 0;
if (purpose === "Disassemble") { if (purpose === "Disassemble") {
return flt(frm.doc.produced_qty); return flt(frm.doc.produced_qty - frm.doc.disassembled_qty);
} }
if (frm.doc.skip_transfer) { if (frm.doc.skip_transfer) {

View File

@@ -20,6 +20,7 @@
"qty", "qty",
"material_transferred_for_manufacturing", "material_transferred_for_manufacturing",
"produced_qty", "produced_qty",
"disassembled_qty",
"process_loss_qty", "process_loss_qty",
"project", "project",
"track_semi_finished_goods", "track_semi_finished_goods",
@@ -592,6 +593,14 @@
"fieldname": "reserve_stock", "fieldname": "reserve_stock",
"fieldtype": "Check", "fieldtype": "Check",
"label": " Reserve Stock" "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, "grid_page_length": 50,
@@ -600,7 +609,7 @@
"image_field": "image", "image_field": "image",
"is_submittable": 1, "is_submittable": 1,
"links": [], "links": [],
"modified": "2025-04-25 11:46:38.739588", "modified": "2025-06-21 00:55:45.916224",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Manufacturing", "module": "Manufacturing",
"name": "Work Order", "name": "Work Order",

View File

@@ -89,6 +89,7 @@ class WorkOrder(Document):
company: DF.Link company: DF.Link
corrective_operation_cost: DF.Currency corrective_operation_cost: DF.Currency
description: DF.SmallText | None description: DF.SmallText | None
disassembled_qty: DF.Float
expected_delivery_date: DF.Date | None expected_delivery_date: DF.Date | None
fg_warehouse: DF.Link | None fg_warehouse: DF.Link | None
from_wip_warehouse: DF.Check from_wip_warehouse: DF.Check
@@ -477,6 +478,18 @@ class WorkOrder(Document):
self.set_produced_qty_for_sub_assembly_item() self.set_produced_qty_for_sub_assembly_item()
self.update_production_plan_status() 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): def get_transferred_or_manufactured_qty(self, purpose):
table = frappe.qb.DocType("Stock Entry") table = frappe.qb.DocType("Stock Entry")
query = frappe.qb.from_(table).where( 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.to_warehouse = target_warehouse or work_order.source_warehouse
stock_entry.set_stock_entry_type() stock_entry.set_stock_entry_type()
stock_entry.get_items() stock_entry.get_items(qty, work_order.production_item)
if purpose != "Disassemble": if purpose != "Disassemble":
stock_entry.set_serial_no_batch_for_finished_good() 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.controllers.taxes_and_totals import init_landed_taxes_and_totals
from erpnext.manufacturing.doctype.bom.bom import ( from erpnext.manufacturing.doctype.bom.bom import (
add_additional_cost, add_additional_cost,
get_bom_items_as_dict,
get_op_cost_from_sub_assemblies, get_op_cost_from_sub_assemblies,
get_scrap_items_from_sub_assemblies, get_scrap_items_from_sub_assemblies,
validate_bom_no, validate_bom_no,
@@ -248,6 +249,7 @@ class StockEntry(StockController):
self.validate_closed_subcontracting_order() self.validate_closed_subcontracting_order()
self.make_bundle_using_old_serial_batch_fields() self.make_bundle_using_old_serial_batch_fields()
self.update_work_order() self.update_work_order()
self.update_disassembled_order()
self.update_stock_ledger() self.update_stock_ledger()
self.make_stock_reserve_for_wip_and_fg() self.make_stock_reserve_for_wip_and_fg()
@@ -278,6 +280,7 @@ class StockEntry(StockController):
self.validate_work_order_status() self.validate_work_order_status()
self.update_work_order() self.update_work_order()
self.update_disassembled_order(is_cancel=True)
self.update_stock_ledger() self.update_stock_ledger()
self.ignore_linked_doctypes = ( self.ignore_linked_doctypes = (
@@ -1650,6 +1653,13 @@ class StockEntry(StockController):
if not pro_doc.operations: if not pro_doc.operations:
pro_doc.set_actual_dates() 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): def make_stock_reserve_for_wip_and_fg(self):
if self.is_stock_reserve_for_work_order(): if self.is_stock_reserve_for_work_order():
pro_doc = frappe.get_doc("Work Order", self.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""" """Get items for Disassembly Order"""
if not self.work_order: if not self.work_order:
@@ -1834,9 +1844,9 @@ class StockEntry(StockController):
items = self.get_items_from_manufacture_entry() items = self.get_items_from_manufacture_entry()
s_warehouse = "" s_warehouse = frappe.db.get_value("Work Order", self.work_order, "fg_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: for row in items:
child_row = self.append("items", {}) child_row = self.append("items", {})
@@ -1844,6 +1854,15 @@ class StockEntry(StockController):
if value is not None: if value is not None:
child_row.set(field, value) 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.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.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 child_row.is_finished_item = 0 if row.is_finished_item else 1
@@ -1876,12 +1895,13 @@ class StockEntry(StockController):
) )
@frappe.whitelist() @frappe.whitelist()
def get_items(self): def get_items(self, qty, production_item):
self.set("items", []) self.set("items", [])
self.validate_work_order() self.validate_work_order()
# print(qty, 'qty\n\n')
if self.purpose == "Disassemble": if self.purpose == "Disassemble" and qty is not None:
return self.get_items_for_disassembly() return self.get_items_for_disassembly(qty, production_item)
if not self.posting_date or not self.posting_time: if not self.posting_date or not self.posting_time:
frappe.throw(_("Posting date and posting time is mandatory")) frappe.throw(_("Posting date and posting time is mandatory"))