From 553336fa6fdb22176433c9ff22f4ba53f92babc6 Mon Sep 17 00:00:00 2001 From: Mihir Kandoi Date: Thu, 23 Jul 2026 13:07:51 +0530 Subject: [PATCH] fix: resolve or require operation_id server-side --- .../doctype/job_card/job_card.py | 20 ++++++++++++++ .../doctype/job_card/test_job_card.py | 27 +++++++++++++++++++ 2 files changed, 47 insertions(+) diff --git a/erpnext/manufacturing/doctype/job_card/job_card.py b/erpnext/manufacturing/doctype/job_card/job_card.py index d69d8346f1d..4437a0d89e7 100644 --- a/erpnext/manufacturing/doctype/job_card/job_card.py +++ b/erpnext/manufacturing/doctype/job_card/job_card.py @@ -163,6 +163,7 @@ class JobCard(Document): self.validate_time_logs() self.validate_on_hold() self.set_status() + self.set_operation_id() self.validate_sequence_id() self.set_sub_operations() self.update_sub_operation_status() @@ -1338,6 +1339,25 @@ class JobCard(Document): if not self.wip_warehouse: self.wip_warehouse = frappe.get_cached_value("Company", self.company, "default_wip_warehouse") + def set_operation_id(self): + if self.operation_id or not (self.work_order and self.operation): + return + + operation_rows = frappe.get_all( + "Work Order Operation", + filters={"parent": self.work_order, "operation": self.operation}, + pluck="name", + ) + + if len(operation_rows) == 1: + self.operation_id = operation_rows[0] + elif operation_rows and self.docstatus == 0: + frappe.throw( + _( + "Operation {0} is added multiple times in the work order {1}. Please select the operation row." + ).format(bold(self.operation), get_link_to_form("Work Order", self.work_order)) + ) + @frappe.whitelist() def pause_job(self, **kwargs): frappe.has_permission("Job Card", "write", doc=self, throw=True) diff --git a/erpnext/manufacturing/doctype/job_card/test_job_card.py b/erpnext/manufacturing/doctype/job_card/test_job_card.py index 9acbe89c34e..32f84c9943a 100644 --- a/erpnext/manufacturing/doctype/job_card/test_job_card.py +++ b/erpnext/manufacturing/doctype/job_card/test_job_card.py @@ -148,6 +148,33 @@ class TestJobCard(ERPNextTestSuite): ) self.assertRaises(frappe.ValidationError, job_card_doc.submit) + def test_set_operation_id(self): + work_order = make_wo_order_test_record(item="_Test FG Item 2", qty=2, do_not_submit=1) + operation_row = work_order.operations[0] + + job_card = frappe.new_doc("Job Card") + job_card.work_order = work_order.name + job_card.operation = operation_row.operation + job_card.set_operation_id() + self.assertEqual(job_card.operation_id, operation_row.name) + + work_order.append( + "operations", + { + "operation": operation_row.operation, + "workstation": operation_row.workstation, + "time_in_mins": operation_row.time_in_mins, + "hour_rate": operation_row.hour_rate, + "sequence_id": work_order.operations[-1].sequence_id, + }, + ) + work_order.save() + + job_card = frappe.new_doc("Job Card") + job_card.work_order = work_order.name + job_card.operation = operation_row.operation + self.assertRaises(frappe.ValidationError, job_card.set_operation_id) + def test_job_card_with_different_work_station(self): job_cards = frappe.get_all( "Job Card",