diff --git a/erpnext/manufacturing/doctype/job_card/job_card.js b/erpnext/manufacturing/doctype/job_card/job_card.js index 5305db318b19..4a46d5774450 100644 --- a/erpnext/manufacturing/doctype/job_card/job_card.js +++ b/erpnext/manufacturing/doctype/job_card/job_card.js @@ -83,7 +83,7 @@ frappe.ui.form.on('Job Card', { // and if stock mvt for WIP is required if (frm.doc.work_order) { frappe.db.get_value('Work Order', frm.doc.work_order, ['skip_transfer', 'status'], (result) => { - if (result.skip_transfer === 1 || result.status == 'In Process' || frm.doc.transferred_qty > 0) { + if (result.skip_transfer === 1 || result.status == 'In Process' || frm.doc.transferred_qty > 0 || !frm.doc.items.length) { frm.trigger("prepare_timer_buttons"); } }); @@ -411,6 +411,16 @@ frappe.ui.form.on('Job Card', { } }); + if (frm.doc.total_completed_qty && frm.doc.for_quantity > frm.doc.total_completed_qty) { + let flt_precision = precision('for_quantity', frm.doc); + let process_loss_qty = ( + flt(frm.doc.for_quantity, flt_precision) + - flt(frm.doc.total_completed_qty, flt_precision) + ); + + frm.set_value('process_loss_qty', process_loss_qty); + } + refresh_field("total_completed_qty"); } }); diff --git a/erpnext/manufacturing/doctype/job_card/job_card.json b/erpnext/manufacturing/doctype/job_card/job_card.json index 316e586b7a2a..dc0213a7bcdb 100644 --- a/erpnext/manufacturing/doctype/job_card/job_card.json +++ b/erpnext/manufacturing/doctype/job_card/job_card.json @@ -38,6 +38,7 @@ "time_logs", "section_break_13", "total_completed_qty", + "process_loss_qty", "column_break_15", "total_time_in_mins", "section_break_8", @@ -435,11 +436,17 @@ "fieldname": "expected_end_date", "fieldtype": "Datetime", "label": "Expected End Date" + }, + { + "fieldname": "process_loss_qty", + "fieldtype": "Float", + "label": "Process Loss Qty", + "read_only": 1 } ], "is_submittable": 1, "links": [], - "modified": "2023-05-23 09:56:43.826602", + "modified": "2023-06-09 12:04:55.534264", "modified_by": "Administrator", "module": "Manufacturing", "name": "Job Card", @@ -497,4 +504,4 @@ "states": [], "title_field": "operation", "track_changes": 1 -} \ No newline at end of file +} diff --git a/erpnext/manufacturing/doctype/job_card/job_card.py b/erpnext/manufacturing/doctype/job_card/job_card.py index fcaa3fd276fc..496cbfd0a6bc 100644 --- a/erpnext/manufacturing/doctype/job_card/job_card.py +++ b/erpnext/manufacturing/doctype/job_card/job_card.py @@ -161,7 +161,7 @@ def validate_time_logs(self): self.total_completed_qty = flt(self.total_completed_qty, self.precision("total_completed_qty")) for row in self.sub_operations: - self.total_completed_qty += row.completed_qty + self.c += row.completed_qty def get_overlap_for(self, args, check_next_available_slot=False): production_capacity = 1 @@ -451,6 +451,9 @@ def get_required_items(self): }, ) + def before_save(self): + self.set_process_loss() + def on_submit(self): self.validate_transfer_qty() self.validate_job_card() @@ -487,19 +490,35 @@ def validate_job_card(self): ) ) - if self.for_quantity and self.total_completed_qty != self.for_quantity: + precision = self.precision("total_completed_qty") + total_completed_qty = flt( + flt(self.total_completed_qty, precision) + flt(self.process_loss_qty, precision) + ) + + if self.for_quantity and flt(total_completed_qty, precision) != flt( + self.for_quantity, precision + ): total_completed_qty = bold(_("Total Completed Qty")) qty_to_manufacture = bold(_("Qty to Manufacture")) frappe.throw( _("The {0} ({1}) must be equal to {2} ({3})").format( total_completed_qty, - bold(self.total_completed_qty), + bold(flt(total_completed_qty, precision)), qty_to_manufacture, bold(self.for_quantity), ) ) + def set_process_loss(self): + precision = self.precision("total_completed_qty") + + self.process_loss_qty = 0.0 + if self.total_completed_qty and self.for_quantity > self.total_completed_qty: + self.process_loss_qty = flt(self.for_quantity, precision) - flt( + self.total_completed_qty, precision + ) + def update_work_order(self): if not self.work_order: return @@ -511,7 +530,7 @@ def update_work_order(self): ): return - for_quantity, time_in_mins = 0, 0 + for_quantity, time_in_mins, process_loss_qty = 0, 0, 0 from_time_list, to_time_list = [], [] field = "operation_id" @@ -519,6 +538,7 @@ def update_work_order(self): if data and len(data) > 0: for_quantity = flt(data[0].completed_qty) time_in_mins = flt(data[0].time_in_mins) + process_loss_qty = flt(data[0].process_loss_qty) wo = frappe.get_doc("Work Order", self.work_order) @@ -526,8 +546,8 @@ def update_work_order(self): self.update_corrective_in_work_order(wo) elif self.operation_id: - self.validate_produced_quantity(for_quantity, wo) - self.update_work_order_data(for_quantity, time_in_mins, wo) + self.validate_produced_quantity(for_quantity, process_loss_qty, wo) + self.update_work_order_data(for_quantity, process_loss_qty, time_in_mins, wo) def update_corrective_in_work_order(self, wo): wo.corrective_operation_cost = 0.0 @@ -542,11 +562,11 @@ def update_corrective_in_work_order(self, wo): wo.flags.ignore_validate_update_after_submit = True wo.save() - def validate_produced_quantity(self, for_quantity, wo): + def validate_produced_quantity(self, for_quantity, process_loss_qty, wo): if self.docstatus < 2: return - if wo.produced_qty > for_quantity: + if wo.produced_qty > for_quantity + process_loss_qty: first_part_msg = _( "The {0} {1} is used to calculate the valuation cost for the finished good {2}." ).format( @@ -561,7 +581,7 @@ def validate_produced_quantity(self, for_quantity, wo): _("{0} {1}").format(first_part_msg, second_part_msg), JobCardCancelError, title=_("Error") ) - def update_work_order_data(self, for_quantity, time_in_mins, wo): + def update_work_order_data(self, for_quantity, process_loss_qty, time_in_mins, wo): workstation_hour_rate = frappe.get_value("Workstation", self.workstation, "hour_rate") jc = frappe.qb.DocType("Job Card") jctl = frappe.qb.DocType("Job Card Time Log") @@ -582,6 +602,7 @@ def update_work_order_data(self, for_quantity, time_in_mins, wo): for data in wo.operations: if data.get("name") == self.operation_id: data.completed_qty = for_quantity + data.process_loss_qty = process_loss_qty data.actual_operation_time = time_in_mins data.actual_start_time = time_data[0].start_time if time_data else None data.actual_end_time = time_data[0].end_time if time_data else None @@ -599,7 +620,11 @@ def update_work_order_data(self, for_quantity, time_in_mins, wo): def get_current_operation_data(self): return frappe.get_all( "Job Card", - fields=["sum(total_time_in_mins) as time_in_mins", "sum(total_completed_qty) as completed_qty"], + fields=[ + "sum(total_time_in_mins) as time_in_mins", + "sum(total_completed_qty) as completed_qty", + "sum(process_loss_qty) as process_loss_qty", + ], filters={ "docstatus": 1, "work_order": self.work_order, @@ -777,7 +802,7 @@ def validate_sequence_id(self): data = frappe.get_all( "Work Order Operation", - fields=["operation", "status", "completed_qty"], + fields=["operation", "status", "completed_qty", "sequence_id"], filters={"docstatus": 1, "parent": self.work_order, "sequence_id": ("<", self.sequence_id)}, order_by="sequence_id, idx", ) @@ -795,6 +820,16 @@ def validate_sequence_id(self): OperationSequenceError, ) + if row.completed_qty < current_operation_qty: + msg = f"""The completed quantity {bold(current_operation_qty)} + of an operation {bold(self.operation)} cannot be greater + than the completed quantity {bold(row.completed_qty)} + of a previous operation + {bold(row.operation)}. + """ + + frappe.throw(_(msg)) + def validate_work_order(self): if self.is_work_order_closed(): frappe.throw(_("You can't make any changes to Job Card since Work Order is closed.")) diff --git a/erpnext/manufacturing/doctype/job_card/test_job_card.py b/erpnext/manufacturing/doctype/job_card/test_job_card.py index a7f06486abc6..e7fbcda7ab0d 100644 --- a/erpnext/manufacturing/doctype/job_card/test_job_card.py +++ b/erpnext/manufacturing/doctype/job_card/test_job_card.py @@ -5,6 +5,7 @@ from typing import Literal import frappe +from frappe.test_runner import make_test_records from frappe.tests.utils import FrappeTestCase, change_settings from frappe.utils import random_string from frappe.utils.data import add_to_date, now, today @@ -469,6 +470,119 @@ def test_job_card_material_request_and_bom_details(self): self.assertEqual(ste.from_bom, 1.0) self.assertEqual(ste.bom_no, work_order.bom_no) + def test_job_card_proccess_qty_and_completed_qty(self): + from erpnext.manufacturing.doctype.routing.test_routing import ( + create_routing, + setup_bom, + setup_operations, + ) + from erpnext.manufacturing.doctype.work_order.work_order import ( + make_stock_entry as make_stock_entry_for_wo, + ) + from erpnext.stock.doctype.item.test_item import make_item + from erpnext.stock.doctype.warehouse.test_warehouse import create_warehouse + + operations = [ + {"operation": "Test Operation A1", "workstation": "Test Workstation A", "time_in_mins": 30}, + {"operation": "Test Operation B1", "workstation": "Test Workstation A", "time_in_mins": 20}, + ] + + make_test_records("UOM") + + warehouse = create_warehouse("Test Warehouse 123 for Job Card") + + setup_operations(operations) + + item_code = "Test Job Card Process Qty Item" + for item in [item_code, item_code + "RM 1", item_code + "RM 2"]: + if not frappe.db.exists("Item", item): + make_item( + item, + { + "item_name": item, + "stock_uom": "Nos", + "is_stock_item": 1, + }, + ) + + routing_doc = create_routing(routing_name="Testing Route", operations=operations) + bom_doc = setup_bom( + item_code=item_code, + routing=routing_doc.name, + raw_materials=[item_code + "RM 1", item_code + "RM 2"], + source_warehouse=warehouse, + ) + + for row in bom_doc.items: + make_stock_entry( + item_code=row.item_code, + target=row.source_warehouse, + qty=10, + basic_rate=100, + ) + + wo_doc = make_wo_order_test_record( + production_item=item_code, + bom_no=bom_doc.name, + skip_transfer=1, + wip_warehouse=warehouse, + source_warehouse=warehouse, + ) + + for row in routing_doc.operations: + self.assertEqual(row.sequence_id, row.idx) + + first_job_card = frappe.get_all( + "Job Card", + filters={"work_order": wo_doc.name, "sequence_id": 1}, + fields=["name"], + order_by="sequence_id", + limit=1, + )[0].name + + jc = frappe.get_doc("Job Card", first_job_card) + jc.time_logs[0].completed_qty = 8 + jc.save() + jc.submit() + + self.assertEqual(jc.process_loss_qty, 2) + self.assertEqual(jc.for_quantity, 10) + + second_job_card = frappe.get_all( + "Job Card", + filters={"work_order": wo_doc.name, "sequence_id": 2}, + fields=["name"], + order_by="sequence_id", + limit=1, + )[0].name + + jc2 = frappe.get_doc("Job Card", second_job_card) + jc2.time_logs[0].completed_qty = 10 + + self.assertRaises(frappe.ValidationError, jc2.save) + + jc2.load_from_db() + jc2.time_logs[0].completed_qty = 8 + jc2.save() + jc2.submit() + + self.assertEqual(jc2.for_quantity, 10) + self.assertEqual(jc2.process_loss_qty, 2) + + s = frappe.get_doc(make_stock_entry_for_wo(wo_doc.name, "Manufacture", 10)) + s.submit() + + self.assertEqual(s.process_loss_qty, 2) + + wo_doc.reload() + for row in wo_doc.operations: + self.assertEqual(row.completed_qty, 8) + self.assertEqual(row.process_loss_qty, 2) + + self.assertEqual(wo_doc.produced_qty, 8) + self.assertEqual(wo_doc.process_loss_qty, 2) + self.assertEqual(wo_doc.status, "Completed") + def create_bom_with_multiple_operations(): "Create a BOM with multiple operations and Material Transfer against Job Card" diff --git a/erpnext/manufacturing/doctype/routing/test_routing.py b/erpnext/manufacturing/doctype/routing/test_routing.py index 48f1851cb10a..a37ff28031bb 100644 --- a/erpnext/manufacturing/doctype/routing/test_routing.py +++ b/erpnext/manufacturing/doctype/routing/test_routing.py @@ -141,6 +141,7 @@ def setup_bom(**args): routing=args.routing, with_operations=1, currency=args.currency, + source_warehouse=args.source_warehouse, ) else: bom_doc = frappe.get_doc("BOM", name) diff --git a/erpnext/manufacturing/doctype/work_order/test_work_order.py b/erpnext/manufacturing/doctype/work_order/test_work_order.py index bb53c8c225c4..f1ac5d7b3086 100644 --- a/erpnext/manufacturing/doctype/work_order/test_work_order.py +++ b/erpnext/manufacturing/doctype/work_order/test_work_order.py @@ -891,7 +891,7 @@ def test_wo_completion_with_pl_bom(self): self.assertEqual(se.process_loss_qty, 1) wo.load_from_db() - self.assertEqual(wo.status, "In Process") + self.assertEqual(wo.status, "Completed") @timeout(seconds=60) def test_job_card_scrap_item(self): diff --git a/erpnext/manufacturing/doctype/work_order/work_order.js b/erpnext/manufacturing/doctype/work_order/work_order.js index d0c9966f8ba6..c1a078d65e05 100644 --- a/erpnext/manufacturing/doctype/work_order/work_order.js +++ b/erpnext/manufacturing/doctype/work_order/work_order.js @@ -139,7 +139,7 @@ frappe.ui.form.on("Work Order", { } if (frm.doc.status != "Closed") { - if (frm.doc.docstatus === 1 + if (frm.doc.docstatus === 1 && frm.doc.status !== "Completed" && frm.doc.operations && frm.doc.operations.length) { const not_completed = frm.doc.operations.filter(d => { @@ -256,6 +256,12 @@ frappe.ui.form.on("Work Order", { label: __('Batch Size'), read_only: 1 }, + { + fieldtype: 'Int', + fieldname: 'sequence_id', + label: __('Sequence Id'), + read_only: 1 + }, ], data: operations_data, in_place_edit: true, @@ -280,8 +286,8 @@ frappe.ui.form.on("Work Order", { var pending_qty = 0; frm.doc.operations.forEach(data => { - if(data.completed_qty != frm.doc.qty) { - pending_qty = frm.doc.qty - flt(data.completed_qty); + if(data.completed_qty + data.process_loss_qty != frm.doc.qty) { + pending_qty = frm.doc.qty - flt(data.completed_qty) - flt(data.process_loss_qty); if (pending_qty) { dialog.fields_dict.operations.df.data.push({ @@ -290,7 +296,8 @@ frappe.ui.form.on("Work Order", { 'workstation': data.workstation, 'batch_size': data.batch_size, 'qty': pending_qty, - 'pending_qty': pending_qty + 'pending_qty': pending_qty, + 'sequence_id': data.sequence_id }); } } diff --git a/erpnext/manufacturing/doctype/work_order/work_order.json b/erpnext/manufacturing/doctype/work_order/work_order.json index aa9049801cca..38e72533ba0c 100644 --- a/erpnext/manufacturing/doctype/work_order/work_order.json +++ b/erpnext/manufacturing/doctype/work_order/work_order.json @@ -47,8 +47,8 @@ "required_items_section", "materials_and_operations_tab", "operations_section", - "operations", "transfer_material_against", + "operations", "time", "planned_start_date", "planned_end_date", @@ -331,7 +331,6 @@ "label": "Expected Delivery Date" }, { - "collapsible": 1, "fieldname": "operations_section", "fieldtype": "Section Break", "label": "Operations", @@ -599,7 +598,7 @@ "image_field": "image", "is_submittable": 1, "links": [], - "modified": "2023-04-06 12:35:12.149827", + "modified": "2023-06-09 13:20:09.154362", "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 75845226a65a..19e081acf733 100644 --- a/erpnext/manufacturing/doctype/work_order/work_order.py +++ b/erpnext/manufacturing/doctype/work_order/work_order.py @@ -249,7 +249,9 @@ def get_status(self, status=None): status = "Not Started" if flt(self.material_transferred_for_manufacturing) > 0: status = "In Process" - if flt(self.produced_qty) >= flt(self.qty): + + total_qty = flt(self.produced_qty) + flt(self.process_loss_qty) + if flt(total_qty) >= flt(self.qty): status = "Completed" else: status = "Cancelled" @@ -736,13 +738,15 @@ def update_operation_status(self): max_allowed_qty_for_wo = flt(self.qty) + (allowance_percentage / 100 * flt(self.qty)) for d in self.get("operations"): - if not d.completed_qty: + precision = d.precision("completed_qty") + qty = flt(d.completed_qty, precision) + flt(d.process_loss_qty, precision) + if not qty: d.status = "Pending" - elif flt(d.completed_qty) < flt(self.qty): + elif flt(qty) < flt(self.qty): d.status = "Work in Progress" - elif flt(d.completed_qty) == flt(self.qty): + elif flt(qty) == flt(self.qty): d.status = "Completed" - elif flt(d.completed_qty) <= max_allowed_qty_for_wo: + elif flt(qty) <= max_allowed_qty_for_wo: d.status = "Completed" else: frappe.throw(_("Completed Qty cannot be greater than 'Qty to Manufacture'")) diff --git a/erpnext/manufacturing/doctype/work_order_operation/work_order_operation.json b/erpnext/manufacturing/doctype/work_order_operation/work_order_operation.json index 31b920145e0f..de1f67f13fd6 100644 --- a/erpnext/manufacturing/doctype/work_order_operation/work_order_operation.json +++ b/erpnext/manufacturing/doctype/work_order_operation/work_order_operation.json @@ -2,12 +2,14 @@ "actions": [], "creation": "2014-10-16 14:35:41.950175", "doctype": "DocType", + "editable_grid": 1, "engine": "InnoDB", "field_order": [ "details", "operation", "status", "completed_qty", + "process_loss_qty", "column_break_4", "bom", "workstation_type", @@ -36,6 +38,7 @@ "fieldtype": "Section Break" }, { + "columns": 2, "fieldname": "operation", "fieldtype": "Link", "in_list_view": 1, @@ -46,6 +49,7 @@ "reqd": 1 }, { + "columns": 2, "fieldname": "bom", "fieldtype": "Link", "in_list_view": 1, @@ -62,7 +66,7 @@ "oldfieldtype": "Text" }, { - "columns": 1, + "columns": 2, "description": "Operation completed for how many finished goods?", "fieldname": "completed_qty", "fieldtype": "Float", @@ -80,6 +84,7 @@ "options": "Pending\nWork in Progress\nCompleted" }, { + "columns": 1, "fieldname": "workstation", "fieldtype": "Link", "in_list_view": 1, @@ -115,7 +120,7 @@ "fieldname": "time_in_mins", "fieldtype": "Float", "in_list_view": 1, - "label": "Operation Time", + "label": "Time", "oldfieldname": "time_in_mins", "oldfieldtype": "Currency", "reqd": 1 @@ -203,12 +208,21 @@ "fieldtype": "Link", "label": "Workstation Type", "options": "Workstation Type" + }, + { + "columns": 2, + "fieldname": "process_loss_qty", + "fieldtype": "Float", + "in_list_view": 1, + "label": "Process Loss Qty", + "no_copy": 1, + "read_only": 1 } ], "index_web_pages_for_search": 1, "istable": 1, "links": [], - "modified": "2022-11-09 01:37:56.563068", + "modified": "2023-06-09 14:03:01.612909", "modified_by": "Administrator", "module": "Manufacturing", "name": "Work Order Operation", diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.js b/erpnext/stock/doctype/stock_entry/stock_entry.js index a82c709b60fd..bc5ac2addb0f 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.js +++ b/erpnext/stock/doctype/stock_entry/stock_entry.js @@ -677,6 +677,21 @@ frappe.ui.form.on('Stock Entry', { }); } }, + + process_loss_qty(frm) { + if (frm.doc.process_loss_qty) { + frm.doc.process_loss_percentage = flt(frm.doc.process_loss_qty / frm.doc.fg_completed_qty * 100, precision("process_loss_qty", frm.doc)); + refresh_field("process_loss_percentage"); + } + }, + + process_loss_percentage(frm) { + debugger + if (frm.doc.process_loss_percentage) { + frm.doc.process_loss_qty = flt((frm.doc.fg_completed_qty * frm.doc.process_loss_percentage) / 100 , precision("process_loss_qty", frm.doc)); + refresh_field("process_loss_qty"); + } + } }); frappe.ui.form.on('Stock Entry Detail', { diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.json b/erpnext/stock/doctype/stock_entry/stock_entry.json index bc5533fd2de3..9bf679b8955f 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.json +++ b/erpnext/stock/doctype/stock_entry/stock_entry.json @@ -24,6 +24,7 @@ "company", "posting_date", "posting_time", + "column_break_eaoa", "set_posting_time", "inspection_required", "apply_putaway_rule", @@ -640,16 +641,16 @@ }, { "collapsible": 1, + "depends_on": "eval: doc.fg_completed_qty > 0 && in_list([\"Manufacture\", \"Repack\"], doc.purpose)", "fieldname": "section_break_7qsm", "fieldtype": "Section Break", "label": "Process Loss" }, { - "depends_on": "process_loss_percentage", + "depends_on": "eval: doc.fg_completed_qty > 0 && in_list([\"Manufacture\", \"Repack\"], doc.purpose)", "fieldname": "process_loss_qty", "fieldtype": "Float", - "label": "Process Loss Qty", - "read_only": 1 + "label": "Process Loss Qty" }, { "fieldname": "column_break_e92r", @@ -657,8 +658,6 @@ }, { "depends_on": "eval:doc.from_bom && doc.fg_completed_qty", - "fetch_from": "bom_no.process_loss_percentage", - "fetch_if_empty": 1, "fieldname": "process_loss_percentage", "fieldtype": "Percent", "label": "% Process Loss" @@ -667,6 +666,10 @@ "fieldname": "items_section", "fieldtype": "Section Break", "label": "Items" + }, + { + "fieldname": "column_break_eaoa", + "fieldtype": "Column Break" } ], "icon": "fa fa-file-text", @@ -674,7 +677,7 @@ "index_web_pages_for_search": 1, "is_submittable": 1, "links": [], - "modified": "2023-04-06 12:42:56.673180", + "modified": "2023-06-09 15:46:28.418339", "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 efadf36199c8..3f5457c633ed 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.py +++ b/erpnext/stock/doctype/stock_entry/stock_entry.py @@ -455,13 +455,16 @@ def validate_fg_completed_qty(self): if self.purpose == "Manufacture" and self.work_order: for d in self.items: if d.is_finished_item: + if self.process_loss_qty: + d.qty = self.fg_completed_qty - self.process_loss_qty + item_wise_qty.setdefault(d.item_code, []).append(d.qty) precision = frappe.get_precision("Stock Entry Detail", "qty") for item_code, qty_list in item_wise_qty.items(): total = flt(sum(qty_list), precision) - if (self.fg_completed_qty - total) > 0: + if (self.fg_completed_qty - total) > 0 and not self.process_loss_qty: self.process_loss_qty = flt(self.fg_completed_qty - total, precision) self.process_loss_percentage = flt(self.process_loss_qty * 100 / self.fg_completed_qty) @@ -591,7 +594,9 @@ def check_if_operations_completed(self): for d in prod_order.get("operations"): total_completed_qty = flt(self.fg_completed_qty) + flt(prod_order.produced_qty) - completed_qty = d.completed_qty + (allowance_percentage / 100 * d.completed_qty) + completed_qty = ( + d.completed_qty + d.process_loss_qty + (allowance_percentage / 100 * d.completed_qty) + ) if total_completed_qty > flt(completed_qty): job_card = frappe.db.get_value("Job Card", {"operation_id": d.name}, "name") if not job_card: @@ -1573,16 +1578,36 @@ def set_process_loss_qty(self): if self.purpose not in ("Manufacture", "Repack"): return - self.process_loss_qty = 0.0 - if not self.process_loss_percentage: + precision = self.precision("process_loss_qty") + if self.work_order: + data = frappe.get_all( + "Work Order Operation", + filters={"parent": self.work_order}, + fields=["max(process_loss_qty) as process_loss_qty"], + ) + + if data and data[0].process_loss_qty is not None: + process_loss_qty = data[0].process_loss_qty + if flt(self.process_loss_qty, precision) != flt(process_loss_qty, precision): + self.process_loss_qty = flt(process_loss_qty, precision) + + frappe.msgprint( + _("The Process Loss Qty has reset as per job cards Process Loss Qty"), alert=True + ) + + if not self.process_loss_percentage and not self.process_loss_qty: self.process_loss_percentage = frappe.get_cached_value( "BOM", self.bom_no, "process_loss_percentage" ) - if self.process_loss_percentage: + if self.process_loss_percentage and not self.process_loss_qty: self.process_loss_qty = flt( (flt(self.fg_completed_qty) * flt(self.process_loss_percentage)) / 100 ) + elif self.process_loss_qty and not self.process_loss_percentage: + self.process_loss_percentage = flt( + (flt(self.process_loss_qty) / flt(self.fg_completed_qty)) * 100 + ) def set_work_order_details(self): if not getattr(self, "pro_doc", None):