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)
+