fix: user created manual job card not linking job card operations with work order operations
diff --git a/erpnext/manufacturing/doctype/job_card/job_card.js b/erpnext/manufacturing/doctype/job_card/job_card.js
index bab0dfb..7ac0b11 100644
--- a/erpnext/manufacturing/doctype/job_card/job_card.js
+++ b/erpnext/manufacturing/doctype/job_card/job_card.js
@@ -2,6 +2,17 @@
// For license information, please see license.txt
frappe.ui.form.on('Job Card', {
+ setup: function(frm) {
+ frm.set_query('operation', function() {
+ return {
+ query: 'erpnext.manufacturing.doctype.job_card.job_card.get_operations',
+ filters: {
+ 'work_order': frm.doc.work_order
+ }
+ };
+ });
+ },
+
refresh: function(frm) {
frappe.flags.pause_job = 0;
frappe.flags.resume_job = 0;
@@ -20,12 +31,60 @@
}
}
+ frm.trigger("toggle_operation_number");
+
if (frm.doc.docstatus == 0 && (frm.doc.for_quantity > frm.doc.total_completed_qty || !frm.doc.for_quantity)
- && (!frm.doc.items.length || frm.doc.for_quantity == frm.doc.transferred_qty)) {
+ && (!frm.doc.items || !frm.doc.items.length || frm.doc.for_quantity == frm.doc.transferred_qty)) {
frm.trigger("prepare_timer_buttons");
}
},
+ operation: function(frm) {
+ frm.trigger("toggle_operation_number");
+
+ if (frm.doc.operation && frm.doc.work_order) {
+ frappe.call({
+ method: "erpnext.manufacturing.doctype.job_card.job_card.get_operation_details",
+ args: {
+ "work_order":frm.doc.work_order,
+ "operation":frm.doc.operation
+ },
+ callback: function (r) {
+ if (r.message) {
+ if (r.message.length == 1) {
+ frm.set_value("operation_id", r.message[0].name);
+ } else {
+ let args = [];
+
+ r.message.forEach((row) => {
+ args.push({ "label": row.idx, "value": row.name });
+ });
+
+ let description = __("Operation {0} added multiple times in the work order {1}",
+ [frm.doc.operation, frm.doc.work_order]);
+
+ frm.set_df_property("operation_row_number", "options", args);
+ frm.set_df_property("operation_row_number", "description", description);
+ }
+
+ frm.trigger("toggle_operation_number");
+ }
+ }
+ })
+ }
+ },
+
+ operation_row_number(frm) {
+ if (frm.doc.operation_row_number) {
+ frm.set_value("operation_id", frm.doc.operation_row_number);
+ }
+ },
+
+ toggle_operation_number(frm) {
+ frm.toggle_display("operation_row_number", !frm.doc.operation_id && frm.doc.operation);
+ frm.toggle_reqd("operation_row_number", !frm.doc.operation_id && frm.doc.operation);
+ },
+
prepare_timer_buttons: function(frm) {
frm.trigger("make_dashboard");
if (!frm.doc.job_started) {
diff --git a/erpnext/manufacturing/doctype/job_card/job_card.json b/erpnext/manufacturing/doctype/job_card/job_card.json
index fba670c..087ab6b 100644
--- a/erpnext/manufacturing/doctype/job_card/job_card.json
+++ b/erpnext/manufacturing/doctype/job_card/job_card.json
@@ -11,6 +11,7 @@
"bom_no",
"workstation",
"operation",
+ "operation_row_number",
"column_break_4",
"posting_date",
"company",
@@ -291,11 +292,15 @@
"no_copy": 1,
"print_hide": 1,
"read_only": 1
+ },
+ {
+ "fieldname": "operation_row_number",
+ "fieldtype": "Select",
+ "label": "Operation Row Number"
}
],
"is_submittable": 1,
- "links": [],
- "modified": "2020-04-20 15:14:00.273441",
+ "modified": "2020-08-24 15:21:21.398267",
"modified_by": "Administrator",
"module": "Manufacturing",
"name": "Job Card",
@@ -347,7 +352,6 @@
"write": 1
}
],
- "quick_entry": 1,
"sort_field": "modified",
"sort_order": "DESC",
"title_field": "operation",
diff --git a/erpnext/manufacturing/doctype/job_card/job_card.py b/erpnext/manufacturing/doctype/job_card/job_card.py
index 8dd90e6..a87e6e5 100644
--- a/erpnext/manufacturing/doctype/job_card/job_card.py
+++ b/erpnext/manufacturing/doctype/job_card/job_card.py
@@ -15,10 +15,13 @@
class OverlapError(frappe.ValidationError): pass
+class OperationMismatchError(frappe.ValidationError): pass
+
class JobCard(Document):
def validate(self):
self.validate_time_logs()
self.set_status()
+ self.validate_operation_id()
def validate_time_logs(self):
self.total_completed_qty = 0.0
@@ -306,6 +309,37 @@
if update_status:
self.db_set('status', self.status)
+ def validate_operation_id(self):
+ if (self.get("operation_id") and self.get("operation_row_number") and self.operation and self.work_order and
+ frappe.get_cached_value("Work Order Operation", self.operation_row_number, "name") != self.operation_id):
+ work_order = frappe.bold(get_link_to_form("Work Order", self.work_order))
+ frappe.throw(_("Operation {0} does not belong to the work order {1}")
+ .format(frappe.bold(self.operation), work_order), OperationMismatchError)
+
+@frappe.whitelist()
+def get_operation_details(work_order, operation):
+ if work_order and operation:
+ return frappe.get_all("Work Order Operation", fields = ["name", "idx"],
+ filters = {
+ "parent": work_order,
+ "operation": operation
+ }
+ )
+
+@frappe.whitelist()
+def get_operations(doctype, txt, searchfield, start, page_len, filters):
+ if filters.get("work_order"):
+ args = {"parent": filters.get("work_order")}
+ if txt:
+ args["operation"] = ("like", "%{0}%".format(txt))
+
+ return frappe.get_all("Work Order Operation",
+ filters = args,
+ fields = ["distinct operation as operation"],
+ limit_start = start,
+ limit_page_length = page_len,
+ order_by="idx asc", as_list=1)
+
@frappe.whitelist()
def make_material_request(source_name, target_doc=None):
def update_item(obj, target, source_parent):
diff --git a/erpnext/manufacturing/doctype/job_card/test_job_card.py b/erpnext/manufacturing/doctype/job_card/test_job_card.py
index ca05fea..2a6c35f 100644
--- a/erpnext/manufacturing/doctype/job_card/test_job_card.py
+++ b/erpnext/manufacturing/doctype/job_card/test_job_card.py
@@ -4,6 +4,28 @@
from __future__ import unicode_literals
import unittest
+import frappe
+from erpnext.manufacturing.doctype.work_order.test_work_order import make_wo_order_test_record
+from erpnext.manufacturing.doctype.job_card.job_card import OperationMismatchError
class TestJobCard(unittest.TestCase):
- pass
+ def test_job_card(self):
+ data = frappe.get_cached_value('BOM',
+ {'docstatus': 1, 'with_operations': 1, 'company': '_Test Company'}, ['name', 'item'])
+
+ if data:
+ bom, bom_item = data
+
+ work_order = make_wo_order_test_record(item=bom_item, qty=1, bom_no=bom)
+
+ job_cards = frappe.get_all('Job Card',
+ filters = {'work_order': work_order.name}, fields = ["operation_id", "name"])
+
+ if job_cards:
+ job_card = job_cards[0]
+ frappe.db.set_value("Job Card", job_card.name, "operation_row_number", job_card.operation_id)
+
+ doc = frappe.get_doc("Job Card", job_card.name)
+ doc.operation_id = "Test Data"
+ self.assertRaises(OperationMismatchError, doc.save)
+