Merge pull request #23439 from Mangesh-Khairnar/mpesa-integration

feat: M-pesa integration
diff --git a/erpnext/accounts/doctype/accounts_settings/accounts_settings.json b/erpnext/accounts/doctype/accounts_settings/accounts_settings.json
index 4d3cbea..41f9ce0 100644
--- a/erpnext/accounts/doctype/accounts_settings/accounts_settings.json
+++ b/erpnext/accounts/doctype/accounts_settings/accounts_settings.json
@@ -40,7 +40,7 @@
  "fields": [
   {
    "default": "1",
-   "description": "If enabled, the system will post accounting entries for inventory automatically.",
+   "description": "If enabled, the system will post accounting entries for inventory automatically",
    "fieldname": "auto_accounting_for_stock",
    "fieldtype": "Check",
    "hidden": 1,
@@ -48,23 +48,23 @@
    "label": "Make Accounting Entry For Every Stock Movement"
   },
   {
-   "description": "Accounting entry frozen up to this date, nobody can do / modify entry except role specified below.",
+   "description": "Accounting entries are frozen up to this date. Nobody can create or modify entries except users with the role specified below",
    "fieldname": "acc_frozen_upto",
    "fieldtype": "Date",
    "in_list_view": 1,
-   "label": "Accounts Frozen Upto"
+   "label": "Accounts Frozen Till Date"
   },
   {
    "description": "Users with this role are allowed to set frozen accounts and create / modify accounting entries against frozen accounts",
    "fieldname": "frozen_accounts_modifier",
    "fieldtype": "Link",
    "in_list_view": 1,
-   "label": "Role Allowed to Set Frozen Accounts & Edit Frozen Entries",
+   "label": "Role Allowed to Set Frozen Accounts and Edit Frozen Entries",
    "options": "Role"
   },
   {
    "default": "Billing Address",
-   "description": "Address used to determine Tax Category in transactions.",
+   "description": "Address used to determine Tax Category in transactions",
    "fieldname": "determine_address_tax_category_from",
    "fieldtype": "Select",
    "label": "Determine Address Tax Category From",
@@ -75,7 +75,7 @@
    "fieldtype": "Column Break"
   },
   {
-   "description": "Role that is allowed to submit transactions that exceed credit limits set.",
+   "description": "This role is allowed to submit transactions that exceed credit limits",
    "fieldname": "credit_controller",
    "fieldtype": "Link",
    "in_list_view": 1,
@@ -127,7 +127,7 @@
    "default": "0",
    "fieldname": "show_inclusive_tax_in_print",
    "fieldtype": "Check",
-   "label": "Show Inclusive Tax In Print"
+   "label": "Show Inclusive Tax in Print"
   },
   {
    "fieldname": "column_break_12",
@@ -165,7 +165,7 @@
   },
   {
    "default": "0",
-   "description": "Only select if you have setup Cash Flow Mapper documents",
+   "description": "Only select this if you have set up the Cash Flow Mapper documents",
    "fieldname": "use_custom_cash_flow",
    "fieldtype": "Check",
    "label": "Use Custom Cash Flow Format"
@@ -177,7 +177,7 @@
    "label": "Automatically Fetch Payment Terms"
   },
   {
-   "description": "Percentage you are allowed to bill more against the amount ordered. For example: If the order value is $100 for an item and tolerance is set as 10% then you are allowed to bill for $110.",
+   "description": "The percentage you are allowed to bill more against the amount ordered. For example, if the order value is $100 for an item and tolerance is set as 10%, then you are allowed to bill up to $110 ",
    "fieldname": "over_billing_allowance",
    "fieldtype": "Currency",
    "label": "Over Billing Allowance (%)"
@@ -199,7 +199,7 @@
   },
   {
    "default": "0",
-   "description": "If this is unchecked direct GL Entries will be created to book Deferred Revenue/Expense",
+   "description": "If this is unchecked, direct GL entries will be created to book deferred revenue or expense",
    "fieldname": "book_deferred_entries_via_journal_entry",
    "fieldtype": "Check",
    "label": "Book Deferred Entries Via Journal Entry"
@@ -214,7 +214,7 @@
   },
   {
    "default": "Days",
-   "description": "If \"Months\" is selected then fixed amount will be booked as deferred revenue or expense for each month irrespective of number of days in a month. Will be prorated if deferred revenue or expense is not booked for an entire month.",
+   "description": "If \"Months\" is selected, a fixed amount will be booked as deferred revenue or expense for each month irrespective of the number of days in a month. It will be prorated if deferred revenue or expense is not booked for an entire month",
    "fieldname": "book_deferred_entries_based_on",
    "fieldtype": "Select",
    "label": "Book Deferred Entries Based On",
@@ -226,7 +226,7 @@
  "index_web_pages_for_search": 1,
  "issingle": 1,
  "links": [],
- "modified": "2020-10-07 14:58:50.325577",
+ "modified": "2020-10-13 11:32:52.268826",
  "modified_by": "Administrator",
  "module": "Accounts",
  "name": "Accounts Settings",
@@ -254,4 +254,4 @@
  "sort_field": "modified",
  "sort_order": "ASC",
  "track_changes": 1
-}
\ No newline at end of file
+}
diff --git a/erpnext/accounts/doctype/cashier_closing/cashier_closing.py b/erpnext/accounts/doctype/cashier_closing/cashier_closing.py
index 6de62ee..7ad1d3a 100644
--- a/erpnext/accounts/doctype/cashier_closing/cashier_closing.py
+++ b/erpnext/accounts/doctype/cashier_closing/cashier_closing.py
@@ -23,13 +23,13 @@
 			where posting_date=%s and posting_time>=%s and posting_time<=%s and owner=%s
 		""", (self.date, self.from_time, self.time, self.user))
 		self.outstanding_amount = flt(values[0][0] if values else 0)
-			
+
 	def make_calculations(self):
 		total = 0.00
 		for i in self.payments:
 			total += flt(i.amount)
 
-		self.net_amount = total + self.outstanding_amount + self.expense - self.custody + self.returns
+		self.net_amount = total + self.outstanding_amount + flt(self.expense) - flt(self.custody) + flt(self.returns)
 
 	def validate_time(self):
 		if self.from_time >= self.time:
diff --git a/erpnext/accounts/doctype/opening_invoice_creation_tool/opening_invoice_creation_tool.js b/erpnext/accounts/doctype/opening_invoice_creation_tool/opening_invoice_creation_tool.js
index 699eb08..3ce5701 100644
--- a/erpnext/accounts/doctype/opening_invoice_creation_tool/opening_invoice_creation_tool.js
+++ b/erpnext/accounts/doctype/opening_invoice_creation_tool/opening_invoice_creation_tool.js
@@ -6,7 +6,7 @@
 		frm.set_query('party_type', 'invoices', function(doc, cdt, cdn) {
 			return {
 				filters: {
-					'name': ['in', 'Customer,Supplier']
+					'name': ['in', 'Customer, Supplier']
 				}
 			};
 		});
@@ -14,29 +14,46 @@
 		if (frm.doc.company) {
 			frm.trigger('setup_company_filters');
 		}
+
+		frappe.realtime.on('opening_invoice_creation_progress', data => {
+			if (!frm.doc.import_in_progress) {
+				frm.dashboard.reset();
+				frm.doc.import_in_progress = true;
+			}
+			if (data.user != frappe.session.user) return;
+			if (data.count == data.total) {
+				setTimeout((title) => {
+					frm.doc.import_in_progress = false;
+					frm.clear_table("invoices");
+					frm.refresh_fields();
+					frm.page.clear_indicator();
+					frm.dashboard.hide_progress(title);
+					frappe.msgprint(__("Opening {0} Invoice created", [frm.doc.invoice_type]));
+				}, 1500, data.title);
+				return;
+			}
+
+			frm.dashboard.show_progress(data.title, (data.count / data.total) * 100, data.message);
+			frm.page.set_indicator(__('In Progress'), 'orange');
+		});
 	},
 
 	refresh: function(frm) {
 		frm.disable_save();
-		frm.trigger("make_dashboard");
+		!frm.doc.import_in_progress && frm.trigger("make_dashboard");
 		frm.page.set_primary_action(__('Create Invoices'), () => {
 			let btn_primary = frm.page.btn_primary.get(0);
 			return frm.call({
 				doc: frm.doc,
-				freeze: true,
 				btn: $(btn_primary),
 				method: "make_invoices",
-				freeze_message: __("Creating {0} Invoice", [frm.doc.invoice_type]),
-				callback: (r) => {
-					if(!r.exc){
-						frappe.msgprint(__("Opening {0} Invoice created", [frm.doc.invoice_type]));
-						frm.clear_table("invoices");
-						frm.refresh_fields();
-						frm.reload_doc();
-					}
-				}
+				freeze_message: __("Creating {0} Invoice", [frm.doc.invoice_type])
 			});
 		});
+
+		if (frm.doc.create_missing_party) {
+			frm.set_df_property("party", "fieldtype", "Data", frm.doc.name, "invoices");
+		}
 	},
 
 	setup_company_filters: function(frm) {
diff --git a/erpnext/accounts/doctype/opening_invoice_creation_tool/opening_invoice_creation_tool.py b/erpnext/accounts/doctype/opening_invoice_creation_tool/opening_invoice_creation_tool.py
index a53417e..d51856a 100644
--- a/erpnext/accounts/doctype/opening_invoice_creation_tool/opening_invoice_creation_tool.py
+++ b/erpnext/accounts/doctype/opening_invoice_creation_tool/opening_invoice_creation_tool.py
@@ -4,9 +4,12 @@
 
 from __future__ import unicode_literals
 import frappe
+import traceback
+from json import dumps
 from frappe import _, scrub
 from frappe.utils import flt, nowdate
 from frappe.model.document import Document
+from frappe.utils.background_jobs import enqueue
 from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import get_accounting_dimensions
 
 
@@ -61,67 +64,48 @@
 			prepare_invoice_summary(doctype, invoices)
 
 		return invoices_summary, max_count
-
-	def make_invoices(self):
-		names = []
-		mandatory_error_msg = _("Row {0}: {1} is required to create the Opening {2} Invoices")
+	
+	def validate_company(self):
 		if not self.company:
 			frappe.throw(_("Please select the Company"))
+	
+	def set_missing_values(self, row):
+		row.qty = row.qty or 1.0
+		row.temporary_opening_account = row.temporary_opening_account or get_temporary_opening_account(self.company)
+		row.party_type = "Customer" if self.invoice_type == "Sales" else "Supplier"
+		row.item_name = row.item_name or _("Opening Invoice Item")
+		row.posting_date = row.posting_date or nowdate()
+		row.due_date = row.due_date or nowdate()
 
-		company_details = frappe.get_cached_value('Company', self.company,
-			["default_currency", "default_letter_head"], as_dict=1) or {}
+	def validate_mandatory_invoice_fields(self, row):
+		if not frappe.db.exists(row.party_type, row.party):
+			if self.create_missing_party:
+				self.add_party(row.party_type, row.party)
+			else:
+				frappe.throw(_("Row #{}: {} {} does not exist.").format(row.idx, frappe.bold(row.party_type), frappe.bold(row.party)))
 
+		mandatory_error_msg = _("Row #{0}: {1} is required to create the Opening {2} Invoices")
+		for d in ("Party", "Outstanding Amount", "Temporary Opening Account"):
+			if not row.get(scrub(d)):
+				frappe.throw(mandatory_error_msg.format(row.idx, d, self.invoice_type))
+
+	def get_invoices(self):
+		invoices = []
 		for row in self.invoices:
-			if not row.qty:
-				row.qty = 1.0
-
-			# always mandatory fields for the invoices
-			if not row.temporary_opening_account:
-				row.temporary_opening_account = get_temporary_opening_account(self.company)
-			row.party_type = "Customer" if self.invoice_type == "Sales" else "Supplier"
-
-			# Allow to create invoice even if no party present in customer or supplier.
-			if not frappe.db.exists(row.party_type, row.party):
-				if self.create_missing_party:
-					self.add_party(row.party_type, row.party)
-				else:
-					frappe.throw(_("{0} {1} does not exist.").format(frappe.bold(row.party_type), frappe.bold(row.party)))
-
-			if not row.item_name:
-				row.item_name = _("Opening Invoice Item")
-			if not row.posting_date:
-				row.posting_date = nowdate()
-			if not row.due_date:
-				row.due_date = nowdate()
-
-			for d in ("Party", "Outstanding Amount", "Temporary Opening Account"):
-				if not row.get(scrub(d)):
-					frappe.throw(mandatory_error_msg.format(row.idx, _(d), self.invoice_type))
-
-			args = self.get_invoice_dict(row=row)
-			if not args:
+			if not row:
 				continue
-
+			self.set_missing_values(row)
+			self.validate_mandatory_invoice_fields(row)
+			invoice = self.get_invoice_dict(row)
+			company_details = frappe.get_cached_value('Company', self.company, ["default_currency", "default_letter_head"], as_dict=1) or {}
 			if company_details:
-				args.update({
+				invoice.update({
 					"currency": company_details.get("default_currency"),
 					"letter_head": company_details.get("default_letter_head")
 				})
+			invoices.append(invoice)
 
-			doc = frappe.get_doc(args).insert()
-			doc.submit()
-			names.append(doc.name)
-
-			if len(self.invoices) > 5:
-				frappe.publish_realtime(
-					"progress", dict(
-						progress=[row.idx, len(self.invoices)],
-						title=_('Creating {0}').format(doc.doctype)
-					),
-					user=frappe.session.user
-				)
-
-		return names
+		return invoices
 
 	def add_party(self, party_type, party):
 		party_doc = frappe.new_doc(party_type)
@@ -140,14 +124,12 @@
 
 	def get_invoice_dict(self, row=None):
 		def get_item_dict():
-			default_uom = frappe.db.get_single_value("Stock Settings", "stock_uom") or _("Nos")
-			cost_center = row.get('cost_center') or frappe.get_cached_value('Company',
-				self.company,  "cost_center")
-
+			cost_center = row.get('cost_center') or frappe.get_cached_value('Company', self.company,  "cost_center")
 			if not cost_center:
-				frappe.throw(
-					_("Please set the Default Cost Center in {0} company.").format(frappe.bold(self.company))
-				)
+				frappe.throw(_("Please set the Default Cost Center in {0} company.").format(frappe.bold(self.company)))
+
+			income_expense_account_field = "income_account" if row.party_type == "Customer" else "expense_account"
+			default_uom = frappe.db.get_single_value("Stock Settings", "stock_uom") or _("Nos")
 			rate = flt(row.outstanding_amount) / flt(row.qty)
 
 			return frappe._dict({
@@ -161,18 +143,9 @@
 				"cost_center": cost_center
 			})
 
-		if not row:
-			return None
-
-		party_type = "Customer"
-		income_expense_account_field = "income_account"
-		if self.invoice_type == "Purchase":
-			party_type = "Supplier"
-			income_expense_account_field = "expense_account"
-
 		item = get_item_dict()
 
-		args = frappe._dict({
+		invoice = frappe._dict({
 			"items": [item],
 			"is_opening": "Yes",
 			"set_posting_time": 1,
@@ -180,21 +153,76 @@
 			"cost_center": self.cost_center,
 			"due_date": row.due_date,
 			"posting_date": row.posting_date,
-			frappe.scrub(party_type): row.party,
+			frappe.scrub(row.party_type): row.party,
+			"is_pos": 0,
 			"doctype": "Sales Invoice" if self.invoice_type == "Sales" else "Purchase Invoice"
 		})
 
 		accounting_dimension = get_accounting_dimensions()
-
 		for dimension in accounting_dimension:
-			args.update({
+			invoice.update({
 				dimension: item.get(dimension)
 			})
 
-		if self.invoice_type == "Sales":
-			args["is_pos"] = 0
+		return invoice
 
-		return args
+	def make_invoices(self):
+		self.validate_company()
+		invoices = self.get_invoices()
+		if len(invoices) < 50:
+			return start_import(invoices)
+		else:
+			from frappe.core.page.background_jobs.background_jobs import get_info
+			from frappe.utils.scheduler import is_scheduler_inactive
+
+			if is_scheduler_inactive() and not frappe.flags.in_test:
+				frappe.throw(_("Scheduler is inactive. Cannot import data."), title=_("Scheduler Inactive"))
+
+			enqueued_jobs = [d.get("job_name") for d in get_info()]
+			if self.name not in enqueued_jobs:
+				enqueue(
+					start_import,
+					queue="default",
+					timeout=6000,
+					event="opening_invoice_creation",
+					job_name=self.name,
+					invoices=invoices,
+					now=frappe.conf.developer_mode or frappe.flags.in_test
+				)
+
+def start_import(invoices):
+	errors = 0
+	names = []
+	for idx, d in enumerate(invoices):
+		try:
+			publish(idx, len(invoices), d.doctype)
+			doc = frappe.get_doc(d)
+			doc.insert()
+			doc.submit()
+			frappe.db.commit()
+			names.append(doc.name)
+		except Exception:
+			errors += 1
+			frappe.db.rollback()
+			message = "\n".join(["Data:", dumps(d, default=str, indent=4), "--" * 50, "\nException:", traceback.format_exc()])
+			frappe.log_error(title="Error while creating Opening Invoice", message=message)
+			frappe.db.commit()
+	if errors:
+		frappe.msgprint(_("You had {} errors while creating opening invoices. Check {} for more details")
+			.format(errors, "<a href='#List/Error Log' class='variant-click'>Error Log</a>"), indicator="red", title=_("Error Occured"))
+	return names
+
+def publish(index, total, doctype):
+	if total < 5: return
+	frappe.publish_realtime(
+		"opening_invoice_creation_progress",
+		dict(
+			title=_("Opening Invoice Creation In Progress"),
+			message=_('Creating {} out of {} {}').format(index + 1, total, doctype),
+			user=frappe.session.user,
+			count=index+1,
+			total=total
+		))
 
 @frappe.whitelist()
 def get_temporary_opening_account(company=None):
diff --git a/erpnext/accounts/doctype/opening_invoice_creation_tool/test_opening_invoice_creation_tool.py b/erpnext/accounts/doctype/opening_invoice_creation_tool/test_opening_invoice_creation_tool.py
index 3bfc10d..54229f5 100644
--- a/erpnext/accounts/doctype/opening_invoice_creation_tool/test_opening_invoice_creation_tool.py
+++ b/erpnext/accounts/doctype/opening_invoice_creation_tool/test_opening_invoice_creation_tool.py
@@ -44,7 +44,7 @@
 			0: ["_Test Supplier", 300, "Overdue"],
 			1: ["_Test Supplier 1", 250, "Overdue"],
 		}
-		self.check_expected_values(invoices, expected_value, invoice_type="Purchase", )
+		self.check_expected_values(invoices, expected_value, "Purchase")
 
 def get_opening_invoice_creation_dict(**args):
 	party = "Customer" if args.get("invoice_type", "Sales") == "Sales" else "Supplier"
diff --git a/erpnext/accounts/page/bank_reconciliation/bank_reconciliation.py b/erpnext/accounts/page/bank_reconciliation/bank_reconciliation.py
index a168cd1..8abe20c 100644
--- a/erpnext/accounts/page/bank_reconciliation/bank_reconciliation.py
+++ b/erpnext/accounts/page/bank_reconciliation/bank_reconciliation.py
@@ -243,7 +243,11 @@
 						continue
 
 				if "reference_no" in am_match and "reference_no" in des_match:
-					if difflib.SequenceMatcher(lambda x: x == " ", am_match["reference_no"], des_match["reference_no"]).ratio() > 70:
+					# Sequence Matcher does not handle None as input
+					am_reference = am_match["reference_no"] or ""
+					des_reference = des_match["reference_no"] or ""
+
+					if difflib.SequenceMatcher(lambda x: x == " ", am_reference, des_reference).ratio() > 70:
 						if am_match not in result:
 							result.append(am_match)
 		if result:
diff --git a/erpnext/buying/doctype/buying_settings/buying_settings.json b/erpnext/buying/doctype/buying_settings/buying_settings.json
index a0ab2a0..618212d 100644
--- a/erpnext/buying/doctype/buying_settings/buying_settings.json
+++ b/erpnext/buying/doctype/buying_settings/buying_settings.json
@@ -46,26 +46,26 @@
   {
    "fieldname": "po_required",
    "fieldtype": "Select",
-   "label": "Purchase Order Required for Purchase Invoice & Receipt Creation",
+   "label": "Is Purchase Order Required for Purchase Invoice & Receipt Creation?",
    "options": "No\nYes"
   },
   {
    "fieldname": "pr_required",
    "fieldtype": "Select",
-   "label": "Purchase Receipt Required for Purchase Invoice Creation",
+   "label": "Is Purchase Receipt Required for Purchase Invoice Creation?",
    "options": "No\nYes"
   },
   {
    "default": "0",
    "fieldname": "maintain_same_rate",
    "fieldtype": "Check",
-   "label": "Maintain same rate throughout purchase cycle"
+   "label": "Maintain Same Rate Throughout the Purchase Cycle"
   },
   {
    "default": "0",
    "fieldname": "allow_multiple_items",
    "fieldtype": "Check",
-   "label": "Allow Item to be added multiple times in a transaction"
+   "label": "Allow Item To Be Added Multiple Times in a Transaction"
   },
   {
    "fieldname": "subcontract",
@@ -93,9 +93,10 @@
  ],
  "icon": "fa fa-cog",
  "idx": 1,
+ "index_web_pages_for_search": 1,
  "issingle": 1,
  "links": [],
- "modified": "2020-05-15 14:49:32.513611",
+ "modified": "2020-10-13 12:00:23.276329",
  "modified_by": "Administrator",
  "module": "Buying",
  "name": "Buying Settings",
@@ -113,4 +114,4 @@
  ],
  "sort_field": "modified",
  "sort_order": "DESC"
-}
\ No newline at end of file
+}
diff --git a/erpnext/hr/doctype/employee/employee.py b/erpnext/hr/doctype/employee/employee.py
index 85eaa5e..dfc600c 100755
--- a/erpnext/hr/doctype/employee/employee.py
+++ b/erpnext/hr/doctype/employee/employee.py
@@ -56,7 +56,7 @@
 			if existing_user_id:
 				remove_user_permission(
 					"Employee", self.name, existing_user_id)
-	
+
 	def after_rename(self, old, new, merge):
 		self.db_set("employee", new)
 
@@ -181,8 +181,11 @@
 			)
 			if reports_to:
 				link_to_employees = [frappe.utils.get_link_to_form('Employee', employee.name, label=employee.employee_name) for employee in reports_to]
-				throw(_("Employee status cannot be set to 'Left' as following employees are currently reporting to this employee:&nbsp;")
-					+ ', '.join(link_to_employees), EmployeeLeftValidationError)
+				message = _("The following employees are currently still reporting to {0}:").format(frappe.bold(self.employee_name))
+				message += "<br><br><ul><li>" + "</li><li>".join(link_to_employees)
+				message += "</li></ul><br>"
+				message += _("Please make sure the employees above report to another Active employee.")
+				throw(message, EmployeeLeftValidationError, _("Cannot Relieve Employee"))
 			if not self.relieving_date:
 				throw(_("Please enter relieving date."))
 
@@ -215,7 +218,7 @@
 
 	def validate_preferred_email(self):
 		if self.prefered_contact_email and not self.get(scrub(self.prefered_contact_email)):
-			frappe.msgprint(_("Please enter " + self.prefered_contact_email))
+			frappe.msgprint(_("Please enter {0}").format(self.prefered_contact_email))
 
 	def validate_onboarding_process(self):
 		employee_onboarding = frappe.get_all("Employee Onboarding",
@@ -417,9 +420,9 @@
 @frappe.whitelist()
 def get_children(doctype, parent=None, company=None, is_root=False, is_tree=False):
 
-	filters = []
+	filters = [['status', '!=', 'Left']]
 	if company and company != 'All Companies':
-		filters = [['company', '=', company]]
+		filters.append(['company', '=', company])
 
 	fields = ['name as value', 'employee_name as title']
 
diff --git a/erpnext/hr/doctype/hr_settings/hr_settings.json b/erpnext/hr/doctype/hr_settings/hr_settings.json
index c42e1d7..4374d29 100644
--- a/erpnext/hr/doctype/hr_settings/hr_settings.json
+++ b/erpnext/hr/doctype/hr_settings/hr_settings.json
@@ -28,144 +28,110 @@
   {
    "fieldname": "employee_settings",
    "fieldtype": "Section Break",
-   "label": "Employee Settings",
-   "show_days": 1,
-   "show_seconds": 1
+   "label": "Employee Settings"
   },
   {
    "description": "Enter retirement age in years",
    "fieldname": "retirement_age",
    "fieldtype": "Data",
-   "label": "Retirement Age",
-   "show_days": 1,
-   "show_seconds": 1
+   "label": "Retirement Age"
   },
   {
    "default": "Naming Series",
-   "description": "Employee record is created using selected field. ",
+   "description": "Employee records are created using the selected field",
    "fieldname": "emp_created_by",
    "fieldtype": "Select",
-   "label": "Employee Records to be created by",
-   "options": "Naming Series\nEmployee Number\nFull Name",
-   "show_days": 1,
-   "show_seconds": 1
+   "label": "Employee Records to Be Created By",
+   "options": "Naming Series\nEmployee Number\nFull Name"
   },
   {
    "fieldname": "column_break_4",
-   "fieldtype": "Column Break",
-   "show_days": 1,
-   "show_seconds": 1
+   "fieldtype": "Column Break"
   },
   {
    "default": "0",
-   "description": "Don't send Employee Birthday Reminders",
+   "description": "Don't send employee birthday reminders",
    "fieldname": "stop_birthday_reminders",
    "fieldtype": "Check",
-   "label": "Stop Birthday Reminders",
-   "show_days": 1,
-   "show_seconds": 1
+   "label": "Stop Birthday Reminders"
   },
   {
    "default": "1",
    "fieldname": "expense_approver_mandatory_in_expense_claim",
    "fieldtype": "Check",
-   "label": "Expense Approver Mandatory In Expense Claim",
-   "show_days": 1,
-   "show_seconds": 1
+   "label": "Expense Approver Mandatory In Expense Claim"
   },
   {
    "collapsible": 1,
    "fieldname": "leave_settings",
    "fieldtype": "Section Break",
-   "label": "Leave Settings",
-   "show_days": 1,
-   "show_seconds": 1
+   "label": "Leave Settings"
   },
   {
    "fieldname": "leave_approval_notification_template",
    "fieldtype": "Link",
    "label": "Leave Approval Notification Template",
-   "options": "Email Template",
-   "show_days": 1,
-   "show_seconds": 1
+   "options": "Email Template"
   },
   {
    "fieldname": "leave_status_notification_template",
    "fieldtype": "Link",
    "label": "Leave Status Notification Template",
-   "options": "Email Template",
-   "show_days": 1,
-   "show_seconds": 1
+   "options": "Email Template"
   },
   {
    "fieldname": "column_break_18",
-   "fieldtype": "Column Break",
-   "show_days": 1,
-   "show_seconds": 1
+   "fieldtype": "Column Break"
   },
   {
    "default": "1",
    "fieldname": "leave_approver_mandatory_in_leave_application",
    "fieldtype": "Check",
-   "label": "Leave Approver Mandatory In Leave Application",
-   "show_days": 1,
-   "show_seconds": 1
+   "label": "Leave Approver Mandatory In Leave Application"
   },
   {
    "default": "0",
    "fieldname": "show_leaves_of_all_department_members_in_calendar",
    "fieldtype": "Check",
-   "label": "Show Leaves Of All Department Members In Calendar",
-   "show_days": 1,
-   "show_seconds": 1
+   "label": "Show Leaves Of All Department Members In Calendar"
   },
   {
    "collapsible": 1,
    "fieldname": "hiring_settings",
    "fieldtype": "Section Break",
-   "label": "Hiring Settings",
-   "show_days": 1,
-   "show_seconds": 1
+   "label": "Hiring Settings"
   },
   {
    "default": "0",
    "fieldname": "check_vacancies",
    "fieldtype": "Check",
-   "label": "Check Vacancies On Job Offer Creation",
-   "show_days": 1,
-   "show_seconds": 1
+   "label": "Check Vacancies On Job Offer Creation"
   },
   {
    "default": "0",
    "fieldname": "auto_leave_encashment",
    "fieldtype": "Check",
-   "label": "Auto Leave Encashment",
-   "show_days": 1,
-   "show_seconds": 1
+   "label": "Auto Leave Encashment"
   },
   {
    "default": "0",
    "fieldname": "restrict_backdated_leave_application",
    "fieldtype": "Check",
-   "label": "Restrict Backdated Leave Application",
-   "show_days": 1,
-   "show_seconds": 1
+   "label": "Restrict Backdated Leave Applications"
   },
   {
    "depends_on": "eval:doc.restrict_backdated_leave_application == 1",
    "fieldname": "role_allowed_to_create_backdated_leave_application",
    "fieldtype": "Link",
    "label": "Role Allowed to Create Backdated Leave Application",
-   "options": "Role",
-   "show_days": 1,
-   "show_seconds": 1
+   "options": "Role"
   }
  ],
  "icon": "fa fa-cog",
  "idx": 1,
  "issingle": 1,
  "links": [],
- "modified": "2020-06-04 15:15:09.865476",
+ "modified": "2020-10-13 11:49:46.168027",
  "modified_by": "Administrator",
  "module": "HR",
  "name": "HR Settings",
@@ -183,4 +149,4 @@
  ],
  "sort_field": "modified",
  "sort_order": "ASC"
-}
\ No newline at end of file
+}
diff --git a/erpnext/manufacturing/doctype/bom/bom.py b/erpnext/manufacturing/doctype/bom/bom.py
index 71d49a9..2ab1b98 100644
--- a/erpnext/manufacturing/doctype/bom/bom.py
+++ b/erpnext/manufacturing/doctype/bom/bom.py
@@ -55,10 +55,11 @@
 			conflicting_bom = frappe.get_doc("BOM", name)
 
 			if conflicting_bom.item != self.item:
+				msg = (_("A BOM with name {0} already exists for item {1}.")
+					.format(frappe.bold(name), frappe.bold(conflicting_bom.item)))
 
-				frappe.throw(_("""A BOM with name {0} already exists for item {1}.
-					<br> Did you rename the item? Please contact Administrator / Tech support
-				""").format(frappe.bold(name), frappe.bold(conflicting_bom.item)))
+				frappe.throw(_("{0}{1} Did you rename the item? Please contact Administrator / Tech support")
+					.format(msg, "<br>"))
 
 		self.name = name
 
@@ -72,6 +73,7 @@
 		self.validate_uom_is_interger()
 		self.set_bom_material_details()
 		self.validate_materials()
+		self.set_routing_operations()
 		self.validate_operations()
 		self.calculate_cost()
 		self.update_cost(update_parent=False, from_child_bom=True, save=False)
@@ -111,18 +113,13 @@
 	def get_routing(self):
 		if self.routing:
 			self.set("operations", [])
-			for d in frappe.get_all("BOM Operation", fields = ["*"],
-				filters = {'parenttype': 'Routing', 'parent': self.routing}, order_by="idx"):
-				child = self.append('operations', {
-					"operation": d.operation,
-					"workstation": d.workstation,
-					"description": d.description,
-					"time_in_mins": d.time_in_mins,
-					"batch_size": d.batch_size,
-					"operating_cost": d.operating_cost,
-					"idx": d.idx
-				})
-				child.hour_rate = flt(d.hour_rate / self.conversion_rate, 2)
+			fields = ["sequence_id", "operation", "workstation", "description",
+				"time_in_mins", "batch_size", "operating_cost", "idx", "hour_rate"]
+
+			for row in frappe.get_all("BOM Operation", fields = fields,
+				filters = {'parenttype': 'Routing', 'parent': self.routing}, order_by="sequence_id, idx"):
+				child = self.append('operations', row)
+				child.hour_rate = flt(row.hour_rate / self.conversion_rate, 2)
 
 	def set_bom_material_details(self):
 		for item in self.get("items"):
@@ -571,6 +568,10 @@
 			if act_pbom and act_pbom[0][0]:
 				frappe.throw(_("Cannot deactivate or cancel BOM as it is linked with other BOMs"))
 
+	def set_routing_operations(self):
+		if self.routing and self.with_operations and not self.operations:
+			self.get_routing()
+
 	def validate_operations(self):
 		if self.with_operations and not self.get('operations'):
 			frappe.throw(_("Operations cannot be left blank"))
diff --git a/erpnext/manufacturing/doctype/bom_operation/bom_operation.json b/erpnext/manufacturing/doctype/bom_operation/bom_operation.json
index 0350e2c..07464e3 100644
--- a/erpnext/manufacturing/doctype/bom_operation/bom_operation.json
+++ b/erpnext/manufacturing/doctype/bom_operation/bom_operation.json
@@ -1,10 +1,12 @@
 {
+ "actions": [],
  "creation": "2013-02-22 01:27:49",
  "doctype": "DocType",
  "document_type": "Setup",
  "editable_grid": 1,
  "engine": "InnoDB",
  "field_order": [
+  "sequence_id",
   "operation",
   "workstation",
   "description",
@@ -106,11 +108,19 @@
    "fieldname": "batch_size",
    "fieldtype": "Int",
    "label": "Batch Size"
+  },
+  {
+   "depends_on": "eval:doc.parenttype == \"Routing\"",
+   "fieldname": "sequence_id",
+   "fieldtype": "Int",
+   "label": "Sequence ID"
   }
  ],
  "idx": 1,
+ "index_web_pages_for_search": 1,
  "istable": 1,
- "modified": "2020-06-16 17:01:11.128420",
+ "links": [],
+ "modified": "2020-10-13 18:14:10.018774",
  "modified_by": "Administrator",
  "module": "Manufacturing",
  "name": "BOM Operation",
diff --git a/erpnext/manufacturing/doctype/job_card/job_card.json b/erpnext/manufacturing/doctype/job_card/job_card.json
index 087ab6b..575e719 100644
--- a/erpnext/manufacturing/doctype/job_card/job_card.json
+++ b/erpnext/manufacturing/doctype/job_card/job_card.json
@@ -36,6 +36,7 @@
   "items",
   "more_information",
   "operation_id",
+  "sequence_id",
   "transferred_qty",
   "requested_qty",
   "column_break_20",
@@ -297,10 +298,18 @@
    "fieldname": "operation_row_number",
    "fieldtype": "Select",
    "label": "Operation Row Number"
+  },
+  {
+   "fieldname": "sequence_id",
+   "fieldtype": "Int",
+   "label": "Sequence Id",
+   "print_hide": 1,
+   "read_only": 1
   }
  ],
  "is_submittable": 1,
- "modified": "2020-08-24 15:21:21.398267",
+ "links": [],
+ "modified": "2020-10-14 12:58:25.327897",
  "modified_by": "Administrator",
  "module": "Manufacturing",
  "name": "Job Card",
diff --git a/erpnext/manufacturing/doctype/job_card/job_card.py b/erpnext/manufacturing/doctype/job_card/job_card.py
index 8855e0a..4dfa78b 100644
--- a/erpnext/manufacturing/doctype/job_card/job_card.py
+++ b/erpnext/manufacturing/doctype/job_card/job_card.py
@@ -5,7 +5,7 @@
 from __future__ import unicode_literals
 import frappe
 import datetime
-from frappe import _
+from frappe import _, bold
 from frappe.model.mapper import get_mapped_doc
 from frappe.model.document import Document
 from frappe.utils import (flt, cint, time_diff_in_hours, get_datetime, getdate,
@@ -16,12 +16,14 @@
 class OverlapError(frappe.ValidationError): pass
 
 class OperationMismatchError(frappe.ValidationError): pass
+class OperationSequenceError(frappe.ValidationError): pass
 
 class JobCard(Document):
 	def validate(self):
 		self.validate_time_logs()
 		self.set_status()
 		self.validate_operation_id()
+		self.validate_sequence_id()
 
 	def validate_time_logs(self):
 		self.total_completed_qty = 0.0
@@ -196,14 +198,14 @@
 	def validate_job_card(self):
 		if not self.time_logs:
 			frappe.throw(_("Time logs are required for {0} {1}")
-				.format(frappe.bold("Job Card"), get_link_to_form("Job Card", self.name)))
+				.format(bold("Job Card"), get_link_to_form("Job Card", self.name)))
 
 		if self.for_quantity and self.total_completed_qty != self.for_quantity:
-			total_completed_qty = frappe.bold(_("Total Completed Qty"))
-			qty_to_manufacture = frappe.bold(_("Qty to Manufacture"))
+			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, frappe.bold(self.total_completed_qty), qty_to_manufacture,frappe.bold(self.for_quantity))))
+			frappe.throw(_("The {0} ({1}) must be equal to {2} ({3})")
+				.format(total_completed_qty, bold(self.total_completed_qty), qty_to_manufacture,bold(self.for_quantity)))
 
 	def update_work_order(self):
 		if not self.work_order:
@@ -213,10 +215,7 @@
 		from_time_list, to_time_list = [], []
 
 		field = "operation_id"
-		data = frappe.get_all('Job Card',
-			fields = ["sum(total_time_in_mins) as time_in_mins", "sum(total_completed_qty) as completed_qty"],
-			filters = {"docstatus": 1, "work_order": self.work_order, field: self.get(field)})
-
+		data = self.get_current_operation_data()
 		if data and len(data) > 0:
 			for_quantity = data[0].completed_qty
 			time_in_mins = data[0].time_in_mins
@@ -246,6 +245,11 @@
 			wo.set_actual_dates()
 			wo.save()
 
+	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"],
+			filters = {"docstatus": 1, "work_order": self.work_order, "operation_id": self.operation_id})
+
 	def set_transferred_qty(self, update_status=False):
 		if not self.items:
 			self.transferred_qty = self.for_quantity if self.docstatus == 1 else 0
@@ -310,9 +314,32 @@
 	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))
+			work_order = 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)
+				.format(bold(self.operation), work_order), OperationMismatchError)
+
+	def validate_sequence_id(self):
+		if not (self.work_order and self.sequence_id): return
+
+		current_operation_qty = 0.0
+		data = self.get_current_operation_data()
+		if data and len(data) > 0:
+			current_operation_qty = flt(data[0].completed_qty)
+
+		current_operation_qty += flt(self.total_completed_qty)
+
+		data = frappe.get_all("Work Order Operation",
+			fields = ["operation", "status", "completed_qty"],
+			filters={"docstatus": 1, "parent": self.work_order, "sequence_id": ('<', self.sequence_id)},
+			order_by = "sequence_id, idx")
+
+		message = "Job Card {0}: As per the sequence of the operations in the work order {1}".format(bold(self.name),
+			bold(get_link_to_form("Work Order", self.work_order)))
+
+		for row in data:
+			if row.status != "Completed" and row.completed_qty < current_operation_qty:
+				frappe.throw(_("{0}, complete the operation {1} before the operation {2}.")
+					.format(message, bold(row.operation), bold(self.operation)), OperationSequenceError)
 
 @frappe.whitelist()
 def get_operation_details(work_order, operation):
diff --git a/erpnext/manufacturing/doctype/manufacturing_settings/manufacturing_settings.json b/erpnext/manufacturing/doctype/manufacturing_settings/manufacturing_settings.json
index 86fa7a8..b7634da 100644
--- a/erpnext/manufacturing/doctype/manufacturing_settings/manufacturing_settings.json
+++ b/erpnext/manufacturing/doctype/manufacturing_settings/manufacturing_settings.json
@@ -1,4 +1,5 @@
 {
+ "actions": [],
  "creation": "2014-11-27 14:12:07.542534",
  "doctype": "DocType",
  "document_type": "Document",
@@ -36,7 +37,7 @@
   {
    "default": "0",
    "depends_on": "eval:!doc.disable_capacity_planning",
-   "description": "Plan time logs outside Workstation Working Hours.",
+   "description": "Plan time logs outside Workstation working hours",
    "fieldname": "allow_overtime",
    "fieldtype": "Check",
    "label": "Allow Overtime"
@@ -56,17 +57,17 @@
   {
    "default": "30",
    "depends_on": "eval:!doc.disable_capacity_planning",
-   "description": "Try planning operations for X days in advance.",
+   "description": "Plan operations X days in advance",
    "fieldname": "capacity_planning_for_days",
    "fieldtype": "Int",
    "label": "Capacity Planning For (Days)"
   },
   {
    "depends_on": "eval:!doc.disable_capacity_planning",
-   "description": "Default 10 mins",
+   "description": "Default: 10 mins",
    "fieldname": "mins_between_operations",
    "fieldtype": "Int",
-   "label": "Time Between Operations (in mins)"
+   "label": "Time Between Operations (Mins)"
   },
   {
    "fieldname": "section_break_6",
@@ -92,14 +93,14 @@
   },
   {
    "default": "0",
-   "description": "Allow multiple Material Consumption against a Work Order",
+   "description": "Allow multiple material consumptions against a Work Order",
    "fieldname": "material_consumption",
    "fieldtype": "Check",
    "label": "Allow Multiple Material Consumption"
   },
   {
    "default": "0",
-   "description": "Update BOM cost automatically via Scheduler, based on latest valuation rate / price list rate / last purchase rate of raw materials.",
+   "description": "Update BOM cost automatically via scheduler, based on the latest Valuation Rate/Price List Rate/Last Purchase Rate of raw materials",
    "fieldname": "update_bom_costs_automatically",
    "fieldtype": "Check",
    "label": "Update BOM Cost Automatically"
@@ -135,7 +136,7 @@
   {
    "fieldname": "over_production_for_sales_and_work_order_section",
    "fieldtype": "Section Break",
-   "label": "Over Production for Sales and Work Order"
+   "label": "Overproduction for Sales and Work Order"
   },
   {
    "fieldname": "raw_materials_consumption_section",
@@ -157,8 +158,10 @@
   }
  ],
  "icon": "icon-wrench",
+ "index_web_pages_for_search": 1,
  "issingle": 1,
- "modified": "2019-11-26 13:10:45.569341",
+ "links": [],
+ "modified": "2020-10-13 10:55:43.996581",
  "modified_by": "Administrator",
  "module": "Manufacturing",
  "name": "Manufacturing Settings",
@@ -175,4 +178,4 @@
  "sort_field": "modified",
  "sort_order": "DESC",
  "track_changes": 1
-}
\ No newline at end of file
+}
diff --git a/erpnext/manufacturing/doctype/operation/test_operation.py b/erpnext/manufacturing/doctype/operation/test_operation.py
index 17d206a..0067231 100644
--- a/erpnext/manufacturing/doctype/operation/test_operation.py
+++ b/erpnext/manufacturing/doctype/operation/test_operation.py
@@ -9,3 +9,23 @@
 
 class TestOperation(unittest.TestCase):
 	pass
+
+def make_operation(*args, **kwargs):
+	args = args if args else kwargs
+	if isinstance(args, tuple):
+		args = args[0]
+
+	args = frappe._dict(args)
+
+	try:
+		doc = frappe.get_doc({
+			"doctype": "Operation",
+			"name": args.operation,
+			"workstation": args.workstation
+		})
+
+		doc.insert()
+
+		return doc
+	except frappe.DuplicateEntryError:
+		return frappe.get_doc("Operation", args.operation)
\ No newline at end of file
diff --git a/erpnext/manufacturing/doctype/production_plan/test_production_plan.py b/erpnext/manufacturing/doctype/production_plan/test_production_plan.py
index d020bc8..fa9d080 100644
--- a/erpnext/manufacturing/doctype/production_plan/test_production_plan.py
+++ b/erpnext/manufacturing/doctype/production_plan/test_production_plan.py
@@ -237,7 +237,9 @@
 		'item': args.item,
 		'currency': args.currency or 'USD',
 		'quantity': args.quantity or 1,
-		'company': args.company or '_Test Company'
+		'company': args.company or '_Test Company',
+		'routing': args.routing,
+		'with_operations': args.with_operations or 0
 	})
 
 	for item in args.raw_materials:
diff --git a/erpnext/manufacturing/doctype/routing/routing.js b/erpnext/manufacturing/doctype/routing/routing.js
index d7589fa..741a3f0 100644
--- a/erpnext/manufacturing/doctype/routing/routing.js
+++ b/erpnext/manufacturing/doctype/routing/routing.js
@@ -2,6 +2,13 @@
 // For license information, please see license.txt
 
 frappe.ui.form.on('Routing', {
+	setup: function(frm) {
+		frappe.meta.get_docfield("BOM Operation", "sequence_id",
+			frm.doc.name).in_list_view = true;
+
+		frm.fields_dict.operations.grid.refresh();
+	},
+
 	calculate_operating_cost: function(frm, child) {
 		const operating_cost = flt(flt(child.hour_rate) * flt(child.time_in_mins) / 60, 2);
 		frappe.model.set_value(child.doctype, child.name, "operating_cost", operating_cost);
diff --git a/erpnext/manufacturing/doctype/routing/routing.py b/erpnext/manufacturing/doctype/routing/routing.py
index ecd0ba8..8312d74 100644
--- a/erpnext/manufacturing/doctype/routing/routing.py
+++ b/erpnext/manufacturing/doctype/routing/routing.py
@@ -3,7 +3,22 @@
 # For license information, please see license.txt
 
 from __future__ import unicode_literals
+import frappe
+from frappe.utils import cint
+from frappe import _
 from frappe.model.document import Document
 
 class Routing(Document):
-	pass
+	def validate(self):
+		self.set_routing_id()
+
+	def set_routing_id(self):
+		sequence_id = 0
+		for row in self.operations:
+			if not row.sequence_id:
+				row.sequence_id = sequence_id + 1
+			elif sequence_id and row.sequence_id and cint(sequence_id) > cint(row.sequence_id):
+				frappe.throw(_("At row #{0}: the sequence id {1} cannot be less than previous row sequence id {2}")
+					.format(row.idx, row.sequence_id, sequence_id))
+
+			sequence_id = row.sequence_id
\ No newline at end of file
diff --git a/erpnext/manufacturing/doctype/routing/test_routing.py b/erpnext/manufacturing/doctype/routing/test_routing.py
index 53ad152..73d05a6 100644
--- a/erpnext/manufacturing/doctype/routing/test_routing.py
+++ b/erpnext/manufacturing/doctype/routing/test_routing.py
@@ -4,6 +4,88 @@
 from __future__ import unicode_literals
 
 import unittest
+import frappe
+from frappe.test_runner import make_test_records
+from erpnext.stock.doctype.item.test_item import make_item
+from erpnext.manufacturing.doctype.operation.test_operation import make_operation
+from erpnext.manufacturing.doctype.job_card.job_card import OperationSequenceError
+from erpnext.manufacturing.doctype.workstation.test_workstation import make_workstation
+from erpnext.manufacturing.doctype.work_order.test_work_order import make_wo_order_test_record
 
 class TestRouting(unittest.TestCase):
-	pass
+	def test_sequence_id(self):
+		item_code = "Test Routing Item - A"
+		operations = [{"operation": "Test Operation A", "workstation": "Test Workstation A", "time_in_mins": 30},
+			{"operation": "Test Operation B", "workstation": "Test Workstation A", "time_in_mins": 20}]
+
+		make_test_records("UOM")
+
+		setup_operations(operations)
+		routing_doc = create_routing(routing_name="Testing Route", operations=operations)
+		bom_doc = setup_bom(item_code=item_code, routing=routing_doc.name)
+		wo_doc = make_wo_order_test_record(production_item = item_code, bom_no=bom_doc.name)
+
+		for row in routing_doc.operations:
+			self.assertEqual(row.sequence_id, row.idx)
+
+		for data in frappe.get_all("Job Card",
+			filters={"work_order": wo_doc.name}, order_by="sequence_id desc"):
+			job_card_doc = frappe.get_doc("Job Card", data.name)
+			job_card_doc.time_logs[0].completed_qty = 10
+			if job_card_doc.sequence_id != 1:
+				self.assertRaises(OperationSequenceError, job_card_doc.save)
+			else:
+				job_card_doc.save()
+				self.assertEqual(job_card_doc.total_completed_qty, 10)
+
+		wo_doc.cancel()
+		wo_doc.delete()
+
+def setup_operations(rows):
+	for row in rows:
+		make_workstation(row)
+		make_operation(row)
+
+def create_routing(**args):
+	args = frappe._dict(args)
+
+	doc = frappe.new_doc("Routing")
+	doc.update(args)
+
+	if not args.do_not_save:
+		try:
+			for operation in args.operations:
+				doc.append("operations", operation)
+
+			doc.insert()
+		except frappe.DuplicateEntryError:
+			doc = frappe.get_doc("Routing", args.routing_name)
+
+	return doc
+
+def setup_bom(**args):
+	from erpnext.manufacturing.doctype.production_plan.test_production_plan import make_bom
+
+	args = frappe._dict(args)
+
+	if not frappe.db.exists('Item', args.item_code):
+		make_item(args.item_code, {
+			'is_stock_item': 1
+		})
+
+	if not args.raw_materials:
+		if not frappe.db.exists('Item', "Test Extra Item 1"):
+			make_item("Test Extra Item N-1", {
+				'is_stock_item': 1,
+			})
+
+		args.raw_materials = ['Test Extra Item N-1']
+
+	name = frappe.db.get_value('BOM', {'item': args.item_code}, 'name')
+	if not name:
+		bom_doc = make_bom(item = args.item_code, raw_materials = args.get("raw_materials"),
+			routing = args.routing, with_operations=1)
+	else:
+		bom_doc = frappe.get_doc("BOM", name)
+
+	return bom_doc
\ No newline at end of file
diff --git a/erpnext/manufacturing/doctype/work_order/test_work_order.py b/erpnext/manufacturing/doctype/work_order/test_work_order.py
index 7010f29..5f8a134 100644
--- a/erpnext/manufacturing/doctype/work_order/test_work_order.py
+++ b/erpnext/manufacturing/doctype/work_order/test_work_order.py
@@ -193,6 +193,42 @@
 		self.assertEqual(cint(bin1_on_end_production.projected_qty),
 			cint(bin1_on_end_production.projected_qty))
 
+	def test_backflush_qty_for_overpduction_manufacture(self):
+		cancel_stock_entry = []
+		allow_overproduction("overproduction_percentage_for_work_order", 30)
+		wo_order = make_wo_order_test_record(planned_start_date=now(), qty=100)
+		ste1 = test_stock_entry.make_stock_entry(item_code="_Test Item",
+			target="_Test Warehouse - _TC", qty=120, basic_rate=5000.0)
+		ste2 = test_stock_entry.make_stock_entry(item_code="_Test Item Home Desktop 100",
+			target="_Test Warehouse - _TC", qty=240, basic_rate=1000.0)
+
+		cancel_stock_entry.extend([ste1.name, ste2.name])
+
+		s = frappe.get_doc(make_stock_entry(wo_order.name, "Material Transfer for Manufacture", 60))
+		s.submit()
+		cancel_stock_entry.append(s.name)
+
+		s = frappe.get_doc(make_stock_entry(wo_order.name, "Manufacture", 60))
+		s.submit()
+		cancel_stock_entry.append(s.name)
+
+		s = frappe.get_doc(make_stock_entry(wo_order.name, "Material Transfer for Manufacture", 60))
+		s.submit()
+		cancel_stock_entry.append(s.name)
+
+		s1 = frappe.get_doc(make_stock_entry(wo_order.name, "Manufacture", 50))
+		s1.submit()
+		cancel_stock_entry.append(s1.name)
+
+		self.assertEqual(s1.items[0].qty, 50)
+		self.assertEqual(s1.items[1].qty, 100)
+		cancel_stock_entry.reverse()
+		for ste in cancel_stock_entry:
+			doc = frappe.get_doc("Stock Entry", ste)
+			doc.cancel()
+
+		allow_overproduction("overproduction_percentage_for_work_order", 0)
+
 	def test_reserved_qty_for_stopped_production(self):
 		test_stock_entry.make_stock_entry(item_code="_Test Item",
 			target= self.warehouse, qty=100, basic_rate=100)
diff --git a/erpnext/manufacturing/doctype/work_order/work_order.py b/erpnext/manufacturing/doctype/work_order/work_order.py
index 7f8341f..cc93bf9 100644
--- a/erpnext/manufacturing/doctype/work_order/work_order.py
+++ b/erpnext/manufacturing/doctype/work_order/work_order.py
@@ -378,7 +378,7 @@
 			select
 				operation, description, workstation, idx,
 				base_hour_rate as hour_rate, time_in_mins,
-				"Pending" as status, parent as bom, batch_size
+				"Pending" as status, parent as bom, batch_size, sequence_id
 			from
 				`tabBOM Operation`
 			where
@@ -865,6 +865,7 @@
 		'bom_no': work_order.bom_no,
 		'project': work_order.project,
 		'company': work_order.company,
+		'sequence_id': row.get("sequence_id"),
 		'wip_warehouse': work_order.wip_warehouse
 	})
 
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 3f5e18e..8c5cde9 100644
--- a/erpnext/manufacturing/doctype/work_order_operation/work_order_operation.json
+++ b/erpnext/manufacturing/doctype/work_order_operation/work_order_operation.json
@@ -8,6 +8,7 @@
   "details",
   "operation",
   "bom",
+  "sequence_id",
   "description",
   "col_break1",
   "completed_qty",
@@ -187,11 +188,19 @@
    "fieldtype": "Int",
    "label": "Batch Size",
    "read_only": 1
+  },
+  {
+   "fieldname": "sequence_id",
+   "fieldtype": "Int",
+   "label": "Sequence ID",
+   "print_hide": 1,
+   "read_only": 1
   }
  ],
+ "index_web_pages_for_search": 1,
  "istable": 1,
  "links": [],
- "modified": "2019-12-03 19:24:29.594189",
+ "modified": "2020-10-14 12:58:49.241252",
  "modified_by": "Administrator",
  "module": "Manufacturing",
  "name": "Work Order Operation",
diff --git a/erpnext/manufacturing/doctype/workstation/test_workstation.py b/erpnext/manufacturing/doctype/workstation/test_workstation.py
index 8266cf7..c6699be 100644
--- a/erpnext/manufacturing/doctype/workstation/test_workstation.py
+++ b/erpnext/manufacturing/doctype/workstation/test_workstation.py
@@ -21,17 +21,22 @@
 		self.assertRaises(WorkstationHolidayError, check_if_within_operating_hours,
 			"_Test Workstation 1", "Operation 1", "2013-02-01 10:00:00", "2013-02-02 20:00:00")
 
-def make_workstation(**args):
+def make_workstation(*args, **kwargs):
+	args = args if args else kwargs
+	if isinstance(args, tuple):
+		args = args[0]
+
 	args = frappe._dict(args)
 
+	workstation_name = args.workstation_name or args.workstation
 	try:
 		doc = frappe.get_doc({
 			"doctype": "Workstation",
-			"workstation_name": args.workstation_name
+			"workstation_name": workstation_name
 		})
 
 		doc.insert()
 
 		return doc
 	except frappe.DuplicateEntryError:
-		return frappe.get_doc("Workstation", args.workstation_name)
\ No newline at end of file
+		return frappe.get_doc("Workstation", workstation_name)
\ No newline at end of file
diff --git a/erpnext/selling/doctype/selling_settings/selling_settings.json b/erpnext/selling/doctype/selling_settings/selling_settings.json
index dcbc074..4044f09 100644
--- a/erpnext/selling/doctype/selling_settings/selling_settings.json
+++ b/erpnext/selling/doctype/selling_settings/selling_settings.json
@@ -63,7 +63,7 @@
   },
   {
    "default": "15",
-   "description": "Auto close Opportunity after 15 days",
+   "description": "Auto close Opportunity after the no. of days mentioned above",
    "fieldname": "close_opportunity_after_days",
    "fieldtype": "Int",
    "label": "Close Opportunity After Days"
@@ -80,18 +80,18 @@
   {
    "fieldname": "so_required",
    "fieldtype": "Select",
-   "label": "Sales Order Required for Sales Invoice & Delivery Note Creation",
+   "label": "Is Sales Order Required for Sales Invoice & Delivery Note Creation?",
    "options": "No\nYes"
   },
   {
    "fieldname": "dn_required",
    "fieldtype": "Select",
-   "label": "Delivery Note Required for Sales Invoice Creation",
+   "label": "Is Delivery Note Required for Sales Invoice Creation?",
    "options": "No\nYes"
   },
   {
    "default": "Each Transaction",
-   "description": "How often should project and company be updated based on Sales Transactions.",
+   "description": "How often should Project and Company be updated based on Sales Transactions?",
    "fieldname": "sales_update_frequency",
    "fieldtype": "Select",
    "label": "Sales Update Frequency",
@@ -108,38 +108,39 @@
    "default": "0",
    "fieldname": "editable_price_list_rate",
    "fieldtype": "Check",
-   "label": "Allow user to edit Price List Rate in transactions"
+   "label": "Allow User to Edit Price List Rate in Transactions"
   },
   {
    "default": "0",
    "fieldname": "allow_multiple_items",
    "fieldtype": "Check",
-   "label": "Allow Item to be added multiple times in a transaction"
+   "label": "Allow Item to Be Added Multiple Times in a Transaction"
   },
   {
    "default": "0",
    "fieldname": "allow_against_multiple_purchase_orders",
    "fieldtype": "Check",
-   "label": "Allow multiple Sales Orders against a Customer's Purchase Order"
+   "label": "Allow Multiple Sales Orders Against a Customer's Purchase Order"
   },
   {
    "default": "0",
    "fieldname": "validate_selling_price",
    "fieldtype": "Check",
-   "label": "Validate Selling Price for Item against Purchase Rate or Valuation Rate"
+   "label": "Validate Selling Price for Item Against Purchase Rate or Valuation Rate"
   },
   {
    "default": "0",
    "fieldname": "hide_tax_id",
    "fieldtype": "Check",
-   "label": "Hide Customer's Tax Id from Sales Transactions"
+   "label": "Hide Customer's Tax ID from Sales Transactions"
   }
  ],
  "icon": "fa fa-cog",
  "idx": 1,
+ "index_web_pages_for_search": 1,
  "issingle": 1,
  "links": [],
- "modified": "2020-06-01 13:58:35.637858",
+ "modified": "2020-10-13 12:12:56.784014",
  "modified_by": "Administrator",
  "module": "Selling",
  "name": "Selling Settings",
diff --git a/erpnext/stock/doctype/delivery_note/test_delivery_note.py b/erpnext/stock/doctype/delivery_note/test_delivery_note.py
index 4b04a0a..0168613 100644
--- a/erpnext/stock/doctype/delivery_note/test_delivery_note.py
+++ b/erpnext/stock/doctype/delivery_note/test_delivery_note.py
@@ -57,7 +57,7 @@
 
 		sle = frappe.get_doc("Stock Ledger Entry", {"voucher_type": "Delivery Note", "voucher_no": dn.name})
 
-		self.assertEqual(sle.stock_value_difference, -1*stock_queue[0][1])
+		self.assertEqual(sle.stock_value_difference, flt(-1*stock_queue[0][1], 2))
 
 		self.assertFalse(get_gl_entries("Delivery Note", dn.name))
 
diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.py b/erpnext/stock/doctype/stock_entry/stock_entry.py
index 2a54169..9ad3694 100644
--- a/erpnext/stock/doctype/stock_entry/stock_entry.py
+++ b/erpnext/stock/doctype/stock_entry/stock_entry.py
@@ -1117,7 +1117,10 @@
 				for d in backflushed_materials.get(item.item_code):
 					if d.get(item.warehouse):
 						if (qty > req_qty):
-							qty-= d.get(item.warehouse)
+							qty = (qty/trans_qty) * flt(self.fg_completed_qty)
+
+			if cint(frappe.get_cached_value('UOM', item.stock_uom, 'must_be_whole_number')):
+				qty = frappe.utils.ceil(qty)
 
 			if qty > 0:
 				self.add_to_stock_entry_detail({
@@ -1320,8 +1323,10 @@
 				for sr in get_serial_nos(item.serial_no):
 					sales_order = frappe.db.get_value("Serial No", sr, "sales_order")
 					if sales_order:
-						frappe.throw(_("Item {0} (Serial No: {1}) cannot be consumed as is reserverd\
-						 to fullfill Sales Order {2}.").format(item.item_code, sr, sales_order))
+						msg = (_("(Serial No: {0}) cannot be consumed as it's reserverd to fullfill Sales Order {1}.")
+							.format(sr, sales_order))
+
+						frappe.throw(_("Item {0} {1}").format(item.item_code, msg))
 
 	def update_transferred_qty(self):
 		if self.purpose == 'Material Transfer' and self.outgoing_stock_entry:
diff --git a/erpnext/stock/doctype/stock_settings/stock_settings.json b/erpnext/stock/doctype/stock_settings/stock_settings.json
index 9c5d3d8..067659f 100644
--- a/erpnext/stock/doctype/stock_settings/stock_settings.json
+++ b/erpnext/stock/doctype/stock_settings/stock_settings.json
@@ -82,7 +82,7 @@
    "options": "FIFO\nMoving Average"
   },
   {
-   "description": "Percentage you are allowed to receive or deliver more against the quantity ordered. For example: If you have ordered 100 units. and your Allowance is 10% then you are allowed to receive 110 units.",
+   "description": "The percentage you are allowed to receive or deliver more against the quantity ordered. For example, if you have ordered 100 units, and your Allowance is 10%, then you are allowed to receive 110 units.",
    "fieldname": "over_delivery_receipt_allowance",
    "fieldtype": "Float",
    "label": "Over Delivery/Receipt Allowance (%)"
@@ -91,7 +91,7 @@
    "default": "Stop",
    "fieldname": "action_if_quality_inspection_is_not_submitted",
    "fieldtype": "Select",
-   "label": "Action if Quality inspection is not submitted",
+   "label": "Action If Quality Inspection Is Not Submitted",
    "options": "Stop\nWarn"
   },
   {
@@ -114,7 +114,7 @@
    "default": "0",
    "fieldname": "auto_insert_price_list_rate_if_missing",
    "fieldtype": "Check",
-   "label": "Auto insert Price List rate if missing"
+   "label": "Auto Insert Price List Rate If Missing"
   },
   {
    "default": "0",
@@ -130,13 +130,13 @@
    "default": "1",
    "fieldname": "automatically_set_serial_nos_based_on_fifo",
    "fieldtype": "Check",
-   "label": "Automatically Set Serial Nos based on FIFO"
+   "label": "Automatically Set Serial Nos Based on FIFO"
   },
   {
    "default": "1",
    "fieldname": "set_qty_in_transactions_based_on_serial_no_input",
    "fieldtype": "Check",
-   "label": "Set Qty in Transactions based on Serial No Input"
+   "label": "Set Qty in Transactions Based on Serial No Input"
   },
   {
    "fieldname": "auto_material_request",
@@ -147,13 +147,13 @@
    "default": "0",
    "fieldname": "auto_indent",
    "fieldtype": "Check",
-   "label": "Raise Material Request when stock reaches re-order level"
+   "label": "Raise Material Request When Stock Reaches Re-order Level"
   },
   {
    "default": "0",
    "fieldname": "reorder_email_notify",
    "fieldtype": "Check",
-   "label": "Notify by Email on creation of automatic Material Request"
+   "label": "Notify by Email on Creation of Automatic Material Request"
   },
   {
    "fieldname": "freeze_stock_entries",
@@ -168,12 +168,12 @@
   {
    "fieldname": "stock_frozen_upto_days",
    "fieldtype": "Int",
-   "label": "Freeze Stocks Older Than [Days]"
+   "label": "Freeze Stocks Older Than (Days)"
   },
   {
    "fieldname": "stock_auth_role",
    "fieldtype": "Link",
-   "label": "Role Allowed to edit frozen stock",
+   "label": "Role Allowed to Edit Frozen Stock",
    "options": "Role"
   },
   {
@@ -203,20 +203,21 @@
    "default": "0",
    "fieldname": "allow_from_dn",
    "fieldtype": "Check",
-   "label": "Allow Material Transfer From Delivery Note and Sales Invoice"
+   "label": "Allow Material Transfer from Delivery Note to Sales Invoice"
   },
   {
    "default": "0",
    "fieldname": "allow_from_pr",
    "fieldtype": "Check",
-   "label": "Allow Material Transfer From Purchase Receipt and Purchase Invoice"
+   "label": "Allow Material Transfer from Purchase Receipt to Purchase Invoice"
   }
  ],
  "icon": "icon-cog",
  "idx": 1,
+ "index_web_pages_for_search": 1,
  "issingle": 1,
  "links": [],
- "modified": "2020-06-20 11:39:15.344112",
+ "modified": "2020-10-13 10:33:29.147682",
  "modified_by": "Administrator",
  "module": "Stock",
  "name": "Stock Settings",