Merge pull request #26176 from deepeshgarg007/item_tax_fetch_fix

fix: User is not able to change item tax template
diff --git a/erpnext/accounts/custom/address.py b/erpnext/accounts/custom/address.py
index 5e76403..c417a49 100644
--- a/erpnext/accounts/custom/address.py
+++ b/erpnext/accounts/custom/address.py
@@ -33,6 +33,8 @@
 	if address and frappe.db.get_value('Dynamic Link',
 		{'parent': address, 'link_name': company}):
 		filters.append(["Address", "name", "=", address])
+	if not address:
+		filters.append(["Address", "is_shipping_address", "=", 1])
 
 	address = frappe.get_all("Address", filters=filters, fields=fields) or {}
 
diff --git a/erpnext/accounts/deferred_revenue.py b/erpnext/accounts/deferred_revenue.py
index dd346bc..2f86c6c 100644
--- a/erpnext/accounts/deferred_revenue.py
+++ b/erpnext/accounts/deferred_revenue.py
@@ -263,6 +263,9 @@
 			amount, base_amount = calculate_amount(doc, item, last_gl_entry,
 				total_days, total_booking_days, account_currency)
 
+		if not amount:
+			return
+
 		if via_journal_entry:
 			book_revenue_via_journal_entry(doc, credit_account, debit_account, against, amount,
 				base_amount, end_date, project, account_currency, item.cost_center, item, deferred_process, submit_journal_entry)
diff --git a/erpnext/accounts/doctype/accounting_dimension/accounting_dimension.py b/erpnext/accounts/doctype/accounting_dimension/accounting_dimension.py
index 7cd1e77..fac28c9 100644
--- a/erpnext/accounts/doctype/accounting_dimension/accounting_dimension.py
+++ b/erpnext/accounts/doctype/accounting_dimension/accounting_dimension.py
@@ -19,7 +19,7 @@
 
 	def validate(self):
 		if self.document_type in core_doctypes_list + ('Accounting Dimension', 'Project',
-				'Cost Center', 'Accounting Dimension Detail', 'Company') :
+				'Cost Center', 'Accounting Dimension Detail', 'Company', 'Account') :
 
 			msg = _("Not allowed to create accounting dimension for {0}").format(self.document_type)
 			frappe.throw(msg)
diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.json b/erpnext/accounts/doctype/payment_entry/payment_entry.json
index 54623dd..51f18a5 100644
--- a/erpnext/accounts/doctype/payment_entry/payment_entry.json
+++ b/erpnext/accounts/doctype/payment_entry/payment_entry.json
@@ -690,7 +690,7 @@
    "options": "Account"
   },
   {
-   "depends_on": "eval:doc.received_amount",
+   "depends_on": "eval:doc.received_amount && doc.payment_type != 'Internal Transfer'",
    "fieldname": "received_amount_after_tax",
    "fieldtype": "Currency",
    "label": "Received Amount After Tax",
@@ -707,7 +707,7 @@
  "index_web_pages_for_search": 1,
  "is_submittable": 1,
  "links": [],
- "modified": "2021-06-09 11:55:04.215050",
+ "modified": "2021-06-22 20:37:06.154206",
  "modified_by": "Administrator",
  "module": "Accounts",
  "name": "Payment Entry",
diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py
index b6b2bef..adaf99a 100644
--- a/erpnext/accounts/doctype/payment_entry/payment_entry.py
+++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py
@@ -706,7 +706,7 @@
 			if account_currency != self.company_currency:
 				frappe.throw(_("Currency for {0} must be {1}").format(d.account_head, self.company_currency))
 
-			if self.payment_type == 'Pay':
+			if self.payment_type in ('Pay', 'Internal Transfer'):
 				dr_or_cr = "debit" if d.add_deduct_tax == "Add" else "credit"
 			elif self.payment_type == 'Receive':
 				dr_or_cr = "credit" if d.add_deduct_tax == "Add" else "debit"
@@ -761,7 +761,7 @@
 			return self.advance_tax_account
 		elif self.payment_type == 'Receive':
 			return self.paid_from
-		elif self.payment_type == 'Pay':
+		elif self.payment_type in ('Pay', 'Internal Transfer'):
 			return self.paid_to
 
 	def update_advance_paid(self):
diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js
index f58c8f4..dc9094c 100644
--- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js
+++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js
@@ -27,10 +27,6 @@
 		});
 	},
 
-	company: function() {
-		erpnext.accounts.dimensions.update_dimension(this.frm, this.frm.doctype);
-	},
-
 	onload: function() {
 		this._super();
 
@@ -569,5 +565,9 @@
 			frm: frm,
 			freeze_message: __("Creating Purchase Receipt ...")
 		})
-	}
+	},
+
+	company: function(frm) {
+		erpnext.accounts.dimensions.update_dimension(frm, frm.doctype);
+	},
 })
diff --git a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py
index ff433b9..2f5d36c 100644
--- a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py
+++ b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py
@@ -966,7 +966,7 @@
 		update_tax_witholding_category('_Test Company', 'TDS Payable - _TC', nowdate())
 
 		# Create Purchase Order with TDS applied
-		po = create_purchase_order(do_not_save=1, supplier=supplier.name, rate=3000)
+		po = create_purchase_order(do_not_save=1, supplier=supplier.name, rate=3000, item='_Test Non Stock Item')
 		po.apply_tds = 1
 		po.tax_withholding_category = 'TDS - 194 - Dividends - Individual'
 		po.save()
@@ -1002,6 +1002,7 @@
 		# Create Purchase Invoice against Purchase Order
 		purchase_invoice = get_mapped_purchase_invoice(po.name)
 		purchase_invoice.allocate_advances_automatically = 1
+		purchase_invoice.items[0].item_code = '_Test Non Stock Item'
 		purchase_invoice.items[0].expense_account = '_Test Account Cost for Goods Sold - _TC'
 		purchase_invoice.save()
 		purchase_invoice.submit()
diff --git a/erpnext/accounts/general_ledger.py b/erpnext/accounts/general_ledger.py
index 59009ae..25d2cf1 100644
--- a/erpnext/accounts/general_ledger.py
+++ b/erpnext/accounts/general_ledger.py
@@ -101,7 +101,7 @@
 
 def check_if_in_list(gle, gl_map, dimensions=None):
 	account_head_fieldnames = ['party_type', 'party', 'against_voucher', 'against_voucher_type',
-		'cost_center', 'project']
+		'cost_center', 'project', 'voucher_detail_no']
 
 	if dimensions:
 		account_head_fieldnames = account_head_fieldnames + dimensions
diff --git a/erpnext/accounts/report/general_ledger/general_ledger.py b/erpnext/accounts/report/general_ledger/general_ledger.py
index 03808c3..744ada9 100644
--- a/erpnext/accounts/report/general_ledger/general_ledger.py
+++ b/erpnext/accounts/report/general_ledger/general_ledger.py
@@ -222,7 +222,7 @@
 def get_conditions(filters):
 	conditions = []
 
-	if filters.get("account") and not filters.get("include_dimensions"):
+	if filters.get("account"):
 		filters.account = get_accounts_with_children(filters.account)
 		conditions.append("account in %(account)s")
 
diff --git a/erpnext/buying/doctype/buying_settings/buying_settings.json b/erpnext/buying/doctype/buying_settings/buying_settings.json
index 630a1dc..b9c77d5 100644
--- a/erpnext/buying/doctype/buying_settings/buying_settings.json
+++ b/erpnext/buying/doctype/buying_settings/buying_settings.json
@@ -9,13 +9,14 @@
   "supp_master_name",
   "supplier_group",
   "buying_price_list",
+  "maintain_same_rate_action",
+  "role_to_override_stop_action",
   "column_break_3",
   "po_required",
   "pr_required",
   "maintain_same_rate",
-  "maintain_same_rate_action",
-  "role_to_override_stop_action",
   "allow_multiple_items",
+  "bill_for_rejected_quantity_in_purchase_invoice",
   "subcontract",
   "backflush_raw_materials_of_subcontract_based_on",
   "column_break_11",
@@ -108,6 +109,13 @@
    "fieldtype": "Link",
    "label": "Role Allowed to Override Stop Action",
    "options": "Role"
+  },
+  {
+   "default": "1",
+   "description": "If checked, Rejected Quantity will be included while making Purchase Invoice from Purchase Receipt.",
+   "fieldname": "bill_for_rejected_quantity_in_purchase_invoice",
+   "fieldtype": "Check",
+   "label": "Bill for Rejected Quantity in Purchase Invoice"
   }
  ],
  "icon": "fa fa-cog",
@@ -115,7 +123,7 @@
  "index_web_pages_for_search": 1,
  "issingle": 1,
  "links": [],
- "modified": "2021-04-04 20:01:44.087066",
+ "modified": "2021-06-24 10:38:28.934525",
  "modified_by": "Administrator",
  "module": "Buying",
  "name": "Buying Settings",
diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py
index 243939b..1c086e9 100644
--- a/erpnext/controllers/accounts_controller.py
+++ b/erpnext/controllers/accounts_controller.py
@@ -828,8 +828,14 @@
 					role_allowed_to_over_bill = frappe.db.get_single_value('Accounts Settings', 'role_allowed_to_over_bill')
 
 					if total_billed_amt - max_allowed_amt > 0.01 and role_allowed_to_over_bill not in frappe.get_roles():
-						frappe.throw(_("Cannot overbill for Item {0} in row {1} more than {2}. To allow over-billing, please set allowance in Accounts Settings")
-							.format(item.item_code, item.idx, max_allowed_amt))
+						if self.doctype != "Purchase Invoice":
+							self.throw_overbill_exception(item, max_allowed_amt)
+						elif not cint(frappe.db.get_single_value("Buying Settings", "bill_for_rejected_quantity_in_purchase_invoice")):
+							self.throw_overbill_exception(item, max_allowed_amt)
+
+	def throw_overbill_exception(self, item, max_allowed_amt):
+		frappe.throw(_("Cannot overbill for Item {0} in row {1} more than {2}. To allow over-billing, please set allowance in Accounts Settings")
+			.format(item.item_code, item.idx, max_allowed_amt))
 
 	def get_company_default(self, fieldname):
 		from erpnext.accounts.utils import get_company_default
diff --git a/erpnext/controllers/queries.py b/erpnext/controllers/queries.py
index 4ada834..2803193 100644
--- a/erpnext/controllers/queries.py
+++ b/erpnext/controllers/queries.py
@@ -19,7 +19,7 @@
 	fields = get_fields("Employee", ["name", "employee_name"])
 
 	return frappe.db.sql("""select {fields} from `tabEmployee`
-		where status = 'Active'
+		where status in ('Active', 'Suspended')
 			and docstatus < 2
 			and ({key} like %(txt)s
 				or employee_name like %(txt)s)
diff --git a/erpnext/crm/report/first_response_time_for_opportunity/first_response_time_for_opportunity.js b/erpnext/crm/report/first_response_time_for_opportunity/first_response_time_for_opportunity.js
index 3f5c95a..fe5707a 100644
--- a/erpnext/crm/report/first_response_time_for_opportunity/first_response_time_for_opportunity.js
+++ b/erpnext/crm/report/first_response_time_for_opportunity/first_response_time_for_opportunity.js
@@ -22,10 +22,10 @@
 	get_chart_data: function (_columns, result) {
 		return {
 			data: {
-				labels: result.map(d => d[0]),
+				labels: result.map(d => d.creation_date),
 				datasets: [{
 					name: "First Response Time",
-					values: result.map(d => d[1])
+					values: result.map(d => d.first_response_time)
 				}]
 			},
 			type: "line",
@@ -35,8 +35,7 @@
 						hide_days: 0,
 						hide_seconds: 0
 					};
-					value = frappe.utils.get_formatted_duration(d, duration_options);
-					return value;
+					return frappe.utils.get_formatted_duration(d, duration_options);
 				}
 			}
 		}
diff --git a/erpnext/hr/doctype/employee/employee.json b/erpnext/hr/doctype/employee/employee.json
index 5442ed5..d592a9c 100644
--- a/erpnext/hr/doctype/employee/employee.json
+++ b/erpnext/hr/doctype/employee/employee.json
@@ -207,7 +207,7 @@
    "label": "Status",
    "oldfieldname": "status",
    "oldfieldtype": "Select",
-   "options": "Active\nInactive\nLeft",
+   "options": "Active\nInactive\nSuspended\nLeft",
    "reqd": 1,
    "search_index": 1
   },
@@ -813,7 +813,7 @@
  "idx": 24,
  "image_field": "image",
  "links": [],
- "modified": "2021-06-12 11:31:37.730760",
+ "modified": "2021-06-17 11:31:37.730760",
  "modified_by": "Administrator",
  "module": "HR",
  "name": "Employee",
diff --git a/erpnext/hr/doctype/employee/employee.py b/erpnext/hr/doctype/employee/employee.py
index bc56942..fa017d9 100755
--- a/erpnext/hr/doctype/employee/employee.py
+++ b/erpnext/hr/doctype/employee/employee.py
@@ -4,7 +4,7 @@
 from __future__ import unicode_literals
 import frappe
 
-from frappe.utils import getdate, validate_email_address, today, add_years, format_datetime, cstr
+from frappe.utils import getdate, validate_email_address, today, add_years, cstr
 from frappe.model.naming import set_name_by_naming_series
 from frappe import throw, _, scrub
 from frappe.permissions import add_user_permission, remove_user_permission, \
@@ -12,7 +12,6 @@
 from frappe.model.document import Document
 from erpnext.utilities.transaction_base import delete_events
 from frappe.utils.nestedset import NestedSet
-from erpnext.hr.doctype.job_offer.job_offer import get_staffing_plan_detail
 
 class EmployeeUserDisabledError(frappe.ValidationError): pass
 class EmployeeLeftValidationError(frappe.ValidationError): pass
@@ -37,7 +36,7 @@
 
 	def validate(self):
 		from erpnext.controllers.status_updater import validate_status
-		validate_status(self.status, ["Active", "Inactive", "Left"])
+		validate_status(self.status, ["Active", "Inactive", "Suspended", "Left"])
 
 		self.employee = self.name
 		self.set_employee_name()
diff --git a/erpnext/hr/doctype/employee/employee_dashboard.py b/erpnext/hr/doctype/employee/employee_dashboard.py
index 285374d..e853bee 100644
--- a/erpnext/hr/doctype/employee/employee_dashboard.py
+++ b/erpnext/hr/doctype/employee/employee_dashboard.py
@@ -7,7 +7,8 @@
 		'heatmap_message': _('This is based on the attendance of this Employee'),
 		'fieldname': 'employee',
 		'non_standard_fieldnames': {
-			'Bank Account': 'party'
+			'Bank Account': 'party',
+			'Employee Grievance': 'raised_by'
 		},
 		'transactions': [
 			{
@@ -20,7 +21,7 @@
 			},
 			{
 				'label': _('Lifecycle'),
-				'items': ['Employee Transfer', 'Employee Promotion', 'Employee Separation']
+				'items': ['Employee Transfer', 'Employee Promotion', 'Employee Separation', 'Employee Grievance']
 			},
 			{
 				'label': _('Shift'),
diff --git a/erpnext/hr/doctype/employee/employee_list.js b/erpnext/hr/doctype/employee/employee_list.js
index 6679e31..d37e149 100644
--- a/erpnext/hr/doctype/employee/employee_list.js
+++ b/erpnext/hr/doctype/employee/employee_list.js
@@ -3,7 +3,7 @@
 	filters: [["status","=", "Active"]],
 	get_indicator: function(doc) {
 		var indicator = [__(doc.status), frappe.utils.guess_colour(doc.status), "status,=," + doc.status];
-		indicator[1] = {"Active": "green", "Inactive": "red", "Left": "gray"}[doc.status];
+		indicator[1] = {"Active": "green", "Inactive": "red", "Left": "gray", "Suspended": "orange"}[doc.status];
 		return indicator;
 	}
 };
diff --git a/erpnext/hr/doctype/employee_grievance/__init__.py b/erpnext/hr/doctype/employee_grievance/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/erpnext/hr/doctype/employee_grievance/__init__.py
diff --git a/erpnext/hr/doctype/employee_grievance/employee_grievance.js b/erpnext/hr/doctype/employee_grievance/employee_grievance.js
new file mode 100644
index 0000000..25c5bad
--- /dev/null
+++ b/erpnext/hr/doctype/employee_grievance/employee_grievance.js
@@ -0,0 +1,39 @@
+// Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors
+// For license information, please see license.txt
+
+frappe.ui.form.on('Employee Grievance', {
+	setup: function(frm) {
+		frm.set_query('grievance_against_party', function() {
+			return {
+				filters: {
+					name: ['in', [
+						'Company', 'Department', 'Employee Group', 'Employee Grade', 'Employee']
+					]
+				}
+			};
+		});
+		frm.set_query('associated_document_type', function() {
+			let ignore_modules = ["Setup", "Core", "Integrations", "Automation", "Website",
+				"Utilities", "Event Streaming", "Social", "Chat", "Data Migration", "Printing", "Desk", "Custom"];
+			return {
+				filters: {
+					istable: 0,
+					issingle: 0,
+					module: ["Not In", ignore_modules]
+				}
+			};
+		});
+	},
+
+	grievance_against_party: function(frm) {
+		let filters = {};
+		if (frm.doc.grievance_against_party == 'Employee' && frm.doc.raised_by) {
+			filters.name =  ["!=", frm.doc.raised_by];
+		}
+		frm.set_query('grievance_against', function() {
+			return {
+				filters: filters
+			};
+		});
+	},
+});
diff --git a/erpnext/hr/doctype/employee_grievance/employee_grievance.json b/erpnext/hr/doctype/employee_grievance/employee_grievance.json
new file mode 100644
index 0000000..5a91856
--- /dev/null
+++ b/erpnext/hr/doctype/employee_grievance/employee_grievance.json
@@ -0,0 +1,261 @@
+{
+ "actions": [],
+ "autoname": "HR-GRIEV-.YYYY.-.#####",
+ "creation": "2021-05-11 13:41:51.485295",
+ "doctype": "DocType",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+  "subject",
+  "raised_by",
+  "employee_name",
+  "designation",
+  "column_break_3",
+  "date",
+  "status",
+  "reports_to",
+  "grievance_details_section",
+  "grievance_against_party",
+  "grievance_against",
+  "grievance_type",
+  "column_break_11",
+  "associated_document_type",
+  "associated_document",
+  "section_break_14",
+  "description",
+  "investigation_details_section",
+  "cause_of_grievance",
+  "resolution_details_section",
+  "resolved_by",
+  "resolution_date",
+  "employee_responsible",
+  "column_break_16",
+  "resolution_detail",
+  "amended_from"
+ ],
+ "fields": [
+  {
+   "fieldname": "grievance_type",
+   "fieldtype": "Link",
+   "in_list_view": 1,
+   "label": "Grievance Type",
+   "options": "Grievance Type",
+   "reqd": 1
+  },
+  {
+   "fieldname": "column_break_3",
+   "fieldtype": "Column Break"
+  },
+  {
+   "fieldname": "date",
+   "fieldtype": "Date",
+   "in_list_view": 1,
+   "label": "Date ",
+   "reqd": 1
+  },
+  {
+   "default": "Open",
+   "fieldname": "status",
+   "fieldtype": "Select",
+   "in_list_view": 1,
+   "label": "Status",
+   "options": "Open\nInvestigated\nResolved\nInvalid",
+   "reqd": 1
+  },
+  {
+   "fieldname": "description",
+   "fieldtype": "Text",
+   "label": "Description",
+   "reqd": 1
+  },
+  {
+   "fieldname": "cause_of_grievance",
+   "fieldtype": "Text",
+   "label": "Cause of Grievance",
+   "mandatory_depends_on": "eval: doc.status == \"Investigated\" || doc.status ==  \"Resolved\""
+  },
+  {
+   "fieldname": "resolution_details_section",
+   "fieldtype": "Section Break",
+   "label": "Resolution Details"
+  },
+  {
+   "fieldname": "resolved_by",
+   "fieldtype": "Link",
+   "label": "Resolved By",
+   "mandatory_depends_on": "eval: doc.status == \"Resolved\"",
+   "options": "User"
+  },
+  {
+   "fieldname": "employee_responsible",
+   "fieldtype": "Link",
+   "label": "Employee Responsible ",
+   "options": "Employee"
+  },
+  {
+   "fieldname": "resolution_detail",
+   "fieldtype": "Small Text",
+   "label": "Resolution Details",
+   "mandatory_depends_on": "eval: doc.status == \"Resolved\""
+  },
+  {
+   "fieldname": "column_break_16",
+   "fieldtype": "Column Break"
+  },
+  {
+   "fieldname": "resolution_date",
+   "fieldtype": "Date",
+   "label": "Resolution Date",
+   "mandatory_depends_on": "eval: doc.status == \"Resolved\""
+  },
+  {
+   "fieldname": "grievance_against",
+   "fieldtype": "Dynamic Link",
+   "label": "Grievance Against",
+   "options": "grievance_against_party",
+   "reqd": 1
+  },
+  {
+   "fieldname": "raised_by",
+   "fieldtype": "Link",
+   "label": "Raised By",
+   "options": "Employee",
+   "reqd": 1
+  },
+  {
+   "fieldname": "amended_from",
+   "fieldtype": "Link",
+   "label": "Amended From",
+   "no_copy": 1,
+   "options": "Employee Grievance",
+   "print_hide": 1,
+   "read_only": 1
+  },
+  {
+   "fetch_from": "raised_by.designation",
+   "fieldname": "designation",
+   "fieldtype": "Link",
+   "label": "Designation",
+   "options": "Designation",
+   "read_only": 1
+  },
+  {
+   "fetch_from": "raised_by.reports_to",
+   "fieldname": "reports_to",
+   "fieldtype": "Link",
+   "label": "Reports To",
+   "options": "Employee",
+   "read_only": 1
+  },
+  {
+   "fieldname": "grievance_details_section",
+   "fieldtype": "Section Break",
+   "label": "Grievance Details"
+  },
+  {
+   "fieldname": "column_break_11",
+   "fieldtype": "Column Break"
+  },
+  {
+   "fieldname": "section_break_14",
+   "fieldtype": "Section Break"
+  },
+  {
+   "fieldname": "grievance_against_party",
+   "fieldtype": "Link",
+   "in_list_view": 1,
+   "label": "Grievance Against Party",
+   "options": "DocType",
+   "reqd": 1
+  },
+  {
+   "fieldname": "associated_document_type",
+   "fieldtype": "Link",
+   "label": "Associated Document Type",
+   "options": "DocType"
+  },
+  {
+   "fieldname": "associated_document",
+   "fieldtype": "Dynamic Link",
+   "label": "Associated Document",
+   "options": "associated_document_type"
+  },
+  {
+   "fieldname": "investigation_details_section",
+   "fieldtype": "Section Break",
+   "label": "Investigation Details"
+  },
+  {
+   "fetch_from": "raised_by.employee_name",
+   "fieldname": "employee_name",
+   "fieldtype": "Data",
+   "label": "Employee Name",
+   "read_only": 1
+  },
+  {
+   "fieldname": "subject",
+   "fieldtype": "Data",
+   "label": "Subject",
+   "reqd": 1
+  }
+ ],
+ "index_web_pages_for_search": 1,
+ "is_submittable": 1,
+ "links": [],
+ "modified": "2021-06-21 12:51:01.499486",
+ "modified_by": "Administrator",
+ "module": "HR",
+ "name": "Employee Grievance",
+ "owner": "Administrator",
+ "permissions": [
+  {
+   "amend": 1,
+   "cancel": 1,
+   "create": 1,
+   "delete": 1,
+   "email": 1,
+   "export": 1,
+   "print": 1,
+   "read": 1,
+   "report": 1,
+   "role": "System Manager",
+   "select": 1,
+   "share": 1,
+   "submit": 1,
+   "write": 1
+  },
+  {
+   "amend": 1,
+   "cancel": 1,
+   "create": 1,
+   "delete": 1,
+   "email": 1,
+   "export": 1,
+   "print": 1,
+   "read": 1,
+   "report": 1,
+   "role": "HR Manager",
+   "select": 1,
+   "share": 1,
+   "submit": 1,
+   "write": 1
+  },
+  {
+   "create": 1,
+   "delete": 1,
+   "email": 1,
+   "export": 1,
+   "print": 1,
+   "read": 1,
+   "report": 1,
+   "role": "HR User",
+   "share": 1,
+   "write": 1
+  }
+ ],
+ "search_fields": "subject,raised_by,grievance_against_party",
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "title_field": "subject",
+ "track_changes": 1
+}
\ No newline at end of file
diff --git a/erpnext/hr/doctype/employee_grievance/employee_grievance.py b/erpnext/hr/doctype/employee_grievance/employee_grievance.py
new file mode 100644
index 0000000..503b5ea
--- /dev/null
+++ b/erpnext/hr/doctype/employee_grievance/employee_grievance.py
@@ -0,0 +1,15 @@
+# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors
+# For license information, please see license.txt
+
+import frappe
+from frappe import _, bold
+from frappe.model.document import Document
+
+class EmployeeGrievance(Document):
+	def on_submit(self):
+		if self.status not in ["Invalid", "Resolved"]:
+			frappe.throw(_("Only Employee Grievance with status {0} or {1} can be submitted").format(
+				bold("Invalid"),
+				bold("Resolved"))
+			)
+
diff --git a/erpnext/hr/doctype/employee_grievance/employee_grievance_list.js b/erpnext/hr/doctype/employee_grievance/employee_grievance_list.js
new file mode 100644
index 0000000..fc08e21
--- /dev/null
+++ b/erpnext/hr/doctype/employee_grievance/employee_grievance_list.js
@@ -0,0 +1,12 @@
+frappe.listview_settings["Employee Grievance"] = {
+	has_indicator_for_draft: 1,
+	get_indicator: function(doc) {
+		var colors = {
+			"Open": "red",
+			"Investigated": "orange",
+			"Resolved": "green",
+			"Invalid": "grey"
+		};
+		return [__(doc.status), colors[doc.status], "status,=," + doc.status];
+	}
+};
\ No newline at end of file
diff --git a/erpnext/hr/doctype/employee_grievance/test_employee_grievance.py b/erpnext/hr/doctype/employee_grievance/test_employee_grievance.py
new file mode 100644
index 0000000..a615b20
--- /dev/null
+++ b/erpnext/hr/doctype/employee_grievance/test_employee_grievance.py
@@ -0,0 +1,51 @@
+# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and Contributors
+# See license.txt
+
+import frappe
+import unittest
+from frappe.utils import today
+from erpnext.hr.doctype.employee.test_employee import make_employee
+class TestEmployeeGrievance(unittest.TestCase):
+	def test_create_employee_grievance(self):
+		create_employee_grievance()
+
+def create_employee_grievance():
+	grievance_type = create_grievance_type()
+	emp_1 = make_employee("test_emp_grievance_@example.com", company="_Test Company")
+	emp_2 = make_employee("testculprit@example.com", company="_Test Company")
+
+	grievance = frappe.new_doc("Employee Grievance")
+	grievance.subject = "Test Employee Grievance"
+	grievance.raised_by = emp_1
+	grievance.date = today()
+	grievance.grievance_type = grievance_type
+	grievance.grievance_against_party = "Employee"
+	grievance.grievance_against = emp_2
+	grievance.description = "test descrip"
+
+	#set cause
+	grievance.cause_of_grievance = "test cause"
+
+	#resolution details
+	grievance.resolution_date = today()
+	grievance.resolution_detail = "test resolution detail"
+	grievance.resolved_by = "test_emp_grievance_@example.com"
+	grievance.employee_responsible = emp_2
+	grievance.status = "Resolved"
+
+	grievance.save()
+	grievance.submit()
+
+	return grievance
+
+
+def create_grievance_type():
+	if frappe.db.exists("Grievance Type", "Employee Abuse"):
+		return frappe.get_doc("Grievance Type", "Employee Abuse")
+	grievance_type = frappe.new_doc("Grievance Type")
+	grievance_type.name = "Employee Abuse"
+	grievance_type.description = "Test"
+	grievance_type.save()
+
+	return grievance_type.name
+
diff --git a/erpnext/hr/doctype/grievance_type/__init__.py b/erpnext/hr/doctype/grievance_type/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/erpnext/hr/doctype/grievance_type/__init__.py
diff --git a/erpnext/hr/doctype/grievance_type/grievance_type.js b/erpnext/hr/doctype/grievance_type/grievance_type.js
new file mode 100644
index 0000000..425f2fd
--- /dev/null
+++ b/erpnext/hr/doctype/grievance_type/grievance_type.js
@@ -0,0 +1,8 @@
+// Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors
+// For license information, please see license.txt
+
+frappe.ui.form.on('Grievance Type', {
+	// refresh: function(frm) {
+
+	// }
+});
diff --git a/erpnext/hr/doctype/grievance_type/grievance_type.json b/erpnext/hr/doctype/grievance_type/grievance_type.json
new file mode 100644
index 0000000..1dce00a
--- /dev/null
+++ b/erpnext/hr/doctype/grievance_type/grievance_type.json
@@ -0,0 +1,70 @@
+{
+ "actions": [],
+ "autoname": "Prompt",
+ "creation": "2021-05-11 12:41:50.256071",
+ "doctype": "DocType",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+  "section_break_5",
+  "description"
+ ],
+ "fields": [
+  {
+   "fieldname": "section_break_5",
+   "fieldtype": "Section Break"
+  },
+  {
+   "fieldname": "description",
+   "fieldtype": "Text",
+   "label": "Description"
+  }
+ ],
+ "index_web_pages_for_search": 1,
+ "links": [],
+ "modified": "2021-06-21 12:54:37.764712",
+ "modified_by": "Administrator",
+ "module": "HR",
+ "name": "Grievance Type",
+ "owner": "Administrator",
+ "permissions": [
+  {
+   "create": 1,
+   "delete": 1,
+   "email": 1,
+   "export": 1,
+   "print": 1,
+   "read": 1,
+   "report": 1,
+   "role": "System Manager",
+   "share": 1,
+   "write": 1
+  },
+  {
+   "create": 1,
+   "delete": 1,
+   "email": 1,
+   "export": 1,
+   "print": 1,
+   "read": 1,
+   "report": 1,
+   "role": "HR Manager",
+   "share": 1,
+   "write": 1
+  },
+  {
+   "create": 1,
+   "delete": 1,
+   "email": 1,
+   "export": 1,
+   "print": 1,
+   "read": 1,
+   "report": 1,
+   "role": "HR User",
+   "share": 1,
+   "write": 1
+  }
+ ],
+ "sort_field": "modified",
+ "sort_order": "DESC"
+}
\ No newline at end of file
diff --git a/erpnext/hr/doctype/grievance_type/grievance_type.py b/erpnext/hr/doctype/grievance_type/grievance_type.py
new file mode 100644
index 0000000..618cf0a
--- /dev/null
+++ b/erpnext/hr/doctype/grievance_type/grievance_type.py
@@ -0,0 +1,8 @@
+# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors
+# For license information, please see license.txt
+
+# import frappe
+from frappe.model.document import Document
+
+class GrievanceType(Document):
+	pass
diff --git a/erpnext/hr/doctype/grievance_type/test_grievance_type.py b/erpnext/hr/doctype/grievance_type/test_grievance_type.py
new file mode 100644
index 0000000..a02a34d
--- /dev/null
+++ b/erpnext/hr/doctype/grievance_type/test_grievance_type.py
@@ -0,0 +1,8 @@
+# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and Contributors
+# See license.txt
+
+# import frappe
+import unittest
+
+class TestGrievanceType(unittest.TestCase):
+	pass
diff --git a/erpnext/hr/doctype/job_applicant/job_applicant_list.js b/erpnext/hr/doctype/job_applicant/job_applicant_list.js
index 3b9141b..2ad0d59 100644
--- a/erpnext/hr/doctype/job_applicant/job_applicant_list.js
+++ b/erpnext/hr/doctype/job_applicant/job_applicant_list.js
@@ -2,7 +2,7 @@
 // MIT License. See license.txt
 
 frappe.listview_settings['Job Applicant'] = {
-	add_fields: ["company", "designation", "job_applicant", "status"],
+	add_fields: ["status"],
 	get_indicator: function (doc) {
 		if (doc.status == "Accepted") {
 			return [__(doc.status), "green", "status,=," + doc.status];
diff --git a/erpnext/hr/doctype/leave_allocation/leave_allocation.json b/erpnext/hr/doctype/leave_allocation/leave_allocation.json
index ae02c51..3a6539e 100644
--- a/erpnext/hr/doctype/leave_allocation/leave_allocation.json
+++ b/erpnext/hr/doctype/leave_allocation/leave_allocation.json
@@ -110,6 +110,7 @@
    "label": "Allocation"
   },
   {
+   "allow_on_submit": 1,
    "bold": 1,
    "fieldname": "new_leaves_allocated",
    "fieldtype": "Float",
@@ -235,7 +236,7 @@
  "index_web_pages_for_search": 1,
  "is_submittable": 1,
  "links": [],
- "modified": "2021-04-14 15:28:26.335104",
+ "modified": "2021-06-03 15:28:26.335104",
  "modified_by": "Administrator",
  "module": "HR",
  "name": "Leave Allocation",
@@ -277,4 +278,4 @@
  "sort_field": "modified",
  "sort_order": "DESC",
  "timeline_field": "employee"
-}
\ No newline at end of file
+}
diff --git a/erpnext/hr/doctype/leave_allocation/leave_allocation.py b/erpnext/hr/doctype/leave_allocation/leave_allocation.py
index 11302ca..4757cd3 100755
--- a/erpnext/hr/doctype/leave_allocation/leave_allocation.py
+++ b/erpnext/hr/doctype/leave_allocation/leave_allocation.py
@@ -8,6 +8,7 @@
 from frappe.model.document import Document
 from erpnext.hr.utils import set_employee_name, get_leave_period
 from erpnext.hr.doctype.leave_ledger_entry.leave_ledger_entry import expire_allocation, create_leave_ledger_entry
+from erpnext.hr.doctype.leave_application.leave_application import get_approved_leaves_for_period
 
 class OverlapError(frappe.ValidationError): pass
 class BackDatedAllocationError(frappe.ValidationError): pass
@@ -55,6 +56,43 @@
 		if self.carry_forward:
 			self.set_carry_forwarded_leaves_in_previous_allocation(on_cancel=True)
 
+	def on_update_after_submit(self):
+		if self.has_value_changed("new_leaves_allocated"):
+			self.validate_against_leave_applications()
+			leaves_to_be_added = self.new_leaves_allocated - self.get_existing_leave_count()
+			args = {
+				"leaves": leaves_to_be_added,
+				"from_date": self.from_date,
+				"to_date": self.to_date,
+				"is_carry_forward": 0
+			}
+			create_leave_ledger_entry(self, args, True)
+
+	def get_existing_leave_count(self):
+		ledger_entries = frappe.get_all("Leave Ledger Entry",
+								filters={
+									"transaction_type": "Leave Allocation",
+									"transaction_name": self.name,
+									"employee": self.employee,
+									"company": self.company,
+									"leave_type": self.leave_type
+								},
+								pluck="leaves")
+		total_existing_leaves = 0
+		for entry in ledger_entries:
+			total_existing_leaves += entry
+
+		return total_existing_leaves
+
+	def validate_against_leave_applications(self):
+		leaves_taken = get_approved_leaves_for_period(self.employee, self.leave_type,
+			self.from_date, self.to_date)
+		if flt(leaves_taken) > flt(self.total_leaves_allocated):
+			if frappe.db.get_value("Leave Type", self.leave_type, "allow_negative"):
+				frappe.msgprint(_("Note: Total allocated leaves {0} shouldn't be less than already approved leaves {1} for the period").format(self.total_leaves_allocated, leaves_taken))
+			else:
+				frappe.throw(_("Total allocated leaves {0} cannot be less than already approved leaves {1} for the period").format(self.total_leaves_allocated, leaves_taken), LessAllocationError)
+
 	def update_leave_policy_assignments_when_no_allocations_left(self):
 		allocations = frappe.db.get_list("Leave Allocation", filters = {
 			"docstatus": 1,
@@ -225,4 +263,4 @@
 
 def validate_carry_forward(leave_type):
 	if not frappe.db.get_value("Leave Type", leave_type, "is_carry_forward"):
-		frappe.throw(_("Leave Type {0} cannot be carry-forwarded").format(leave_type))
\ No newline at end of file
+		frappe.throw(_("Leave Type {0} cannot be carry-forwarded").format(leave_type))
diff --git a/erpnext/hr/doctype/leave_allocation/test_leave_allocation.py b/erpnext/hr/doctype/leave_allocation/test_leave_allocation.py
index 6e7ae87..bff06e6 100644
--- a/erpnext/hr/doctype/leave_allocation/test_leave_allocation.py
+++ b/erpnext/hr/doctype/leave_allocation/test_leave_allocation.py
@@ -1,5 +1,6 @@
 from __future__ import unicode_literals
 import frappe
+import erpnext
 import unittest
 from frappe.utils import nowdate, add_months, getdate, add_days
 from erpnext.hr.doctype.leave_type.test_leave_type import create_leave_type
@@ -164,6 +165,51 @@
 		leave_allocation.cancel()
 		self.assertFalse(frappe.db.exists("Leave Ledger Entry", {'transaction_name':leave_allocation.name}))
 
+	def test_leave_addition_after_submit(self):
+		frappe.db.sql("delete from `tabLeave Allocation`")
+		frappe.db.sql("delete from `tabLeave Ledger Entry`")
+
+		leave_allocation = create_leave_allocation()
+		leave_allocation.submit()
+		self.assertTrue(leave_allocation.total_leaves_allocated, 15)
+		leave_allocation.new_leaves_allocated = 40
+		leave_allocation.submit()
+		self.assertTrue(leave_allocation.total_leaves_allocated, 40)
+
+	def test_leave_subtraction_after_submit(self):
+		frappe.db.sql("delete from `tabLeave Allocation`")
+		frappe.db.sql("delete from `tabLeave Ledger Entry`")
+		leave_allocation = create_leave_allocation()
+		leave_allocation.submit()
+		self.assertTrue(leave_allocation.total_leaves_allocated, 15)
+		leave_allocation.new_leaves_allocated = 10
+		leave_allocation.submit()
+		self.assertTrue(leave_allocation.total_leaves_allocated, 10)
+
+	def test_against_leave_application_validation_after_submit(self):
+		frappe.db.sql("delete from `tabLeave Allocation`")
+		frappe.db.sql("delete from `tabLeave Ledger Entry`")
+
+		leave_allocation = create_leave_allocation()
+		leave_allocation.submit()
+		self.assertTrue(leave_allocation.total_leaves_allocated, 15)
+		employee = frappe.get_doc("Employee", frappe.db.sql_list("select name from tabEmployee limit 1")[0])
+		leave_application = frappe.get_doc({
+			"doctype": 'Leave Application',
+			"employee": employee.name,
+			"leave_type": "_Test Leave Type",
+			"from_date": add_months(nowdate(), 2),
+			"to_date": add_months(add_days(nowdate(), 10), 2),
+			"company": erpnext.get_default_company() or "_Test Company",
+			"docstatus": 1,
+			"status": "Approved",
+			"leave_approver": 'test@example.com'
+		})
+		leave_application.submit()
+		leave_allocation.new_leaves_allocated = 8
+		leave_allocation.total_leaves_allocated = 8
+		self.assertRaises(frappe.ValidationError, leave_allocation.submit)
+
 def create_leave_allocation(**args):
 	args = frappe._dict(args)
 
diff --git a/erpnext/hr/doctype/staffing_plan/staffing_plan.py b/erpnext/hr/doctype/staffing_plan/staffing_plan.py
index 533149a..e6c783a 100644
--- a/erpnext/hr/doctype/staffing_plan/staffing_plan.py
+++ b/erpnext/hr/doctype/staffing_plan/staffing_plan.py
@@ -41,7 +41,7 @@
 
 			detail.total_estimated_cost = 0
 			if detail.number_of_positions > 0:
-				if detail.vacancies > 0 and detail.estimated_cost_per_position:
+				if detail.vacancies and detail.estimated_cost_per_position:
 					detail.total_estimated_cost = cint(detail.vacancies) * flt(detail.estimated_cost_per_position)
 
 			self.total_estimated_budget += detail.total_estimated_cost
@@ -76,12 +76,12 @@
 		if cint(staffing_plan_detail.vacancies) > cint(parent_plan_details[0].vacancies) or \
 			flt(staffing_plan_detail.total_estimated_cost) > flt(parent_plan_details[0].total_estimated_cost):
 			frappe.throw(_("You can only plan for upto {0} vacancies and budget {1} \
-				for {2} as per staffing plan {3} for parent company {4}."
-				.format(cint(parent_plan_details[0].vacancies),
+				for {2} as per staffing plan {3} for parent company {4}.").format(
+					cint(parent_plan_details[0].vacancies),
 					parent_plan_details[0].total_estimated_cost,
 					frappe.bold(staffing_plan_detail.designation),
 					parent_plan_details[0].name,
-					parent_company)), ParentCompanyError)
+					parent_company), ParentCompanyError)
 
 		#Get vacanices already planned for all companies down the hierarchy of Parent Company
 		lft, rgt = frappe.get_cached_value('Company',  parent_company,  ["lft", "rgt"])
@@ -98,14 +98,14 @@
 			(flt(parent_plan_details[0].total_estimated_cost) < \
 			(flt(staffing_plan_detail.total_estimated_cost) + flt(all_sibling_details.total_estimated_cost))):
 			frappe.throw(_("{0} vacancies and {1} budget for {2} already planned for subsidiary companies of {3}. \
-				You can only plan for upto {4} vacancies and and budget {5} as per staffing plan {6} for parent company {3}."
-				.format(cint(all_sibling_details.vacancies),
+				You can only plan for upto {4} vacancies and and budget {5} as per staffing plan {6} for parent company {3}.").format(
+					cint(all_sibling_details.vacancies),
 					all_sibling_details.total_estimated_cost,
 					frappe.bold(staffing_plan_detail.designation),
 					parent_company,
 					cint(parent_plan_details[0].vacancies),
 					parent_plan_details[0].total_estimated_cost,
-					parent_plan_details[0].name)))
+					parent_plan_details[0].name))
 
 	def validate_with_subsidiary_plans(self, staffing_plan_detail):
 		#Valdate this plan with all child company plan
@@ -121,11 +121,11 @@
 			cint(staffing_plan_detail.vacancies) < cint(children_details.vacancies) or \
 			flt(staffing_plan_detail.total_estimated_cost) < flt(children_details.total_estimated_cost):
 			frappe.throw(_("Subsidiary companies have already planned for {1} vacancies at a budget of {2}. \
-				Staffing Plan for {0} should allocate more vacancies and budget for {3} than planned for its subsidiary companies"
-				.format(self.company,
+				Staffing Plan for {0} should allocate more vacancies and budget for {3} than planned for its subsidiary companies").format(
+					self.company,
 					cint(children_details.vacancies),
 					children_details.total_estimated_cost,
-					frappe.bold(staffing_plan_detail.designation))), SubsidiaryCompanyError)
+					frappe.bold(staffing_plan_detail.designation)), SubsidiaryCompanyError)
 
 @frappe.whitelist()
 def get_designation_counts(designation, company):
@@ -170,4 +170,4 @@
 				designation, from_date, to_date)
 
 	# Only a single staffing plan can be active for a designation on given date
-	return staffing_plan if staffing_plan else None
\ No newline at end of file
+	return staffing_plan if staffing_plan else None
diff --git a/erpnext/hr/report/employee_leave_balance/employee_leave_balance.py b/erpnext/hr/report/employee_leave_balance/employee_leave_balance.py
index 4dd4570..b8953b3 100644
--- a/erpnext/hr/report/employee_leave_balance/employee_leave_balance.py
+++ b/erpnext/hr/report/employee_leave_balance/employee_leave_balance.py
@@ -178,7 +178,7 @@
 			is_carry_forward, is_expired
 		FROM `tabLeave Ledger Entry`
 		WHERE employee=%(employee)s AND leave_type=%(leave_type)s
-			AND docstatus=1 AND leaves>0
+			AND docstatus=1
 			AND (from_date between %(from_date)s AND %(to_date)s
 				OR to_date between %(from_date)s AND %(to_date)s
 				OR (from_date < %(from_date)s AND to_date > %(to_date)s))
diff --git a/erpnext/hr/workspace/hr/hr.json b/erpnext/hr/workspace/hr/hr.json
index c5201c2..4500ba4 100644
--- a/erpnext/hr/workspace/hr/hr.json
+++ b/erpnext/hr/workspace/hr/hr.json
@@ -154,6 +154,24 @@
    "type": "Link"
   },
   {
+   "hidden": 0,
+   "is_query_report": 0,
+   "label": "Grievance Type",
+   "link_to": "Grievance Type",
+   "link_type": "DocType",
+   "onboard": 0,
+   "type": "Link"
+  },
+  {
+   "hidden": 0,
+   "is_query_report": 0,
+   "label": "Employee Grievance",
+   "link_to": "Employee Grievance",
+   "link_type": "DocType",
+   "onboard": 0,
+   "type": "Link"
+  },
+  {
    "dependencies": "Employee",
    "hidden": 0,
    "is_query_report": 0,
@@ -823,7 +841,7 @@
    "type": "Link"
   }
  ],
- "modified": "2021-04-26 13:36:15.413819",
+ "modified": "2021-05-13 17:19:40.524444",
  "modified_by": "Administrator",
  "module": "HR",
  "name": "HR",
diff --git a/erpnext/manufacturing/doctype/bom/bom.js b/erpnext/manufacturing/doctype/bom/bom.js
index a09a5e3..27019db 100644
--- a/erpnext/manufacturing/doctype/bom/bom.js
+++ b/erpnext/manufacturing/doctype/bom/bom.js
@@ -71,7 +71,6 @@
 
 	refresh: function(frm) {
 		frm.toggle_enable("item", frm.doc.__islocal);
-		toggle_operations(frm);
 
 		frm.set_indicator_formatter('item_code',
 			function(doc) {
@@ -651,15 +650,8 @@
 	erpnext.bom.calculate_total(frm.doc);
 });
 
-var toggle_operations = function(frm) {
-	frm.toggle_display("operations_section", cint(frm.doc.with_operations) == 1);
-	frm.toggle_display("transfer_material_against", cint(frm.doc.with_operations) == 1);
-	frm.toggle_reqd("transfer_material_against", cint(frm.doc.with_operations) == 1);
-};
-
 frappe.ui.form.on("BOM", "with_operations", function(frm) {
 	if(!cint(frm.doc.with_operations)) {
 		frm.set_value("operations", []);
 	}
-	toggle_operations(frm);
 });
\ No newline at end of file
diff --git a/erpnext/manufacturing/doctype/bom/bom.json b/erpnext/manufacturing/doctype/bom/bom.json
index f551b91..f38d1b9 100644
--- a/erpnext/manufacturing/doctype/bom/bom.json
+++ b/erpnext/manufacturing/doctype/bom/bom.json
@@ -193,6 +193,7 @@
   },
   {
    "default": "Work Order",
+   "depends_on": "with_operations",
    "fieldname": "transfer_material_against",
    "fieldtype": "Select",
    "label": "Transfer Material Against",
@@ -235,6 +236,7 @@
   {
    "fieldname": "operations_section",
    "fieldtype": "Section Break",
+   "hide_border": 1,
    "oldfieldtype": "Section Break"
   },
   {
@@ -245,6 +247,7 @@
    "options": "Routing"
   },
   {
+   "depends_on": "with_operations",
    "fieldname": "operations",
    "fieldtype": "Table",
    "label": "Operations",
@@ -517,7 +520,7 @@
  "image_field": "image",
  "is_submittable": 1,
  "links": [],
- "modified": "2020-05-21 12:29:32.634952",
+ "modified": "2021-03-16 12:25:09.081968",
  "modified_by": "Administrator",
  "module": "Manufacturing",
  "name": "BOM",
diff --git a/erpnext/manufacturing/doctype/bom/bom.py b/erpnext/manufacturing/doctype/bom/bom.py
index d1f6385..3e85560 100644
--- a/erpnext/manufacturing/doctype/bom/bom.py
+++ b/erpnext/manufacturing/doctype/bom/bom.py
@@ -81,7 +81,7 @@
 		self.validate_operations()
 		self.calculate_cost()
 		self.update_stock_qty()
-		self.update_cost(update_parent=False, from_child_bom=True, save=False)
+		self.update_cost(update_parent=False, from_child_bom=True, update_hour_rate = False, save=False)
 
 	def get_context(self, context):
 		context.parents = [{'name': 'boms', 'title': _('All BOMs') }]
@@ -213,7 +213,7 @@
 		return flt(rate) * flt(self.plc_conversion_rate or 1) / (self.conversion_rate or 1)
 
 	@frappe.whitelist()
-	def update_cost(self, update_parent=True, from_child_bom=False, save=True):
+	def update_cost(self, update_parent=True, from_child_bom=False, update_hour_rate = True, save=True):
 		if self.docstatus == 2:
 			return
 
@@ -242,7 +242,7 @@
 
 		if self.docstatus == 1:
 			self.flags.ignore_validate_update_after_submit = True
-			self.calculate_cost()
+			self.calculate_cost(update_hour_rate)
 		if save:
 			self.db_update()
 
@@ -403,32 +403,47 @@
 		bom_list.reverse()
 		return bom_list
 
-	def calculate_cost(self):
+	def calculate_cost(self, update_hour_rate = False):
 		"""Calculate bom totals"""
-		self.calculate_op_cost()
+		self.calculate_op_cost(update_hour_rate)
 		self.calculate_rm_cost()
 		self.calculate_sm_cost()
 		self.total_cost = self.operating_cost + self.raw_material_cost - self.scrap_material_cost
 		self.base_total_cost = self.base_operating_cost + self.base_raw_material_cost - self.base_scrap_material_cost
 
-	def calculate_op_cost(self):
+	def calculate_op_cost(self, update_hour_rate = False):
 		"""Update workstation rate and calculates totals"""
 		self.operating_cost = 0
 		self.base_operating_cost = 0
 		for d in self.get('operations'):
 			if d.workstation:
-				if not d.hour_rate:
-					hour_rate = flt(frappe.db.get_value("Workstation", d.workstation, "hour_rate"))
-					d.hour_rate = hour_rate / flt(self.conversion_rate) if self.conversion_rate else hour_rate
-
-			if d.hour_rate and d.time_in_mins:
-				d.base_hour_rate = flt(d.hour_rate) * flt(self.conversion_rate)
-				d.operating_cost = flt(d.hour_rate) * flt(d.time_in_mins) / 60.0
-				d.base_operating_cost = flt(d.operating_cost) * flt(self.conversion_rate)
+				self.update_rate_and_time(d, update_hour_rate)
 
 			self.operating_cost += flt(d.operating_cost)
 			self.base_operating_cost += flt(d.base_operating_cost)
 
+	def update_rate_and_time(self, row, update_hour_rate = False):
+		if not row.hour_rate or update_hour_rate:
+			hour_rate = flt(frappe.get_cached_value("Workstation", row.workstation, "hour_rate"))
+			row.hour_rate = (hour_rate / flt(self.conversion_rate)
+				if self.conversion_rate and hour_rate else hour_rate)
+
+			if self.routing:
+				row.time_in_mins = flt(frappe.db.get_value("BOM Operation", {
+						"workstation": row.workstation,
+						"operation": row.operation,
+						"sequence_id": row.sequence_id,
+						"parent": self.routing
+				}, ["time_in_mins"]))
+
+		if row.hour_rate and row.time_in_mins:
+			row.base_hour_rate = flt(row.hour_rate) * flt(self.conversion_rate)
+			row.operating_cost = flt(row.hour_rate) * flt(row.time_in_mins) / 60.0
+			row.base_operating_cost = flt(row.operating_cost) * flt(self.conversion_rate)
+
+		if update_hour_rate:
+			row.db_update()
+
 	def calculate_rm_cost(self):
 		"""Fetch RM rate as per today's valuation rate and calculate totals"""
 		total_rm_cost = 0
@@ -575,7 +590,7 @@
 			self.get_routing()
 
 	def validate_operations(self):
-		if self.with_operations and not self.get('operations'):
+		if self.with_operations and not self.get('operations') and self.docstatus == 1:
 			frappe.throw(_("Operations cannot be left blank"))
 
 		if self.with_operations:
@@ -975,7 +990,7 @@
 
 	if filters and filters.get("is_stock_item"):
 		query_filters["is_stock_item"] = 1
-		
+
 	return frappe.get_all("Item",
 		fields = fields, filters=query_filters,
 		or_filters = or_cond_filters, order_by=order_by,
diff --git a/erpnext/manufacturing/doctype/bom/test_bom.py b/erpnext/manufacturing/doctype/bom/test_bom.py
index 42b23f2..1f443fb 100644
--- a/erpnext/manufacturing/doctype/bom/test_bom.py
+++ b/erpnext/manufacturing/doctype/bom/test_bom.py
@@ -123,7 +123,7 @@
 		bom.items[0].conversion_factor = 5
 		bom.insert()
 
-		bom.update_cost()
+		bom.update_cost(update_hour_rate = False)
 
 		# test amounts in selected currency
 		self.assertEqual(bom.items[0].rate, 300)
diff --git a/erpnext/manufacturing/doctype/bom_operation/bom_operation.json b/erpnext/manufacturing/doctype/bom_operation/bom_operation.json
index 07464e3..4458e6d 100644
--- a/erpnext/manufacturing/doctype/bom_operation/bom_operation.json
+++ b/erpnext/manufacturing/doctype/bom_operation/bom_operation.json
@@ -13,10 +13,10 @@
   "col_break1",
   "hour_rate",
   "time_in_mins",
-  "batch_size",
   "operating_cost",
   "base_hour_rate",
   "base_operating_cost",
+  "batch_size",
   "image"
  ],
  "fields": [
@@ -61,6 +61,8 @@
   },
   {
    "description": "In minutes",
+   "fetch_from": "operation.total_operation_time",
+   "fetch_if_empty": 1,
    "fieldname": "time_in_mins",
    "fieldtype": "Float",
    "in_list_view": 1,
@@ -104,7 +106,8 @@
    "label": "Image"
   },
   {
-   "default": "1",
+   "fetch_from": "operation.batch_size",
+   "fetch_if_empty": 1,
    "fieldname": "batch_size",
    "fieldtype": "Int",
    "label": "Batch Size"
@@ -120,7 +123,7 @@
  "index_web_pages_for_search": 1,
  "istable": 1,
  "links": [],
- "modified": "2020-10-13 18:14:10.018774",
+ "modified": "2021-01-12 14:48:09.596843",
  "modified_by": "Administrator",
  "module": "Manufacturing",
  "name": "BOM Operation",
diff --git a/erpnext/manufacturing/doctype/job_card/job_card.js b/erpnext/manufacturing/doctype/job_card/job_card.js
index 4e8dd41..81860c9 100644
--- a/erpnext/manufacturing/doctype/job_card/job_card.js
+++ b/erpnext/manufacturing/doctype/job_card/job_card.js
@@ -11,6 +11,16 @@
 				}
 			};
 		});
+
+		frm.set_indicator_formatter('sub_operation',
+			function(doc) {
+				if (doc.status == "Pending") {
+					return "red";
+				} else {
+					return doc.status === "Complete" ? "green" : "orange";
+				}
+			}
+		);
 	},
 
 	refresh: function(frm) {
@@ -31,6 +41,10 @@
 			}
 		}
 
+		if (frm.doc.docstatus == 1 && !frm.doc.is_corrective_job_card) {
+			frm.trigger('setup_corrective_job_card');
+		}
+
 		frm.set_query("quality_inspection", function() {
 			return {
 				query: "erpnext.stock.doctype.quality_inspection.quality_inspection.quality_inspection_query",
@@ -43,12 +57,62 @@
 
 		frm.trigger("toggle_operation_number");
 
-		if (frm.doc.docstatus == 0 && (frm.doc.for_quantity > frm.doc.total_completed_qty || !frm.doc.for_quantity)
+		if (frm.doc.docstatus == 0 && !frm.is_new() &&
+			(frm.doc.for_quantity > frm.doc.total_completed_qty || !frm.doc.for_quantity)
 			&& (frm.doc.items || !frm.doc.items.length || frm.doc.for_quantity == frm.doc.transferred_qty)) {
 			frm.trigger("prepare_timer_buttons");
 		}
 	},
 
+	setup_corrective_job_card: function(frm) {
+		frm.add_custom_button(__('Corrective Job Card'), () => {
+			let operations = frm.doc.sub_operations.map(d => d.sub_operation).concat(frm.doc.operation);
+
+			let fields = [
+				{
+					fieldtype: 'Link', label: __('Corrective Operation'), options: 'Operation',
+					fieldname: 'operation', get_query() {
+						return {
+							filters: {
+								"is_corrective_operation": 1
+							}
+						};
+					}
+				}, {
+					fieldtype: 'Link', label: __('For Operation'), options: 'Operation',
+					fieldname: 'for_operation', get_query() {
+						return {
+							filters: {
+								"name": ["in", operations]
+							}
+						};
+					}
+				}
+			];
+
+			frappe.prompt(fields, d => {
+				frm.events.make_corrective_job_card(frm, d.operation, d.for_operation);
+			}, __("Select Corrective Operation"));
+		}, __('Make'));
+	},
+
+	make_corrective_job_card: function(frm, operation, for_operation) {
+		frappe.call({
+			method: 'erpnext.manufacturing.doctype.job_card.job_card.make_corrective_job_card',
+			args: {
+				source_name: frm.doc.name,
+				operation: operation,
+				for_operation: for_operation
+			},
+			callback: function(r) {
+				if (r.message) {
+					frappe.model.sync(r.message);
+					frappe.set_route("Form", r.message.doctype, r.message.name);
+				}
+			}
+		});
+	},
+
 	operation: function(frm) {
 		frm.trigger("toggle_operation_number");
 
@@ -97,101 +161,105 @@
 
 	prepare_timer_buttons: function(frm) {
 		frm.trigger("make_dashboard");
-		if (!frm.doc.job_started) {
-			frm.add_custom_button(__("Start"), () => {
-				if (!frm.doc.employee) {
-					frappe.prompt({fieldtype: 'Link', label: __('Employee'), options: "Employee",
-						fieldname: 'employee'}, d => {
-						if (d.employee) {
-							frm.set_value("employee", d.employee);
-						} else {
-							frm.events.start_job(frm);
-						}
-					}, __("Enter Value"), __("Start"));
+
+		if (!frm.doc.started_time && !frm.doc.current_time) {
+			frm.add_custom_button(__("Start Job"), () => {
+				if ((frm.doc.employee && !frm.doc.employee.length) || !frm.doc.employee) {
+					frappe.prompt({fieldtype: 'Table MultiSelect', label: __('Select Employees'),
+						options: "Job Card Time Log", fieldname: 'employees'}, d => {
+						frm.events.start_job(frm, "Work In Progress", d.employees);
+					}, __("Assign Job to Employee"));
 				} else {
-					frm.events.start_job(frm);
+					frm.events.start_job(frm, "Work In Progress", frm.doc.employee);
 				}
 			}).addClass("btn-primary");
 		} else if (frm.doc.status == "On Hold") {
-			frm.add_custom_button(__("Resume"), () => {
-				frappe.flags.resume_job = 1;
-				frm.events.start_job(frm);
+			frm.add_custom_button(__("Resume Job"), () => {
+				frm.events.start_job(frm, "Resume Job", frm.doc.employee);
 			}).addClass("btn-primary");
 		} else {
-			frm.add_custom_button(__("Pause"), () => {
-				frappe.flags.pause_job = 1;
-				frm.set_value("status", "On Hold");
-				frm.events.complete_job(frm);
+			frm.add_custom_button(__("Pause Job"), () => {
+				frm.events.complete_job(frm, "On Hold");
 			});
 
-			frm.add_custom_button(__("Complete"), () => {
-				let completed_time = frappe.datetime.now_datetime();
-				frm.trigger("hide_timer");
+			frm.add_custom_button(__("Complete Job"), () => {
+				var sub_operations = frm.doc.sub_operations;
 
-				if (frm.doc.for_quantity) {
+				let set_qty = true;
+				if (sub_operations && sub_operations.length > 1) {
+					set_qty = false;
+					let last_op_row = sub_operations[sub_operations.length - 2];
+
+					if (last_op_row.status == 'Complete') {
+						set_qty = true;
+					}
+				}
+
+				if (set_qty) {
 					frappe.prompt({fieldtype: 'Float', label: __('Completed Quantity'),
-						fieldname: 'qty', reqd: 1, default: frm.doc.for_quantity}, data => {
-							frm.events.complete_job(frm, completed_time, data.qty);
-						}, __("Enter Value"), __("Complete"));
+						fieldname: 'qty', default: frm.doc.for_quantity}, data => {
+						frm.events.complete_job(frm, "Complete", data.qty);
+					}, __("Enter Value"));
 				} else {
-					frm.events.complete_job(frm, completed_time, 0);
+					frm.events.complete_job(frm, "Complete", 0.0);
 				}
 			}).addClass("btn-primary");
 		}
 	},
 
-	start_job: function(frm) {
-		let row = frappe.model.add_child(frm.doc, 'Job Card Time Log', 'time_logs');
-		row.from_time = frappe.datetime.now_datetime();
-		frm.set_value('job_started', 1);
-		frm.set_value('started_time' , row.from_time);
-		frm.set_value("status", "Work In Progress");
-
-		if (!frappe.flags.resume_job) {
-			frm.set_value('current_time' , 0);
-		}
-
-		frm.save();
+	start_job: function(frm, status, employee) {
+		const args = {
+			job_card_id: frm.doc.name,
+			start_time: frappe.datetime.now_datetime(),
+			employees: employee,
+			status: status
+		};
+		frm.events.make_time_log(frm, args);
 	},
 
-	complete_job: function(frm, completed_time, completed_qty) {
-		frm.doc.time_logs.forEach(d => {
-			if (d.from_time && !d.to_time) {
-				d.to_time = completed_time || frappe.datetime.now_datetime();
-				d.completed_qty = completed_qty || 0;
+	complete_job: function(frm, status, completed_qty) {
+		const args = {
+			job_card_id: frm.doc.name,
+			complete_time: frappe.datetime.now_datetime(),
+			status: status,
+			completed_qty: completed_qty
+		};
+		frm.events.make_time_log(frm, args);
+	},
 
-				if(frappe.flags.pause_job) {
-					let currentIncrement = moment(d.to_time).diff(moment(d.from_time),"seconds") || 0;
-					frm.set_value('current_time' , currentIncrement + (frm.doc.current_time || 0));
-				} else {
-					frm.set_value('started_time' , '');
-					frm.set_value('job_started', 0);
-					frm.set_value('current_time' , 0);
-				}
+	make_time_log: function(frm, args) {
+		frm.events.update_sub_operation(frm, args);
 
-				frm.save();
+		frappe.call({
+			method: "erpnext.manufacturing.doctype.job_card.job_card.make_time_log",
+			args: {
+				args: args
+			},
+			freeze: true,
+			callback: function () {
+				frm.reload_doc();
+				frm.trigger("make_dashboard");
 			}
 		});
 	},
 
+	update_sub_operation: function(frm, args) {
+		if (frm.doc.sub_operations && frm.doc.sub_operations.length) {
+			let sub_operations = frm.doc.sub_operations.filter(d => d.status != 'Complete');
+			if (sub_operations && sub_operations.length) {
+				args["sub_operation"] = sub_operations[0].sub_operation;
+			}
+		}
+	},
+
 	validate: function(frm) {
 		if ((!frm.doc.time_logs || !frm.doc.time_logs.length) && frm.doc.started_time) {
 			frm.trigger("reset_timer");
 		}
 	},
 
-	employee: function(frm) {
-		if (frm.doc.job_started && !frm.doc.current_time) {
-			frm.trigger("reset_timer");
-		} else {
-			frm.events.start_job(frm);
-		}
-	},
-
 	reset_timer: function(frm) {
 		frm.set_value('started_time' , '');
-		frm.set_value('job_started', 0);
-		frm.set_value('current_time' , 0);
 	},
 
 	make_dashboard: function(frm) {
@@ -297,7 +365,6 @@
 	},
 
 	to_time: function(frm) {
-		frm.set_value('job_started', 0);
 		frm.set_value('started_time', '');
 	}
 })
\ No newline at end of file
diff --git a/erpnext/manufacturing/doctype/job_card/job_card.json b/erpnext/manufacturing/doctype/job_card/job_card.json
index 5713f69..046e2fd 100644
--- a/erpnext/manufacturing/doctype/job_card/job_card.json
+++ b/erpnext/manufacturing/doctype/job_card/job_card.json
@@ -9,38 +9,49 @@
   "naming_series",
   "work_order",
   "bom_no",
-  "workstation",
-  "operation",
-  "operation_row_number",
   "column_break_4",
   "posting_date",
   "company",
-  "remarks",
   "production_section",
   "production_item",
   "item_name",
   "for_quantity",
-  "quality_inspection",
-  "wip_warehouse",
+  "serial_no",
   "column_break_12",
-  "employee",
-  "employee_name",
-  "status",
+  "wip_warehouse",
+  "quality_inspection",
   "project",
+  "batch_no",
+  "operation_section_section",
+  "operation",
+  "operation_row_number",
+  "column_break_18",
+  "workstation",
+  "employee",
+  "section_break_21",
+  "sub_operations",
   "timing_detail",
   "time_logs",
   "section_break_13",
   "total_completed_qty",
-  "total_time_in_mins",
   "column_break_15",
+  "total_time_in_mins",
   "section_break_8",
   "items",
+  "corrective_operation_section",
+  "for_job_card",
+  "is_corrective_job_card",
+  "column_break_33",
+  "hour_rate",
+  "for_operation",
   "more_information",
   "operation_id",
   "sequence_id",
   "transferred_qty",
   "requested_qty",
+  "status",
   "column_break_20",
+  "remarks",
   "barcode",
   "job_started",
   "started_time",
@@ -118,13 +129,6 @@
    "label": "Timing Detail"
   },
   {
-   "fieldname": "employee",
-   "fieldtype": "Link",
-   "in_standard_filter": 1,
-   "label": "Employee",
-   "options": "Employee"
-  },
-  {
    "allow_bulk_edit": 1,
    "fieldname": "time_logs",
    "fieldtype": "Table",
@@ -133,9 +137,11 @@
   },
   {
    "fieldname": "section_break_13",
-   "fieldtype": "Section Break"
+   "fieldtype": "Section Break",
+   "hide_border": 1
   },
   {
+   "default": "0",
    "fieldname": "total_completed_qty",
    "fieldtype": "Float",
    "label": "Total Completed Qty",
@@ -160,8 +166,7 @@
    "fieldname": "items",
    "fieldtype": "Table",
    "label": "Items",
-   "options": "Job Card Item",
-   "read_only": 1
+   "options": "Job Card Item"
   },
   {
    "collapsible": 1,
@@ -251,12 +256,7 @@
    "reqd": 1
   },
   {
-   "fetch_from": "employee.employee_name",
-   "fieldname": "employee_name",
-   "fieldtype": "Read Only",
-   "label": "Employee Name"
-  },
-  {
+   "collapsible": 1,
    "fieldname": "production_section",
    "fieldtype": "Section Break",
    "label": "Production"
@@ -314,11 +314,89 @@
    "label": "Quality Inspection",
    "no_copy": 1,
    "options": "Quality Inspection"
+  },
+  {
+   "allow_bulk_edit": 1,
+   "fieldname": "sub_operations",
+   "fieldtype": "Table",
+   "label": "Sub Operations",
+   "options": "Job Card Operation",
+   "read_only": 1
+  },
+  {
+   "fieldname": "operation_section_section",
+   "fieldtype": "Section Break",
+   "label": "Operation Section"
+  },
+  {
+   "fieldname": "column_break_18",
+   "fieldtype": "Column Break"
+  },
+  {
+   "fieldname": "section_break_21",
+   "fieldtype": "Section Break",
+   "hide_border": 1
+  },
+  {
+   "depends_on": "is_corrective_job_card",
+   "fieldname": "hour_rate",
+   "fieldtype": "Currency",
+   "label": "Hour Rate"
+  },
+  {
+   "collapsible": 1,
+   "depends_on": "is_corrective_job_card",
+   "fieldname": "corrective_operation_section",
+   "fieldtype": "Section Break",
+   "label": "Corrective Operation"
+  },
+  {
+   "default": "0",
+   "fieldname": "is_corrective_job_card",
+   "fieldtype": "Check",
+   "label": "Is Corrective Job Card",
+   "read_only": 1
+  },
+  {
+   "fieldname": "column_break_33",
+   "fieldtype": "Column Break"
+  },
+  {
+   "fieldname": "for_job_card",
+   "fieldtype": "Link",
+   "label": "For Job Card",
+   "options": "Job Card",
+   "read_only": 1
+  },
+  {
+   "fetch_from": "for_job_card.operation",
+   "fetch_if_empty": 1,
+   "fieldname": "for_operation",
+   "fieldtype": "Link",
+   "label": "For Operation",
+   "options": "Operation"
+  },
+  {
+   "fieldname": "employee",
+   "fieldtype": "Table MultiSelect",
+   "label": "Employee",
+   "options": "Job Card Time Log"
+  },
+  {
+   "fieldname": "serial_no",
+   "fieldtype": "Small Text",
+   "label": "Serial No"
+  },
+  {
+   "fieldname": "batch_no",
+   "fieldtype": "Link",
+   "label": "Batch No",
+   "options": "Batch"
   }
  ],
  "is_submittable": 1,
  "links": [],
- "modified": "2020-11-19 18:26:50.531664",
+ "modified": "2021-03-16 15:59:32.766484",
  "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 cdc4518..7f8f2ef 100644
--- a/erpnext/manufacturing/doctype/job_card/job_card.py
+++ b/erpnext/manufacturing/doctype/job_card/job_card.py
@@ -5,11 +5,12 @@
 from __future__ import unicode_literals
 import frappe
 import datetime
+import json
 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,
-	get_time, add_to_date, time_diff, add_days, get_datetime_str, get_link_to_form)
+	get_time, add_to_date, time_diff, add_days, get_datetime_str, get_link_to_form, time_diff_in_seconds)
 
 from erpnext.manufacturing.doctype.manufacturing_settings.manufacturing_settings import get_mins_between_operations
 
@@ -25,10 +26,21 @@
 		self.set_status()
 		self.validate_operation_id()
 		self.validate_sequence_id()
+		self.get_sub_operations()
+		self.update_sub_operation_status()
+
+	def get_sub_operations(self):
+		if self.operation:
+			self.sub_operations = []
+			for row in frappe.get_all("Sub Operation",
+				filters = {"parent": self.operation}, fields=["operation", "idx"]):
+				row.status = "Pending"
+				row.sub_operation = row.operation
+				self.append("sub_operations", row)
 
 	def validate_time_logs(self):
-		self.total_completed_qty = 0.0
 		self.total_time_in_mins = 0.0
+		self.total_completed_qty = 0.0
 
 		if self.get('time_logs'):
 			for d in self.get('time_logs'):
@@ -44,11 +56,14 @@
 					d.time_in_mins = time_diff_in_hours(d.to_time, d.from_time) * 60
 					self.total_time_in_mins += d.time_in_mins
 
-				if d.completed_qty:
+				if d.completed_qty and not self.sub_operations:
 					self.total_completed_qty += d.completed_qty
 
 			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
+
 	def get_overlap_for(self, args, check_next_available_slot=False):
 		production_capacity = 1
 
@@ -57,7 +72,7 @@
 				self.workstation, 'production_capacity') or 1
 			validate_overlap_for = " and jc.workstation = %(workstation)s "
 
-		if self.employee:
+		if args.get("employee"):
 			# override capacity for employee
 			production_capacity = 1
 			validate_overlap_for = " and jc.employee = %(employee)s "
@@ -80,7 +95,7 @@
 				"to_time": args.to_time,
 				"name": args.name or "No Name",
 				"parent": args.parent or "No Name",
-				"employee": self.employee,
+				"employee": args.get("employee"),
 				"workstation": self.workstation
 			}, as_dict=True)
 
@@ -158,6 +173,100 @@
 			row.planned_start_time = datetime.datetime.combine(start_date,
 				get_time(workstation_doc.working_hours[0].start_time))
 
+	def add_time_log(self, args):
+		last_row = []
+		employees = args.employees
+		if isinstance(employees, str):
+			employees = json.loads(employees)
+
+		if self.time_logs and len(self.time_logs) > 0:
+			last_row = self.time_logs[-1]
+
+		self.reset_timer_value(args)
+		if last_row and args.get("complete_time"):
+			for row in self.time_logs:
+				if not row.to_time:
+					row.update({
+						"to_time": get_datetime(args.get("complete_time")),
+						"operation": args.get("sub_operation"),
+						"completed_qty": args.get("completed_qty") or 0.0
+					})
+		elif args.get("start_time"):
+			for name in employees:
+				self.append("time_logs", {
+					"from_time": get_datetime(args.get("start_time")),
+					"employee": name.get('employee'),
+					"operation": args.get("sub_operation"),
+					"completed_qty": 0.0
+				})
+
+		if not self.employee:
+			self.set_employees(employees)
+
+		if self.status == "On Hold":
+			self.current_time = time_diff_in_seconds(last_row.to_time, last_row.from_time)
+
+		self.save()
+
+	def set_employees(self, employees):
+		for name in employees:
+			self.append('employee', {
+				'employee': name.get('employee'),
+				'completed_qty': 0.0
+			})
+
+	def reset_timer_value(self, args):
+		self.started_time = None
+
+		if args.get("status") in ["Work In Progress", "Complete"]:
+			self.current_time = 0.0
+
+			if args.get("status") == "Work In Progress":
+				self.started_time = get_datetime(args.get("start_time"))
+
+		if args.get("status") == "Resume Job":
+			args["status"] = "Work In Progress"
+
+		if args.get("status"):
+			self.status = args.get("status")
+
+	def update_sub_operation_status(self):
+		if not (self.sub_operations and self.time_logs):
+			return
+
+		operation_wise_completed_time = {}
+		for time_log in self.time_logs:
+			if time_log.operation not in operation_wise_completed_time:
+				operation_wise_completed_time.setdefault(time_log.operation,
+					frappe._dict({"status": "Pending", "completed_qty":0.0, "completed_time": 0.0, "employee": []}))
+
+			op_row = operation_wise_completed_time[time_log.operation]
+			op_row.status = "Work In Progress" if not time_log.time_in_mins else "Complete"
+			if self.status == 'On Hold':
+				op_row.status = 'Pause'
+
+			op_row.employee.append(time_log.employee)
+			if time_log.time_in_mins:
+				op_row.completed_time += time_log.time_in_mins
+				op_row.completed_qty += time_log.completed_qty
+
+		for row in self.sub_operations:
+			operation_deatils = operation_wise_completed_time.get(row.sub_operation)
+			if operation_deatils:
+				if row.status != 'Complete':
+					row.status = operation_deatils.status
+
+				row.completed_time = operation_deatils.completed_time
+				if operation_deatils.employee:
+					row.completed_time = row.completed_time / len(set(operation_deatils.employee))
+
+					if operation_deatils.completed_qty:
+						row.completed_qty = operation_deatils.completed_qty / len(set(operation_deatils.employee))
+			else:
+				row.status = 'Pending'
+				row.completed_time = 0.0
+				row.completed_qty = 0.0
+
 	def update_time_logs(self, row):
 		self.append("time_logs", {
 			"from_time": row.planned_start_time,
@@ -182,15 +291,18 @@
 
 			if self.get('operation') == d.operation:
 				self.append('items', {
-					'item_code': d.item_code,
-					'source_warehouse': d.source_warehouse,
-					'uom': frappe.db.get_value("Item", d.item_code, 'stock_uom'),
-					'item_name': d.item_name,
-					'description': d.description,
-					'required_qty': (d.required_qty * flt(self.for_quantity)) / doc.qty
+					"item_code": d.item_code,
+					"source_warehouse": d.source_warehouse,
+					"uom": frappe.db.get_value("Item", d.item_code, 'stock_uom'),
+					"item_name": d.item_name,
+					"description": d.description,
+					"required_qty": (d.required_qty * flt(self.for_quantity)) / doc.qty,
+					"rate": d.rate,
+					"amount": d.amount
 				})
 
 	def on_submit(self):
+		self.validate_transfer_qty()
 		self.validate_job_card()
 		self.update_work_order()
 		self.set_transferred_qty()
@@ -199,7 +311,16 @@
 		self.update_work_order()
 		self.set_transferred_qty()
 
+	def validate_transfer_qty(self):
+		if self.items and self.transferred_qty < self.for_quantity:
+			frappe.throw(_('Materials needs to be transferred to the work in progress warehouse for the job card {0}')
+				.format(self.name))
+
 	def validate_job_card(self):
+		if self.work_order and frappe.get_cached_value('Work Order', self.work_order, 'status') == 'Stopped':
+			frappe.throw(_("Transaction not allowed against stopped Work Order {0}")
+				.format(get_link_to_form('Work Order', self.work_order)))
+
 		if not self.time_logs:
 			frappe.throw(_("Time logs are required for {0} {1}")
 				.format(bold("Job Card"), get_link_to_form("Job Card", self.name)))
@@ -215,6 +336,10 @@
 		if not self.work_order:
 			return
 
+		if self.is_corrective_job_card and not cint(frappe.db.get_single_value('Manufacturing Settings',
+			'add_corrective_operation_cost_in_finished_good_valuation')):
+			return
+
 		for_quantity, time_in_mins = 0, 0
 		from_time_list, to_time_list = [], []
 
@@ -225,10 +350,24 @@
 			time_in_mins = flt(data[0].time_in_mins)
 
 		wo = frappe.get_doc('Work Order', self.work_order)
-		if self.operation_id:
+
+		if self.is_corrective_job_card:
+			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)
 
+	def update_corrective_in_work_order(self, wo):
+		wo.corrective_operation_cost = 0.0
+		for row in frappe.get_all('Job Card', fields = ['total_time_in_mins', 'hour_rate'],
+			filters = {'is_corrective_job_card': 1, 'docstatus': 1, 'work_order': self.work_order}):
+			wo.corrective_operation_cost += flt(row.total_time_in_mins) * flt(row.hour_rate)
+
+		wo.calculate_operating_cost()
+		wo.flags.ignore_validate_update_after_submit = True
+		wo.save()
+
 	def validate_produced_quantity(self, for_quantity, wo):
 		if self.docstatus < 2: return
 
@@ -248,8 +387,8 @@
 					min(from_time) as start_time, max(to_time) as end_time
 				FROM `tabJob Card` jc, `tabJob Card Time Log` jctl
 				WHERE
-					jctl.parent = jc.name and jc.work_order = %s
-					and jc.operation_id = %s and jc.docstatus = 1
+					jctl.parent = jc.name and jc.work_order = %s and jc.operation_id = %s
+					and jc.docstatus = 1 and IFNULL(jc.is_corrective_job_card, 0) = 0
 			""", (self.work_order, self.operation_id), as_dict=1)
 
 		for data in wo.operations:
@@ -271,7 +410,8 @@
 	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})
+			filters = {"docstatus": 1, "work_order": self.work_order, "operation_id": self.operation_id,
+				"is_corrective_job_card": 0})
 
 	def set_transferred_qty_in_job_card(self, ste_doc):
 		for row in ste_doc.items:
@@ -354,7 +494,11 @@
 				.format(bold(self.operation), work_order), OperationMismatchError)
 
 	def validate_sequence_id(self):
-		if not (self.work_order and self.sequence_id): return
+		if self.is_corrective_job_card:
+			return
+
+		if not (self.work_order and self.sequence_id):
+			return
 
 		current_operation_qty = 0.0
 		data = self.get_current_operation_data()
@@ -376,6 +520,17 @@
 				frappe.throw(_("{0}, complete the operation {1} before the operation {2}.")
 					.format(message, bold(row.operation), bold(self.operation)), OperationSequenceError)
 
+
+@frappe.whitelist()
+def make_time_log(args):
+	if isinstance(args, str):
+		args = json.loads(args)
+
+	args = frappe._dict(args)
+	doc = frappe.get_doc("Job Card", args.job_card_id)
+	doc.validate_sequence_id()
+	doc.add_time_log(args)
+
 @frappe.whitelist()
 def get_operation_details(work_order, operation):
 	if work_order and operation:
@@ -511,3 +666,28 @@
 			events.append(job_card_data)
 
 	return events
+
+@frappe.whitelist()
+def make_corrective_job_card(source_name, operation=None, for_operation=None, target_doc=None):
+	def set_missing_values(source, target):
+		target.is_corrective_job_card = 1
+		target.operation = operation
+		target.for_operation = for_operation
+
+		target.set('time_logs', [])
+		target.set('employee', [])
+		target.set('items', [])
+		target.get_sub_operations()
+		target.get_required_items()
+		target.validate_time_logs()
+
+	doclist = get_mapped_doc("Job Card", source_name, {
+		"Job Card": {
+			"doctype": "Job Card",
+			"field_map": {
+				"name": "for_job_card",
+			},
+		}
+	}, target_doc, set_missing_values)
+
+	return doclist
\ No newline at end of file
diff --git a/erpnext/manufacturing/doctype/job_card_item/job_card_item.json b/erpnext/manufacturing/doctype/job_card_item/job_card_item.json
index 100ef4c..d91530d 100644
--- a/erpnext/manufacturing/doctype/job_card_item/job_card_item.json
+++ b/erpnext/manufacturing/doctype/job_card_item/job_card_item.json
@@ -25,8 +25,7 @@
    "fieldtype": "Link",
    "in_list_view": 1,
    "label": "Item Code",
-   "options": "Item",
-   "read_only": 1
+   "options": "Item"
   },
   {
    "fieldname": "source_warehouse",
@@ -67,8 +66,7 @@
    "fieldname": "required_qty",
    "fieldtype": "Float",
    "in_list_view": 1,
-   "label": "Required Qty",
-   "read_only": 1
+   "label": "Required Qty"
   },
   {
    "fieldname": "column_break_9",
@@ -107,7 +105,7 @@
  "index_web_pages_for_search": 1,
  "istable": 1,
  "links": [],
- "modified": "2021-02-11 13:50:13.804108",
+ "modified": "2021-04-22 18:50:00.003444",
  "modified_by": "Administrator",
  "module": "Manufacturing",
  "name": "Job Card Item",
diff --git a/erpnext/manufacturing/doctype/job_card_operation/__init__.py b/erpnext/manufacturing/doctype/job_card_operation/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/erpnext/manufacturing/doctype/job_card_operation/__init__.py
diff --git a/erpnext/manufacturing/doctype/job_card_operation/job_card_operation.json b/erpnext/manufacturing/doctype/job_card_operation/job_card_operation.json
new file mode 100644
index 0000000..9a8692b
--- /dev/null
+++ b/erpnext/manufacturing/doctype/job_card_operation/job_card_operation.json
@@ -0,0 +1,59 @@
+{
+ "actions": [],
+ "creation": "2020-12-07 16:58:38.449041",
+ "doctype": "DocType",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+  "sub_operation",
+  "completed_time",
+  "status",
+  "completed_qty"
+ ],
+ "fields": [
+  {
+   "default": "Pending",
+   "fieldname": "status",
+   "fieldtype": "Select",
+   "in_list_view": 1,
+   "label": "Status",
+   "options": "Complete\nPause\nPending\nWork In Progress",
+   "read_only": 1
+  },
+  {
+   "description": "In mins",
+   "fieldname": "completed_time",
+   "fieldtype": "Data",
+   "in_list_view": 1,
+   "label": "Completed Time",
+   "read_only": 1
+  },
+  {
+   "fieldname": "sub_operation",
+   "fieldtype": "Link",
+   "in_list_view": 1,
+   "label": "Operation",
+   "options": "Operation",
+   "read_only": 1
+  },
+  {
+   "fieldname": "completed_qty",
+   "fieldtype": "Float",
+   "label": "Completed Qty",
+   "read_only": 1
+  }
+ ],
+ "index_web_pages_for_search": 1,
+ "istable": 1,
+ "links": [],
+ "modified": "2021-03-16 18:24:35.399593",
+ "modified_by": "Administrator",
+ "module": "Manufacturing",
+ "name": "Job Card Operation",
+ "owner": "Administrator",
+ "permissions": [],
+ "quick_entry": 1,
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "track_changes": 1
+}
\ No newline at end of file
diff --git a/erpnext/manufacturing/doctype/job_card_operation/job_card_operation.py b/erpnext/manufacturing/doctype/job_card_operation/job_card_operation.py
new file mode 100644
index 0000000..85d7298
--- /dev/null
+++ b/erpnext/manufacturing/doctype/job_card_operation/job_card_operation.py
@@ -0,0 +1,10 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors
+# For license information, please see license.txt
+
+from __future__ import unicode_literals
+# import frappe
+from frappe.model.document import Document
+
+class JobCardOperation(Document):
+	pass
diff --git a/erpnext/manufacturing/doctype/job_card_time_log/job_card_time_log.json b/erpnext/manufacturing/doctype/job_card_time_log/job_card_time_log.json
index 9dd54dd..a7102d7 100644
--- a/erpnext/manufacturing/doctype/job_card_time_log/job_card_time_log.json
+++ b/erpnext/manufacturing/doctype/job_card_time_log/job_card_time_log.json
@@ -1,14 +1,17 @@
 {
+ "actions": [],
  "creation": "2019-03-08 23:56:43.187569",
  "doctype": "DocType",
  "editable_grid": 1,
  "engine": "InnoDB",
  "field_order": [
+  "employee",
   "from_time",
   "to_time",
   "column_break_2",
   "time_in_mins",
-  "completed_qty"
+  "completed_qty",
+  "operation"
  ],
  "fields": [
   {
@@ -41,10 +44,27 @@
    "in_list_view": 1,
    "label": "Completed Qty",
    "reqd": 1
+  },
+  {
+   "fieldname": "employee",
+   "fieldtype": "Link",
+   "in_list_view": 1,
+   "label": "Employee",
+   "options": "Employee"
+  },
+  {
+   "fieldname": "operation",
+   "fieldtype": "Link",
+   "label": "Operation",
+   "no_copy": 1,
+   "options": "Operation",
+   "read_only": 1
   }
  ],
+ "index_web_pages_for_search": 1,
  "istable": 1,
- "modified": "2019-12-03 12:56:02.285448",
+ "links": [],
+ "modified": "2020-12-23 14:30:00.970916",
  "modified_by": "Administrator",
  "module": "Manufacturing",
  "name": "Job Card Time Log",
diff --git a/erpnext/manufacturing/doctype/manufacturing_settings/manufacturing_settings.json b/erpnext/manufacturing/doctype/manufacturing_settings/manufacturing_settings.json
index b7634da..024f784 100644
--- a/erpnext/manufacturing/doctype/manufacturing_settings/manufacturing_settings.json
+++ b/erpnext/manufacturing/doctype/manufacturing_settings/manufacturing_settings.json
@@ -26,7 +26,10 @@
   "column_break_16",
   "overproduction_percentage_for_work_order",
   "other_settings_section",
-  "update_bom_costs_automatically"
+  "update_bom_costs_automatically",
+  "add_corrective_operation_cost_in_finished_good_valuation",
+  "column_break_23",
+  "make_serial_no_batch_from_work_order"
  ],
  "fields": [
   {
@@ -155,13 +158,30 @@
   {
    "fieldname": "column_break_5",
    "fieldtype": "Column Break"
+  },
+  {
+   "fieldname": "column_break_23",
+   "fieldtype": "Column Break"
+  },
+  {
+   "default": "0",
+   "description": "System will automatically create the serial numbers / batch for the Finished Good on submission of work order",
+   "fieldname": "make_serial_no_batch_from_work_order",
+   "fieldtype": "Check",
+   "label": "Make Serial No / Batch from Work Order"
+  },
+  {
+   "default": "0",
+   "fieldname": "add_corrective_operation_cost_in_finished_good_valuation",
+   "fieldtype": "Check",
+   "label": "Add Corrective Operation Cost in Finished Good Valuation"
   }
  ],
  "icon": "icon-wrench",
  "index_web_pages_for_search": 1,
  "issingle": 1,
  "links": [],
- "modified": "2020-10-13 10:55:43.996581",
+ "modified": "2021-03-16 15:54:38.967341",
  "modified_by": "Administrator",
  "module": "Manufacturing",
  "name": "Manufacturing Settings",
@@ -178,4 +198,4 @@
  "sort_field": "modified",
  "sort_order": "DESC",
  "track_changes": 1
-}
+}
\ No newline at end of file
diff --git a/erpnext/manufacturing/doctype/operation/operation.js b/erpnext/manufacturing/doctype/operation/operation.js
index 5c2aba6..102b678 100644
--- a/erpnext/manufacturing/doctype/operation/operation.js
+++ b/erpnext/manufacturing/doctype/operation/operation.js
@@ -2,7 +2,13 @@
 // For license information, please see license.txt
 
 frappe.ui.form.on('Operation', {
-	refresh: function(frm) {
-
+	setup: function(frm) {
+		frm.set_query('operation', 'sub_operations', function() {
+			return {
+				filters: {
+					'name': ['not in', [frm.doc.name]]
+				}
+			};
+		});
 	}
-});
+});
\ No newline at end of file
diff --git a/erpnext/manufacturing/doctype/operation/operation.json b/erpnext/manufacturing/doctype/operation/operation.json
index c231fba..10a97ed 100644
--- a/erpnext/manufacturing/doctype/operation/operation.json
+++ b/erpnext/manufacturing/doctype/operation/operation.json
@@ -1,167 +1,132 @@
 {
- "allow_copy": 0, 
- "allow_import": 1, 
- "allow_rename": 1, 
- "autoname": "Prompt", 
- "beta": 0, 
- "creation": "2014-11-07 16:20:30.683186", 
- "custom": 0, 
- "docstatus": 0, 
- "doctype": "DocType", 
- "document_type": "Setup", 
- "editable_grid": 0, 
- "engine": "InnoDB", 
+ "actions": [],
+ "allow_import": 1,
+ "allow_rename": 1,
+ "autoname": "Prompt",
+ "creation": "2014-11-07 16:20:30.683186",
+ "doctype": "DocType",
+ "document_type": "Setup",
+ "engine": "InnoDB",
+ "field_order": [
+  "workstation",
+  "data_2",
+  "is_corrective_operation",
+  "job_card_section",
+  "create_job_card_based_on_batch_size",
+  "column_break_6",
+  "batch_size",
+  "sub_operations_section",
+  "sub_operations",
+  "total_operation_time",
+  "section_break_4",
+  "description"
+ ],
  "fields": [
   {
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "fieldname": "workstation", 
-   "fieldtype": "Link", 
-   "hidden": 0, 
-   "ignore_user_permissions": 0, 
-   "ignore_xss_filter": 0, 
-   "in_filter": 0, 
-   "in_list_view": 1, 
-   "in_standard_filter": 1, 
-   "label": "Default Workstation", 
-   "length": 0, 
-   "no_copy": 0, 
-   "options": "Workstation", 
-   "permlevel": 0, 
-   "precision": "", 
-   "print_hide": 0, 
-   "print_hide_if_no_value": 0, 
-   "read_only": 0, 
-   "remember_last_selected_value": 0, 
-   "report_hide": 0, 
-   "reqd": 0, 
-   "search_index": 0, 
-   "set_only_once": 0, 
-   "unique": 0
-  }, 
+   "fieldname": "workstation",
+   "fieldtype": "Link",
+   "in_list_view": 1,
+   "in_standard_filter": 1,
+   "label": "Default Workstation",
+   "options": "Workstation"
+  },
   {
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "fieldname": "section_break_4", 
-   "fieldtype": "Section Break", 
-   "hidden": 0, 
-   "ignore_user_permissions": 0, 
-   "ignore_xss_filter": 0, 
-   "in_filter": 0, 
-   "in_list_view": 0, 
-   "in_standard_filter": 0, 
-   "length": 0, 
-   "no_copy": 0, 
-   "permlevel": 0, 
-   "precision": "", 
-   "print_hide": 0, 
-   "print_hide_if_no_value": 0, 
-   "read_only": 0, 
-   "remember_last_selected_value": 0, 
-   "report_hide": 0, 
-   "reqd": 0, 
-   "search_index": 0, 
-   "set_only_once": 0, 
-   "unique": 0
-  }, 
+   "collapsible": 1,
+   "fieldname": "section_break_4",
+   "fieldtype": "Section Break",
+   "label": "Operation Description"
+  },
   {
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "fieldname": "description", 
-   "fieldtype": "Text", 
-   "hidden": 0, 
-   "ignore_user_permissions": 0, 
-   "ignore_xss_filter": 0, 
-   "in_filter": 0, 
-   "in_list_view": 0, 
-   "in_standard_filter": 0, 
-   "label": "Description", 
-   "length": 0, 
-   "no_copy": 0, 
-   "permlevel": 0, 
-   "precision": "", 
-   "print_hide": 0, 
-   "print_hide_if_no_value": 0, 
-   "read_only": 0, 
-   "remember_last_selected_value": 0, 
-   "report_hide": 0, 
-   "reqd": 0, 
-   "search_index": 0, 
-   "set_only_once": 0, 
-   "unique": 0
+   "fieldname": "description",
+   "fieldtype": "Text",
+   "label": "Description"
+  },
+  {
+   "collapsible": 1,
+   "fieldname": "sub_operations_section",
+   "fieldtype": "Section Break",
+   "label": "Sub Operations"
+  },
+  {
+   "fieldname": "sub_operations",
+   "fieldtype": "Table",
+   "options": "Sub Operation"
+  },
+  {
+   "description": "Time in mins.",
+   "fieldname": "total_operation_time",
+   "fieldtype": "Float",
+   "label": "Total Operation Time",
+   "read_only": 1
+  },
+  {
+   "fieldname": "data_2",
+   "fieldtype": "Column Break"
+  },
+  {
+   "default": "1",
+   "depends_on": "create_job_card_based_on_batch_size",
+   "fieldname": "batch_size",
+   "fieldtype": "Int",
+   "label": "Batch Size",
+   "mandatory_depends_on": "create_job_card_based_on_batch_size"
+  },
+  {
+   "default": "0",
+   "fieldname": "create_job_card_based_on_batch_size",
+   "fieldtype": "Check",
+   "label": "Create Job Card based on Batch Size"
+  },
+  {
+   "collapsible": 1,
+   "fieldname": "job_card_section",
+   "fieldtype": "Section Break",
+   "label": "Job Card"
+  },
+  {
+   "fieldname": "column_break_6",
+   "fieldtype": "Column Break"
+  },
+  {
+   "default": "0",
+   "fieldname": "is_corrective_operation",
+   "fieldtype": "Check",
+   "label": "Is Corrective Operation"
   }
- ], 
- "hide_heading": 0, 
- "hide_toolbar": 0, 
- "icon": "fa fa-wrench", 
- "idx": 0, 
- "image_view": 0, 
- "in_create": 0, 
-
- "is_submittable": 0, 
- "issingle": 0, 
- "istable": 0, 
- "max_attachments": 0, 
- "modified": "2016-11-07 05:28:27.462413", 
- "modified_by": "Administrator", 
- "module": "Manufacturing", 
- "name": "Operation", 
- "name_case": "", 
- "owner": "Administrator", 
+ ],
+ "icon": "fa fa-wrench",
+ "index_web_pages_for_search": 1,
+ "links": [],
+ "modified": "2021-01-12 15:09:23.593338",
+ "modified_by": "Administrator",
+ "module": "Manufacturing",
+ "name": "Operation",
+ "owner": "Administrator",
  "permissions": [
   {
-   "amend": 0, 
-   "apply_user_permissions": 0, 
-   "cancel": 0, 
-   "create": 1, 
-   "delete": 1, 
-   "email": 0, 
-   "export": 1, 
-   "if_owner": 0, 
-   "import": 1, 
-   "is_custom": 0, 
-   "permlevel": 0, 
-   "print": 0, 
-   "read": 1, 
-   "report": 0, 
-   "role": "Manufacturing User", 
-   "set_user_permissions": 0, 
-   "share": 1, 
-   "submit": 0, 
+   "create": 1,
+   "delete": 1,
+   "export": 1,
+   "import": 1,
+   "read": 1,
+   "role": "Manufacturing User",
+   "share": 1,
    "write": 1
-  }, 
+  },
   {
-   "amend": 0, 
-   "apply_user_permissions": 0, 
-   "cancel": 0, 
-   "create": 1, 
-   "delete": 1, 
-   "email": 0, 
-   "export": 1, 
-   "if_owner": 0, 
-   "import": 1, 
-   "is_custom": 0, 
-   "permlevel": 0, 
-   "print": 0, 
-   "read": 1, 
-   "report": 1, 
-   "role": "Manufacturing Manager", 
-   "set_user_permissions": 0, 
-   "share": 1, 
-   "submit": 0, 
+   "create": 1,
+   "delete": 1,
+   "export": 1,
+   "import": 1,
+   "read": 1,
+   "report": 1,
+   "role": "Manufacturing Manager",
+   "share": 1,
    "write": 1
   }
- ], 
- "quick_entry": 1, 
- "read_only": 0, 
- "read_only_onload": 0, 
- "sort_field": "modified", 
- "sort_order": "DESC", 
- "track_seen": 0
+ ],
+ "quick_entry": 1,
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "track_changes": 1
 }
\ No newline at end of file
diff --git a/erpnext/manufacturing/doctype/operation/operation.py b/erpnext/manufacturing/doctype/operation/operation.py
index 69e8329..374f320 100644
--- a/erpnext/manufacturing/doctype/operation/operation.py
+++ b/erpnext/manufacturing/doctype/operation/operation.py
@@ -2,9 +2,34 @@
 # For license information, please see license.txt
 
 from __future__ import unicode_literals
+
+import frappe
+from frappe import _
 from frappe.model.document import Document
 
 class Operation(Document):
 	def validate(self):
 		if not self.description:
 			self.description = self.name
+
+		self.duplicate_sub_operation()
+		self.set_total_time()
+
+	def duplicate_sub_operation(self):
+		operation_list = []
+		for row in self.sub_operations:
+			if row.operation in operation_list:
+				frappe.throw(_("The operation {0} can not add multiple times")
+					.format(frappe.bold(row.operation)))
+
+			if self.name == row.operation:
+				frappe.throw(_("The operation {0} can not be the sub operation")
+					.format(frappe.bold(row.operation)))
+
+			operation_list.append(row.operation)
+
+	def set_total_time(self):
+		self.total_operation_time = 0.0
+
+		for row in self.sub_operations:
+			self.total_operation_time += row.time_in_mins
diff --git a/erpnext/manufacturing/doctype/routing/routing.py b/erpnext/manufacturing/doctype/routing/routing.py
index 8312d74..ece0db7 100644
--- a/erpnext/manufacturing/doctype/routing/routing.py
+++ b/erpnext/manufacturing/doctype/routing/routing.py
@@ -4,14 +4,24 @@
 
 from __future__ import unicode_literals
 import frappe
-from frappe.utils import cint
+from frappe.utils import cint, flt
 from frappe import _
 from frappe.model.document import Document
 
 class Routing(Document):
 	def validate(self):
+		self.calculate_operating_cost()
 		self.set_routing_id()
 
+	def on_update(self):
+		self.calculate_operating_cost()
+
+	def calculate_operating_cost(self):
+		for operation in self.operations:
+			if not operation.hour_rate:
+				operation.hour_rate = frappe.db.get_value("Workstation", operation.workstation, 'hour_rate')
+			operation.operating_cost = flt(flt(operation.hour_rate) * flt(operation.time_in_mins) / 60, 2)
+
 	def set_routing_id(self):
 		sequence_id = 0
 		for row in self.operations:
@@ -21,4 +31,4 @@
 				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
+			sequence_id = row.sequence_id
diff --git a/erpnext/manufacturing/doctype/routing/test_routing.py b/erpnext/manufacturing/doctype/routing/test_routing.py
index 6a38dcf..92f2694 100644
--- a/erpnext/manufacturing/doctype/routing/test_routing.py
+++ b/erpnext/manufacturing/doctype/routing/test_routing.py
@@ -7,9 +7,7 @@
 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):
@@ -48,7 +46,53 @@
 		wo_doc.cancel()
 		wo_doc.delete()
 
+	def test_update_bom_operation_time(self):
+		operations = [
+			{
+				"operation": "Test Operation A",
+				"workstation": "_Test Workstation A",
+				"hour_rate_rent": 300,
+				"hour_rate_labour": 750 ,
+				"time_in_mins": 30
+			},
+			{
+				"operation": "Test Operation B",
+				"workstation": "_Test Workstation B",
+				"hour_rate_labour": 200,
+				"hour_rate_rent": 1000,
+				"time_in_mins": 20
+			}
+		]
+
+		test_routing_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
+			}
+		]
+		setup_operations(operations)
+		routing_doc = create_routing(routing_name="Routing Test", operations=test_routing_operations)
+		bom_doc = setup_bom(item_code="_Testing Item", routing=routing_doc.name, currency = 'INR')
+		self.assertEqual(routing_doc.operations[0].time_in_mins, 30)
+		self.assertEqual(routing_doc.operations[1].time_in_mins, 20)
+		routing_doc.operations[0].time_in_mins = 90
+		routing_doc.operations[1].time_in_mins = 42.2
+		routing_doc.save()
+		bom_doc.update_cost()
+		bom_doc.reload()
+		self.assertEqual(bom_doc.operations[0].time_in_mins, 90)
+		self.assertEqual(bom_doc.operations[1].time_in_mins, 42.2)
+
+
 def setup_operations(rows):
+	from erpnext.manufacturing.doctype.workstation.test_workstation import make_workstation
+	from erpnext.manufacturing.doctype.operation.test_operation import make_operation
 	for row in rows:
 		make_workstation(row)
 		make_operation(row)
@@ -61,12 +105,14 @@
 
 	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)
+			doc.delete_key('operations')
+			for operation in args.operations:
+				doc.append("operations", operation)
+
+			doc.save()
 
 	return doc
 
@@ -91,7 +137,7 @@
 	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)
+			routing = args.routing, with_operations=1, currency = args.currency)
 	else:
 		bom_doc = frappe.get_doc("BOM", name)
 
diff --git a/erpnext/manufacturing/doctype/sub_operation/__init__.py b/erpnext/manufacturing/doctype/sub_operation/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/erpnext/manufacturing/doctype/sub_operation/__init__.py
diff --git a/erpnext/manufacturing/doctype/sub_operation/sub_operation.js b/erpnext/manufacturing/doctype/sub_operation/sub_operation.js
new file mode 100644
index 0000000..be9db6a
--- /dev/null
+++ b/erpnext/manufacturing/doctype/sub_operation/sub_operation.js
@@ -0,0 +1,8 @@
+// Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors
+// For license information, please see license.txt
+
+frappe.ui.form.on('Sub Operation', {
+	// refresh: function(frm) {
+
+	// }
+});
diff --git a/erpnext/manufacturing/doctype/sub_operation/sub_operation.json b/erpnext/manufacturing/doctype/sub_operation/sub_operation.json
new file mode 100644
index 0000000..f63d2b9
--- /dev/null
+++ b/erpnext/manufacturing/doctype/sub_operation/sub_operation.json
@@ -0,0 +1,51 @@
+{
+ "actions": [],
+ "creation": "2020-12-07 15:39:47.488519",
+ "doctype": "DocType",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+  "operation",
+  "time_in_mins",
+  "column_break_5",
+  "description"
+ ],
+ "fields": [
+  {
+   "fieldname": "operation",
+   "fieldtype": "Link",
+   "in_list_view": 1,
+   "label": "Operation",
+   "options": "Operation"
+  },
+  {
+   "description": "Time in mins",
+   "fieldname": "time_in_mins",
+   "fieldtype": "Float",
+   "in_list_view": 1,
+   "label": "Operation Time"
+  },
+  {
+   "fieldname": "column_break_5",
+   "fieldtype": "Column Break"
+  },
+  {
+   "fieldname": "description",
+   "fieldtype": "Small Text",
+   "label": "Description"
+  }
+ ],
+ "index_web_pages_for_search": 1,
+ "istable": 1,
+ "links": [],
+ "modified": "2020-12-07 18:09:18.005578",
+ "modified_by": "Administrator",
+ "module": "Manufacturing",
+ "name": "Sub Operation",
+ "owner": "Administrator",
+ "permissions": [],
+ "quick_entry": 1,
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "track_changes": 1
+}
\ No newline at end of file
diff --git a/erpnext/manufacturing/doctype/sub_operation/sub_operation.py b/erpnext/manufacturing/doctype/sub_operation/sub_operation.py
new file mode 100644
index 0000000..f4b2775
--- /dev/null
+++ b/erpnext/manufacturing/doctype/sub_operation/sub_operation.py
@@ -0,0 +1,10 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors
+# For license information, please see license.txt
+
+from __future__ import unicode_literals
+# import frappe
+from frappe.model.document import Document
+
+class SubOperation(Document):
+	pass
diff --git a/erpnext/manufacturing/doctype/sub_operation/test_sub_operation.py b/erpnext/manufacturing/doctype/sub_operation/test_sub_operation.py
new file mode 100644
index 0000000..d3410ca
--- /dev/null
+++ b/erpnext/manufacturing/doctype/sub_operation/test_sub_operation.py
@@ -0,0 +1,10 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors
+# See license.txt
+from __future__ import unicode_literals
+
+# import frappe
+import unittest
+
+class TestSubOperation(unittest.TestCase):
+	pass
diff --git a/erpnext/manufacturing/doctype/work_order/work_order.js b/erpnext/manufacturing/doctype/work_order/work_order.js
index 3e5a72d..5120485 100644
--- a/erpnext/manufacturing/doctype/work_order/work_order.js
+++ b/erpnext/manufacturing/doctype/work_order/work_order.js
@@ -141,8 +141,7 @@
 		}
 
 		if (frm.doc.docstatus === 1
-			&& frm.doc.operations && frm.doc.operations.length
-			&& frm.doc.qty != frm.doc.material_transferred_for_manufacturing) {
+			&& frm.doc.operations && frm.doc.operations.length) {
 
 			const not_completed = frm.doc.operations.filter(d => {
 				if(d.status != 'Completed') {
@@ -190,35 +189,41 @@
 		const dialog = frappe.prompt({fieldname: 'operations', fieldtype: 'Table', label: __('Operations'),
 			fields: [
 				{
-					fieldtype:'Link',
-					fieldname:'operation',
+					fieldtype: 'Link',
+					fieldname: 'operation',
 					label: __('Operation'),
-					read_only:1,
-					in_list_view:1
+					read_only: 1,
+					in_list_view: 1
 				},
 				{
-					fieldtype:'Link',
-					fieldname:'workstation',
+					fieldtype: 'Link',
+					fieldname: 'workstation',
 					label: __('Workstation'),
-					read_only:1,
-					in_list_view:1
+					read_only: 1,
+					in_list_view: 1
 				},
 				{
-					fieldtype:'Data',
-					fieldname:'name',
+					fieldtype: 'Data',
+					fieldname: 'name',
 					label: __('Operation Id')
 				},
 				{
-					fieldtype:'Float',
-					fieldname:'pending_qty',
+					fieldtype: 'Float',
+					fieldname: 'pending_qty',
 					label: __('Pending Qty'),
 				},
 				{
-					fieldtype:'Float',
-					fieldname:'qty',
+					fieldtype: 'Float',
+					fieldname: 'qty',
 					label: __('Quantity to Manufacture'),
-					read_only:0,
-					in_list_view:1,
+					read_only: 0,
+					in_list_view: 1,
+				},
+				{
+					fieldtype: 'Float',
+					fieldname: 'batch_size',
+					label: __('Batch Size'),
+					read_only: 1
 				},
 			],
 			data: operations_data,
@@ -229,9 +234,13 @@
 		}, function(data) {
 			frappe.call({
 				method: "erpnext.manufacturing.doctype.work_order.work_order.make_job_card",
+				freeze: true,
 				args: {
 					work_order: frm.doc.name,
 					operations: data.operations,
+				},
+				callback: function() {
+					frm.reload_doc();
 				}
 			});
 		}, __("Job Card"), __("Create"));
@@ -243,13 +252,16 @@
 			if(data.completed_qty != frm.doc.qty) {
 				pending_qty = frm.doc.qty - flt(data.completed_qty);
 
-				dialog.fields_dict.operations.df.data.push({
-					'name': data.name,
-					'operation': data.operation,
-					'workstation': data.workstation,
-					'qty': pending_qty,
-					'pending_qty': pending_qty,
-				});
+				if (pending_qty) {
+					dialog.fields_dict.operations.df.data.push({
+						'name': data.name,
+						'operation': data.operation,
+						'workstation': data.workstation,
+						'batch_size': data.batch_size,
+						'qty': pending_qty,
+						'pending_qty': pending_qty
+					});
+				}
 			}
 		});
 		dialog.fields_dict.operations.grid.refresh();
@@ -704,6 +716,8 @@
 	stop_work_order: function(frm, status) {
 		frappe.call({
 			method: "erpnext.manufacturing.doctype.work_order.work_order.stop_unstop",
+			freeze: true,
+			freeze_message: __("Updating Work Order status"),
 			args: {
 				work_order: frm.doc.name,
 				status: status
diff --git a/erpnext/manufacturing/doctype/work_order/work_order.json b/erpnext/manufacturing/doctype/work_order/work_order.json
index cd9edee..44d76d2 100644
--- a/erpnext/manufacturing/doctype/work_order/work_order.json
+++ b/erpnext/manufacturing/doctype/work_order/work_order.json
@@ -21,6 +21,12 @@
   "produced_qty",
   "sales_order",
   "project",
+  "serial_no_and_batch_for_finished_good_section",
+  "has_serial_no",
+  "has_batch_no",
+  "column_break_17",
+  "serial_no",
+  "batch_size",
   "settings_section",
   "allow_alternative_item",
   "use_multi_level_bom",
@@ -52,6 +58,7 @@
   "actual_operating_cost",
   "additional_operating_cost",
   "column_break_24",
+  "corrective_operation_cost",
   "total_operating_cost",
   "more_info",
   "description",
@@ -488,6 +495,57 @@
    "fieldtype": "Float",
    "label": "Lead Time",
    "read_only": 1
+  },
+  {
+   "collapsible": 1,
+   "depends_on": "eval:!doc.__islocal",
+   "fieldname": "serial_no_and_batch_for_finished_good_section",
+   "fieldtype": "Section Break",
+   "label": "Serial No and Batch for Finished Good"
+  },
+  {
+   "fieldname": "column_break_17",
+   "fieldtype": "Column Break"
+  },
+  {
+   "default": "0",
+   "fetch_from": "production_item.has_serial_no",
+   "fieldname": "has_serial_no",
+   "fieldtype": "Check",
+   "label": "Has Serial No",
+   "read_only": 1
+  },
+  {
+   "default": "0",
+   "fetch_from": "production_item.has_batch_no",
+   "fieldname": "has_batch_no",
+   "fieldtype": "Check",
+   "label": "Has Batch No",
+   "read_only": 1
+  },
+  {
+   "depends_on": "has_serial_no",
+   "fieldname": "serial_no",
+   "fieldtype": "Small Text",
+   "label": "Serial Nos",
+   "no_copy": 1
+  },
+  {
+   "default": "0",
+   "depends_on": "has_batch_no",
+   "fieldname": "batch_size",
+   "fieldtype": "Float",
+   "label": "Batch Size"
+  },
+  {
+   "allow_on_submit": 1,
+   "description": "From Corrective Job Card",
+   "fieldname": "corrective_operation_cost",
+   "fieldtype": "Currency",
+   "label": "Corrective Operation Cost",
+   "no_copy": 1,
+   "print_hide": 1,
+   "read_only": 1
   }
  ],
  "icon": "fa fa-cogs",
@@ -495,7 +553,7 @@
  "image_field": "image",
  "is_submittable": 1,
  "links": [],
- "modified": "2021-03-16 13:27:51.116484",
+ "modified": "2021-06-20 15:19:14.902699",
  "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 2600790..e343ed2 100644
--- a/erpnext/manufacturing/doctype/work_order/work_order.py
+++ b/erpnext/manufacturing/doctype/work_order/work_order.py
@@ -19,14 +19,16 @@
 from erpnext.stock.utils import get_bin, validate_warehouse_company, get_latest_stock_qty
 from erpnext.utilities.transaction_base import validate_uom_is_integer
 from frappe.model.mapper import get_mapped_doc
+from erpnext.stock.doctype.batch.batch import make_batch
+from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos, get_auto_serial_nos, auto_make_serial_nos
 
 class OverProductionError(frappe.ValidationError): pass
 class CapacityError(frappe.ValidationError): pass
 class StockOverProductionError(frappe.ValidationError): pass
 class OperationTooLongError(frappe.ValidationError): pass
 class ItemHasVariantError(frappe.ValidationError): pass
-
-from six import string_types
+class SerialNoQtyError(frappe.ValidationError):
+	pass
 
 form_grid_templates = {
 	"operations": "templates/form_grid/work_order_grid.html"
@@ -127,7 +129,9 @@
 
 		variable_cost = self.actual_operating_cost if self.actual_operating_cost \
 			else self.planned_operating_cost
-		self.total_operating_cost = flt(self.additional_operating_cost) + flt(variable_cost)
+
+		self.total_operating_cost = (flt(self.additional_operating_cost)
+			+ flt(variable_cost) + flt(self.corrective_operation_cost))
 
 	def validate_work_order_against_so(self):
 		# already ordered qty
@@ -235,12 +239,15 @@
 
 		production_plan.run_method("update_produced_qty", produced_qty, self.production_plan_item)
 
+	def before_submit(self):
+		self.create_serial_no_batch_no()
+
 	def on_submit(self):
 		if not self.wip_warehouse:
 			frappe.throw(_("Work-in-Progress Warehouse is required before Submit"))
 		if not self.fg_warehouse:
 			frappe.throw(_("For Warehouse is required before Submit"))
-		
+
 		if self.production_plan and frappe.db.exists('Production Plan Item Reference',{'parent':self.production_plan}):
 			self.update_work_order_qty_in_combined_so()
 		else:
@@ -260,12 +267,76 @@
 			self.update_work_order_qty_in_combined_so()
 		else:
 			self.update_work_order_qty_in_so()
-			
+
 		self.delete_job_card()
 		self.update_completed_qty_in_material_request()
 		self.update_planned_qty()
 		self.update_ordered_qty()
 		self.update_reserved_qty_for_production()
+		self.delete_auto_created_batch_and_serial_no()
+
+	def create_serial_no_batch_no(self):
+		if not (self.has_serial_no or self.has_batch_no):
+			return
+
+		if not cint(frappe.db.get_single_value("Manufacturing Settings", "make_serial_no_batch_from_work_order")):
+			return
+
+		if self.has_batch_no:
+			self.create_batch_for_finished_good()
+
+		args = {
+			"item_code": self.production_item,
+			"work_order": self.name
+		}
+
+		if self.has_serial_no:
+			self.make_serial_nos(args)
+
+	def create_batch_for_finished_good(self):
+		total_qty = self.qty
+		if not self.batch_size:
+			self.batch_size = total_qty
+
+		while total_qty > 0:
+			qty = self.batch_size
+			if self.batch_size >= total_qty:
+				qty = total_qty
+
+			if total_qty > self.batch_size:
+				total_qty -= self.batch_size
+			else:
+				qty = total_qty
+				total_qty = 0
+
+			make_batch(frappe._dict({
+				"item": self.production_item,
+				"qty_to_produce": qty,
+				"reference_doctype": self.doctype,
+				"reference_name": self.name
+			}))
+
+	def delete_auto_created_batch_and_serial_no(self):
+		for row in frappe.get_all("Serial No", filters = {"work_order": self.name}):
+			frappe.delete_doc("Serial No", row.name)
+			self.db_set("serial_no", "")
+
+		for row in frappe.get_all("Batch", filters = {"reference_name": self.name}):
+			frappe.delete_doc("Batch", row.name)
+
+	def make_serial_nos(self, args):
+		serial_no_series = frappe.get_cached_value("Item", self.production_item, "serial_no_series")
+		if serial_no_series:
+			self.serial_no = get_auto_serial_nos(serial_no_series, self.qty)
+
+		if self.serial_no:
+			args.update({"serial_no": self.serial_no, "actual_qty": self.qty})
+			auto_make_serial_nos(args)
+
+		serial_nos_length = len(get_serial_nos(self.serial_no))
+		if serial_nos_length != self.qty:
+			frappe.throw(_("{0} Serial Numbers required for Item {1}. You have provided {2}.")
+				.format(self.qty, self.production_item, serial_nos_length), SerialNoQtyError)
 
 	def create_job_card(self):
 		manufacturing_settings_doc = frappe.get_doc("Manufacturing Settings")
@@ -273,32 +344,40 @@
 		enable_capacity_planning = not cint(manufacturing_settings_doc.disable_capacity_planning)
 		plan_days = cint(manufacturing_settings_doc.capacity_planning_for_days) or 30
 
-		for i, row in enumerate(self.operations):
-			self.set_operation_start_end_time(i, row)
-
-			if not row.workstation:
-				frappe.throw(_("Row {0}: select the workstation against the operation {1}")
-					.format(row.idx, row.operation))
-
-			original_start_time = row.planned_start_time
-			job_card_doc = create_job_card(self, row,
-				enable_capacity_planning=enable_capacity_planning, auto_create=True)
-
-			if enable_capacity_planning and job_card_doc:
-				row.planned_start_time = job_card_doc.time_logs[-1].from_time
-				row.planned_end_time = job_card_doc.time_logs[-1].to_time
-
-				if date_diff(row.planned_start_time, original_start_time) > plan_days:
-					frappe.message_log.pop()
-					frappe.throw(_("Unable to find the time slot in the next {0} days for the operation {1}.")
-						.format(plan_days, row.operation), CapacityError)
-
-				row.db_update()
+		for index, row in enumerate(self.operations):
+			qty = self.qty
+			while qty > 0:
+				qty = split_qty_based_on_batch_size(self, row, qty)
+				if row.job_card_qty > 0:
+					self.prepare_data_for_job_card(row, index,
+						plan_days, enable_capacity_planning)
 
 		planned_end_date = self.operations and self.operations[-1].planned_end_time
 		if planned_end_date:
 			self.db_set("planned_end_date", planned_end_date)
 
+	def prepare_data_for_job_card(self, row, index, plan_days, enable_capacity_planning):
+		self.set_operation_start_end_time(index, row)
+
+		if not row.workstation:
+			frappe.throw(_("Row {0}: select the workstation against the operation {1}")
+				.format(row.idx, row.operation))
+
+		original_start_time = row.planned_start_time
+		job_card_doc = create_job_card(self, row, auto_create=True,
+			enable_capacity_planning=enable_capacity_planning)
+
+		if enable_capacity_planning and job_card_doc:
+			row.planned_start_time = job_card_doc.time_logs[-1].from_time
+			row.planned_end_time = job_card_doc.time_logs[-1].to_time
+
+			if date_diff(row.planned_start_time, original_start_time) > plan_days:
+				frappe.message_log.pop()
+				frappe.throw(_("Unable to find the time slot in the next {0} days for the operation {1}.")
+					.format(plan_days, row.operation), CapacityError)
+
+			row.db_update()
+
 	def set_operation_start_end_time(self, idx, row):
 		"""Set start and end time for given operation. If first operation, set start as
 		`planned_start_date`, else add time diff to end time of earlier operation."""
@@ -365,7 +444,7 @@
 		work_order_qty = qty[0][0] if qty and qty[0][0] else 0
 		frappe.db.set_value('Sales Order Item',
 			self.sales_order_item, 'work_order_qty', flt(work_order_qty/total_bundle_qty, 2))
-		
+
 	def update_work_order_qty_in_combined_so(self):
 		total_bundle_qty = 1
 		if self.product_bundle_item:
@@ -378,7 +457,7 @@
 
 		prod_plan = frappe.get_doc('Production Plan', self.production_plan)
 		item_reference = frappe.get_value('Production Plan Item', self.production_plan_item, 'sales_order_item')
-		
+
 		for plan_reference in prod_plan.prod_plan_references:
 			work_order_qty = 0.0
 			if plan_reference.item_reference == item_reference:
@@ -386,7 +465,7 @@
 					work_order_qty = flt(plan_reference.qty) / total_bundle_qty
 				frappe.db.set_value('Sales Order Item',
 					plan_reference.sales_order_item, 'work_order_qty', work_order_qty)
-	
+
 	def update_completed_qty_in_material_request(self):
 		if self.material_request:
 			frappe.get_doc("Material Request", self.material_request).update_completed_qty([self.material_request_item])
@@ -669,6 +748,17 @@
 		bom.set_bom_material_details()
 		return bom
 
+	def update_batch_produced_qty(self, stock_entry_doc):
+		if not cint(frappe.db.get_single_value("Manufacturing Settings", "make_serial_no_batch_from_work_order")):
+			return
+
+		for row in stock_entry_doc.items:
+			if row.batch_no and (row.is_finished_item or row.is_scrap_item):
+				qty = frappe.get_all("Stock Entry Detail", filters = {"batch_no": row.batch_no, "docstatus": 1},
+					or_filters= {"is_finished_item": 1, "is_scrap_item": 1}, fields = ["sum(qty)"], as_list=1)[0][0]
+
+				frappe.db.set_value("Batch", row.batch_no, "produced_qty", flt(qty))
+
 @frappe.whitelist()
 @frappe.validate_and_sanitize_search_inputs
 def get_bom_operations(doctype, txt, searchfield, start, page_len, filters):
@@ -746,7 +836,7 @@
 	return wo_doc
 
 def add_variant_item(variant_items, wo_doc, bom_no, table_name="items"):
-	if isinstance(variant_items, string_types):
+	if isinstance(variant_items, str):
 		variant_items = json.loads(variant_items)
 
 	for item in variant_items:
@@ -826,6 +916,7 @@
 
 	stock_entry.set_stock_entry_type()
 	stock_entry.get_items()
+	stock_entry.set_serial_no_batch_for_finished_good()
 	return stock_entry.as_dict()
 
 @frappe.whitelist()
@@ -867,13 +958,47 @@
 
 @frappe.whitelist()
 def make_job_card(work_order, operations):
-	if isinstance(operations, string_types):
+	if isinstance(operations, str):
 		operations = json.loads(operations)
 
 	work_order = frappe.get_doc('Work Order', work_order)
 	for row in operations:
+		row = frappe._dict(row)
 		validate_operation_data(row)
-		create_job_card(work_order, row, row.get("qty"), auto_create=True)
+		qty = row.get("qty")
+		while qty > 0:
+			qty = split_qty_based_on_batch_size(work_order, row, qty)
+			if row.job_card_qty > 0:
+				create_job_card(work_order, row, auto_create=True)
+
+def split_qty_based_on_batch_size(wo_doc, row, qty):
+	if not cint(frappe.db.get_value("Operation",
+		row.operation, "create_job_card_based_on_batch_size")):
+		row.batch_size = row.get("qty") or wo_doc.qty
+
+	row.job_card_qty = row.batch_size
+	if row.batch_size and qty >= row.batch_size:
+		qty -= row.batch_size
+	elif qty > 0:
+		row.job_card_qty = qty
+		qty = 0
+
+	get_serial_nos_for_job_card(row, wo_doc)
+
+	return qty
+
+def get_serial_nos_for_job_card(row, wo_doc):
+	if not wo_doc.serial_no:
+		return
+
+	serial_nos = get_serial_nos(wo_doc.serial_no)
+	used_serial_nos = []
+	for d in frappe.get_all('Job Card', fields=['serial_no'],
+		filters={'docstatus': ('<', 2), 'work_order': wo_doc.name, 'operation_id': row.name}):
+		used_serial_nos.extend(get_serial_nos(d.serial_no))
+
+	serial_nos = sorted(list(set(serial_nos) - set(used_serial_nos)))
+	row.serial_no = '\n'.join(serial_nos[0:row.job_card_qty])
 
 def validate_operation_data(row):
 	if row.get("qty") <= 0:
@@ -892,20 +1017,22 @@
 			)
 		)
 
-def create_job_card(work_order, row, qty=0, enable_capacity_planning=False, auto_create=False):
+def create_job_card(work_order, row, enable_capacity_planning=False, auto_create=False):
 	doc = frappe.new_doc("Job Card")
 	doc.update({
 		'work_order': work_order.name,
 		'operation': row.get("operation"),
 		'workstation': row.get("workstation"),
 		'posting_date': nowdate(),
-		'for_quantity': qty or work_order.get('qty', 0),
+		'for_quantity': row.job_card_qty or work_order.get('qty', 0),
 		'operation_id': row.get("name"),
 		'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
+		'wip_warehouse': work_order.wip_warehouse,
+		'hour_rate': row.get("hour_rate"),
+		'serial_no': row.get("serial_no")
 	})
 
 	if work_order.transfer_material_against == 'Job Card' and not work_order.skip_transfer:
diff --git a/erpnext/manufacturing/doctype/work_order/work_order_dashboard.py b/erpnext/manufacturing/doctype/work_order/work_order_dashboard.py
index 87c090f..9aa0715 100644
--- a/erpnext/manufacturing/doctype/work_order/work_order_dashboard.py
+++ b/erpnext/manufacturing/doctype/work_order/work_order_dashboard.py
@@ -4,10 +4,17 @@
 def get_data():
 	return {
 		'fieldname': 'work_order',
+		'non_standard_fieldnames': {
+			'Batch': 'reference_name'
+		},
 		'transactions': [
 			{
 				'label': _('Transactions'),
 				'items': ['Stock Entry', 'Job Card', 'Pick List']
+			},
+			{
+				'label': _('Reference'),
+				'items': ['Serial No', 'Batch']
 			}
 		]
 	}
\ No newline at end of file
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 8c5cde9..6d8fb80 100644
--- a/erpnext/manufacturing/doctype/work_order_operation/work_order_operation.json
+++ b/erpnext/manufacturing/doctype/work_order_operation/work_order_operation.json
@@ -8,8 +8,9 @@
   "details",
   "operation",
   "bom",
-  "sequence_id",
+  "column_break_4",
   "description",
+  "sequence_id",
   "col_break1",
   "completed_qty",
   "status",
@@ -195,12 +196,16 @@
    "label": "Sequence ID",
    "print_hide": 1,
    "read_only": 1
+  },
+  {
+   "fieldname": "column_break_4",
+   "fieldtype": "Column Break"
   }
  ],
  "index_web_pages_for_search": 1,
  "istable": 1,
  "links": [],
- "modified": "2020-10-14 12:58:49.241252",
+ "modified": "2021-01-12 14:48:31.061286",
  "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 c6699be..9b73aca 100644
--- a/erpnext/manufacturing/doctype/workstation/test_workstation.py
+++ b/erpnext/manufacturing/doctype/workstation/test_workstation.py
@@ -1,16 +1,19 @@
 # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors and Contributors
 # See license.txt
 from __future__ import unicode_literals
+from erpnext.manufacturing.doctype.operation.test_operation import make_operation
 
 import frappe
 import unittest
 from erpnext.manufacturing.doctype.workstation.workstation import check_if_within_operating_hours, NotInWorkingHoursError, WorkstationHolidayError
+from erpnext.manufacturing.doctype.routing.test_routing import setup_bom, create_routing
+from frappe.test_runner import make_test_records
 
 test_dependencies = ["Warehouse"]
 test_records = frappe.get_test_records('Workstation')
+make_test_records('Workstation')
 
 class TestWorkstation(unittest.TestCase):
-
 	def test_validate_timings(self):
 		check_if_within_operating_hours("_Test Workstation 1", "Operation 1", "2013-02-02 11:00:00", "2013-02-02 19:00:00")
 		check_if_within_operating_hours("_Test Workstation 1", "Operation 1", "2013-02-02 10:00:00", "2013-02-02 20:00:00")
@@ -21,6 +24,58 @@
 		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 test_update_bom_operation_rate(self):
+		operations = [
+			{
+				"operation": "Test Operation A",
+				"workstation": "_Test Workstation A",
+				"hour_rate_rent": 300,
+				"time_in_mins": 60
+			},
+			{
+				"operation": "Test Operation B",
+				"workstation": "_Test Workstation B",
+				"hour_rate_rent": 1000,
+				"time_in_mins": 60
+			}
+		]
+
+		for row in operations:
+			make_workstation(row)
+			make_operation(row)
+
+		test_routing_operations = [
+			{
+				"operation": "Test Operation A",
+				"workstation": "_Test Workstation A",
+				"time_in_mins": 60
+			},
+			{
+				"operation": "Test Operation B",
+				"workstation": "_Test Workstation A",
+				"time_in_mins": 60
+			}
+		]
+		routing_doc = create_routing(routing_name = "Routing Test", operations=test_routing_operations)
+		bom_doc = setup_bom(item_code="_Testing Item", routing=routing_doc.name, currency="INR")
+		w1 = frappe.get_doc("Workstation", "_Test Workstation A")
+		#resets values
+		w1.hour_rate_rent = 300
+		w1.hour_rate_labour = 0
+		w1.save()
+		bom_doc.update_cost()
+		bom_doc.reload()
+		self.assertEqual(w1.hour_rate, 300)
+		self.assertEqual(bom_doc.operations[0].hour_rate, 300)
+		w1.hour_rate_rent = 250
+		w1.save()
+		#updating after setting new rates in workstations
+		bom_doc.update_cost()
+		bom_doc.reload()
+		self.assertEqual(w1.hour_rate, 250)
+		self.assertEqual(bom_doc.operations[0].hour_rate, 250)
+		self.assertEqual(bom_doc.operations[1].hour_rate, 250)
+
 def make_workstation(*args, **kwargs):
 	args = args if args else kwargs
 	if isinstance(args, tuple):
@@ -34,9 +89,10 @@
 			"doctype": "Workstation",
 			"workstation_name": workstation_name
 		})
-
+		doc.hour_rate_rent = args.get("hour_rate_rent")
+		doc.hour_rate_labour = args.get("hour_rate_labour")
 		doc.insert()
 
 		return doc
 	except frappe.DuplicateEntryError:
-		return frappe.get_doc("Workstation", workstation_name)
\ No newline at end of file
+		return frappe.get_doc("Workstation", workstation_name)
diff --git a/erpnext/manufacturing/doctype/workstation/workstation.py b/erpnext/manufacturing/doctype/workstation/workstation.py
index 3512e59..f4483f7 100644
--- a/erpnext/manufacturing/doctype/workstation/workstation.py
+++ b/erpnext/manufacturing/doctype/workstation/workstation.py
@@ -39,7 +39,8 @@
 
 	def update_bom_operation(self):
 		bom_list = frappe.db.sql("""select DISTINCT parent from `tabBOM Operation`
-			where workstation = %s""", self.name)
+			where workstation = %s and parenttype = 'routing' """, self.name)
+
 		for bom_no in bom_list:
 			frappe.db.sql("""update `tabBOM Operation` set hour_rate = %s
 				where parent = %s and workstation = %s""",
@@ -71,7 +72,7 @@
 def is_within_operating_hours(workstation, operation, from_datetime, to_datetime):
 	operation_length = time_diff_in_seconds(to_datetime, from_datetime)
 	workstation = frappe.get_doc("Workstation", workstation)
-	
+
 	if not workstation.working_hours:
 		return
 
diff --git a/erpnext/manufacturing/report/cost_of_poor_quality_report/__init__.py b/erpnext/manufacturing/report/cost_of_poor_quality_report/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/erpnext/manufacturing/report/cost_of_poor_quality_report/__init__.py
diff --git a/erpnext/manufacturing/report/cost_of_poor_quality_report/cost_of_poor_quality_report.js b/erpnext/manufacturing/report/cost_of_poor_quality_report/cost_of_poor_quality_report.js
new file mode 100644
index 0000000..97e7e0a
--- /dev/null
+++ b/erpnext/manufacturing/report/cost_of_poor_quality_report/cost_of_poor_quality_report.js
@@ -0,0 +1,105 @@
+// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors
+// For license information, please see license.txt
+/* eslint-disable */
+
+frappe.query_reports["Cost of Poor Quality Report"] = {
+	"filters": [
+		{
+			label: __("Company"),
+			fieldname: "company",
+			fieldtype: "Link",
+			options: "Company",
+			default: frappe.defaults.get_user_default("Company"),
+			reqd: 1
+		},
+		{
+			label: __("From Date"),
+			fieldname:"from_date",
+			fieldtype: "Datetime",
+			default: frappe.datetime.convert_to_system_tz(frappe.datetime.add_months(frappe.datetime.now_datetime(), -1)),
+			reqd: 1
+		},
+		{
+			label: __("To Date"),
+			fieldname:"to_date",
+			fieldtype: "Datetime",
+			default: frappe.datetime.now_datetime(),
+			reqd: 1,
+		},
+		{
+			label: __("Job Card"),
+			fieldname: "name",
+			fieldtype: "Link",
+			options: "Job Card",
+			get_query: function() {
+				return {
+					filters: {
+						is_corrective_job_card: 1,
+						docstatus: 1
+					}
+				}
+			}
+		},
+		{
+			label: __("Work Order"),
+			fieldname: "work_order",
+			fieldtype: "Link",
+			options: "Work Order"
+		},
+		{
+			label: __("Operation"),
+			fieldname: "operation",
+			fieldtype: "Link",
+			options: "Operation",
+			get_query: function() {
+				return {
+					filters: {
+						is_corrective_operation: 1
+					}
+				}
+			}
+		},
+		{
+			label: __("Workstation"),
+			fieldname: "workstation",
+			fieldtype: "Link",
+			options: "Workstation"
+		},
+		{
+			label: __("Item"),
+			fieldname: "production_item",
+			fieldtype: "Link",
+			options: "Item"
+		},
+		{
+			label: __("Serial No"),
+			fieldname: "serial_no",
+			fieldtype: "Link",
+			options: "Serial No",
+			depends_on: "eval: doc.production_item",
+			get_query: function() {
+				var item_code = frappe.query_report.get_filter_value('production_item');
+				return {
+					filters: {
+						item_code: item_code
+					}
+				}
+			}
+		},
+		{
+			label: __("Batch No"),
+			fieldname: "batch_no",
+			fieldtype: "Link",
+			options: "Batch No",
+			depends_on: "eval: doc.production_item",
+			get_query: function() {
+				var item_code = frappe.query_report.get_filter_value('production_item');
+				return {
+					filters: {
+						item: item_code
+					}
+				}
+			}
+		},
+	]
+};
diff --git a/erpnext/manufacturing/report/cost_of_poor_quality_report/cost_of_poor_quality_report.json b/erpnext/manufacturing/report/cost_of_poor_quality_report/cost_of_poor_quality_report.json
new file mode 100644
index 0000000..ee63bc1
--- /dev/null
+++ b/erpnext/manufacturing/report/cost_of_poor_quality_report/cost_of_poor_quality_report.json
@@ -0,0 +1,33 @@
+{
+ "add_total_row": 0,
+ "columns": [],
+ "creation": "2021-01-11 11:10:58.292896",
+ "disable_prepared_report": 0,
+ "disabled": 0,
+ "docstatus": 0,
+ "doctype": "Report",
+ "filters": [],
+ "idx": 0,
+ "is_standard": "Yes",
+ "json": "{}",
+ "modified": "2021-01-11 11:11:03.594242",
+ "modified_by": "Administrator",
+ "module": "Manufacturing",
+ "name": "Cost of Poor Quality Report",
+ "owner": "Administrator",
+ "prepared_report": 0,
+ "ref_doctype": "Job Card",
+ "report_name": "Cost of Poor Quality Report",
+ "report_type": "Script Report",
+ "roles": [
+  {
+   "role": "System Manager"
+  },
+  {
+   "role": "Manufacturing User"
+  },
+  {
+   "role": "Manufacturing Manager"
+  }
+ ]
+}
\ No newline at end of file
diff --git a/erpnext/manufacturing/report/cost_of_poor_quality_report/cost_of_poor_quality_report.py b/erpnext/manufacturing/report/cost_of_poor_quality_report/cost_of_poor_quality_report.py
new file mode 100644
index 0000000..9f81e7d
--- /dev/null
+++ b/erpnext/manufacturing/report/cost_of_poor_quality_report/cost_of_poor_quality_report.py
@@ -0,0 +1,127 @@
+# Copyright (c) 2013, Frappe Technologies Pvt. Ltd. and contributors
+# For license information, please see license.txt
+
+from __future__ import unicode_literals
+import frappe
+from frappe import _
+from frappe.utils import flt
+
+def execute(filters=None):
+	columns, data = [], []
+
+	columns = get_columns(filters)
+	data = get_data(filters)
+
+	return columns, data
+
+def get_data(report_filters):
+	data = []
+	operations = frappe.get_all("Operation", filters = {"is_corrective_operation": 1})
+	if operations:
+		operations = [d.name for d in operations]
+		fields = ["production_item as item_code", "item_name", "work_order", "operation",
+			"workstation", "total_time_in_mins", "name", "hour_rate", "serial_no", "batch_no"]
+
+		filters = get_filters(report_filters, operations)
+
+		job_cards = frappe.get_all("Job Card", fields = fields,
+			filters = filters)
+
+		for row in job_cards:
+			row.operating_cost = flt(row.hour_rate) * (flt(row.total_time_in_mins) / 60.0)
+			update_raw_material_cost(row, report_filters)
+			data.append(row)
+
+	return data
+
+def get_filters(report_filters, operations):
+	filters = {"docstatus": 1, "operation": ("in", operations), "is_corrective_job_card": 1}
+	for field in ["name", "work_order", "operation", "workstation", "company", "serial_no", "batch_no", "production_item"]:
+		if report_filters.get(field):
+			if field != 'serial_no':
+				filters[field] = report_filters.get(field)
+			else:
+				filters[field] = ('like', '% {} %'.format(report_filters.get(field)))
+
+	return filters
+
+def update_raw_material_cost(row, filters):
+	row.rm_cost = 0.0
+	for data in frappe.get_all("Job Card Item", fields = ["amount"],
+		filters={"parent": row.name, "docstatus": 1}):
+		row.rm_cost += data.amount
+
+def get_columns(filters):
+	return [
+		{
+			"label": _("Job Card"),
+			"fieldtype": "Link",
+			"fieldname": "name",
+			"options": "Job Card",
+			"width": "100"
+		},
+		{
+			"label": _("Work Order"),
+			"fieldtype": "Link",
+			"fieldname": "work_order",
+			"options": "Work Order",
+			"width": "100"
+		},
+		{
+			"label": _("Item Code"),
+			"fieldtype": "Link",
+			"fieldname": "item_code",
+			"options": "Item",
+			"width": "100"
+		},
+		{
+			"label": _("Item Name"),
+			"fieldtype": "Data",
+			"fieldname": "item_name",
+			"width": "100"
+		},
+		{
+			"label": _("Operation"),
+			"fieldtype": "Link",
+			"fieldname": "operation",
+			"options": "Operation",
+			"width": "100"
+		},
+		{
+			"label": _("Serial No"),
+			"fieldtype": "Data",
+			"fieldname": "serial_no",
+			"width": "100"
+		},
+		{
+			"label": _("Batch No"),
+			"fieldtype": "Data",
+			"fieldname": "batch_no",
+			"width": "100"
+		},
+		{
+			"label": _("Workstation"),
+			"fieldtype": "Link",
+			"fieldname": "workstation",
+			"options": "Workstation",
+			"width": "100"
+		},
+		{
+			"label": _("Operating Cost"),
+			"fieldtype": "Currency",
+			"fieldname": "operating_cost",
+			"width": "100"
+		},
+		{
+			"label": _("Raw Material Cost"),
+			"fieldtype": "Currency",
+			"fieldname": "rm_cost",
+			"width": "100"
+		},
+		{
+			"label": _("Total Time (in Mins)"),
+			"fieldtype": "Float",
+			"fieldname": "total_time_in_mins",
+			"width": "100"
+		}
+	]
\ No newline at end of file
diff --git a/erpnext/patches.txt b/erpnext/patches.txt
index ed6fefd..2b1fc43 100644
--- a/erpnext/patches.txt
+++ b/erpnext/patches.txt
@@ -288,3 +288,5 @@
 erpnext.patches.v13_0.update_timesheet_changes
 erpnext.patches.v13_0.set_training_event_attendance
 erpnext.patches.v13_0.rename_issue_status_hold_to_on_hold
+erpnext.patches.v13_0.bill_for_rejected_quantity_in_purchase_invoice
+erpnext.patches.v13_0.update_job_card_details
diff --git a/erpnext/patches/v13_0/bill_for_rejected_quantity_in_purchase_invoice.py b/erpnext/patches/v13_0/bill_for_rejected_quantity_in_purchase_invoice.py
new file mode 100644
index 0000000..be85cfd
--- /dev/null
+++ b/erpnext/patches/v13_0/bill_for_rejected_quantity_in_purchase_invoice.py
@@ -0,0 +1,8 @@
+from __future__ import unicode_literals
+import frappe
+
+def execute():
+	frappe.reload_doctype("Buying Settings")
+	buying_settings = frappe.get_single("Buying Settings")
+	buying_settings.bill_for_rejected_quantity_in_purchase_invoice = 0
+	buying_settings.save()
\ No newline at end of file
diff --git a/erpnext/patches/v13_0/update_job_card_details.py b/erpnext/patches/v13_0/update_job_card_details.py
new file mode 100644
index 0000000..d4e65c6
--- /dev/null
+++ b/erpnext/patches/v13_0/update_job_card_details.py
@@ -0,0 +1,16 @@
+# Copyright (c) 2019, Frappe and Contributors
+# License: GNU General Public License v3. See license.txt
+
+from __future__ import unicode_literals
+import frappe
+
+def execute():
+	frappe.reload_doc("manufacturing", "doctype", "job_card")
+	frappe.reload_doc("manufacturing", "doctype", "job_card_item")
+	frappe.reload_doc("manufacturing", "doctype", "work_order_operation")
+
+	frappe.db.sql(""" update `tabJob Card` jc, `tabWork Order Operation` wo
+		SET	jc.hour_rate =  wo.hour_rate
+		WHERE
+			jc.operation_id = wo.name and jc.docstatus < 2 and wo.hour_rate > 0
+	""")
\ No newline at end of file
diff --git a/erpnext/payroll/doctype/additional_salary/additional_salary.js b/erpnext/payroll/doctype/additional_salary/additional_salary.js
index d1ed91f..24ffce5 100644
--- a/erpnext/payroll/doctype/additional_salary/additional_salary.js
+++ b/erpnext/payroll/doctype/additional_salary/additional_salary.js
@@ -12,8 +12,12 @@
 				}
 			};
 		});
+	},
 
-		frm.trigger('set_earning_component');
+	onload: function(frm) {
+		if (frm.doc.type) {
+			frm.trigger('set_component_query');
+		}
 	},
 
 	employee: function(frm) {
@@ -46,14 +50,19 @@
 	},
 
 	company: function(frm) {
-		frm.trigger('set_earning_component');
+		frm.set_value("type", "");
+		frm.trigger('set_component_query');
 	},
 
-	set_earning_component: function(frm) {
+	set_component_query: function(frm) {
 		if (!frm.doc.company) return;
+		let filters = {company: frm.doc.company};
+		if (frm.doc.type) {
+			filters.type = frm.doc.type;
+		}
 		frm.set_query("salary_component", function() {
 			return {
-				filters: {type: ["in", ["earning", "deduction"]], company: frm.doc.company}
+				filters: filters
 			};
 		});
 	},
diff --git a/erpnext/payroll/doctype/payroll_entry/payroll_entry.py b/erpnext/payroll/doctype/payroll_entry/payroll_entry.py
index 7a70679..e71d81f 100644
--- a/erpnext/payroll/doctype/payroll_entry/payroll_entry.py
+++ b/erpnext/payroll/doctype/payroll_entry/payroll_entry.py
@@ -11,6 +11,7 @@
 from erpnext.accounts.utils import get_fiscal_year
 from erpnext.hr.doctype.employee.employee import get_holiday_list_for_employee
 from frappe.desk.reportview import get_match_cond, get_filters_cond
+from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import get_accounting_dimensions
 
 class PayrollEntry(Document):
 	def onload(self):
@@ -41,7 +42,7 @@
 				emp_with_sal_slip.append(employee_details.employee)
 
 		if len(emp_with_sal_slip):
-			frappe.throw(_("Salary Slip already exists for {0} ").format(comma_and(emp_with_sal_slip)))
+			frappe.throw(_("Salary Slip already exists for {0}").format(comma_and(emp_with_sal_slip)))
 
 	def on_cancel(self):
 		frappe.delete_doc("Salary Slip", frappe.db.sql_list("""select name from `tabSalary Slip`
@@ -211,7 +212,7 @@
 		return account_dict
 
 	def make_accrual_jv_entry(self):
-		self.check_permission('write')
+		self.check_permission("write")
 		earnings = self.get_salary_component_total(component_type = "earnings") or {}
 		deductions = self.get_salary_component_total(component_type = "deductions") or {}
 		payroll_payable_account = self.payroll_payable_account
@@ -219,12 +220,13 @@
 		precision = frappe.get_precision("Journal Entry Account", "debit_in_account_currency")
 
 		if earnings or deductions:
-			journal_entry = frappe.new_doc('Journal Entry')
-			journal_entry.voucher_type = 'Journal Entry'
-			journal_entry.user_remark = _('Accrual Journal Entry for salaries from {0} to {1}')\
+			journal_entry = frappe.new_doc("Journal Entry")
+			journal_entry.voucher_type = "Journal Entry"
+			journal_entry.user_remark = _("Accrual Journal Entry for salaries from {0} to {1}")\
 				.format(self.start_date, self.end_date)
 			journal_entry.company = self.company
 			journal_entry.posting_date = self.posting_date
+			accounting_dimensions = get_accounting_dimensions() or []
 
 			accounts = []
 			currencies = []
@@ -236,37 +238,34 @@
 			for acc_cc, amount in earnings.items():
 				exchange_rate, amt = self.get_amount_and_exchange_rate_for_journal_entry(acc_cc[0], amount, company_currency, currencies)
 				payable_amount += flt(amount, precision)
-				accounts.append({
+				accounts.append(self.update_accounting_dimensions({
 					"account": acc_cc[0],
 					"debit_in_account_currency": flt(amt, precision),
 					"exchange_rate": flt(exchange_rate),
-					"party_type": '',
 					"cost_center": acc_cc[1] or self.cost_center,
 					"project": self.project
-				})
+				}, accounting_dimensions))
 
 			# Deductions
 			for acc_cc, amount in deductions.items():
 				exchange_rate, amt = self.get_amount_and_exchange_rate_for_journal_entry(acc_cc[0], amount, company_currency, currencies)
 				payable_amount -= flt(amount, precision)
-				accounts.append({
+				accounts.append(self.update_accounting_dimensions({
 					"account": acc_cc[0],
 					"credit_in_account_currency": flt(amt, precision),
 					"exchange_rate": flt(exchange_rate),
 					"cost_center": acc_cc[1] or self.cost_center,
-					"party_type": '',
 					"project": self.project
-				})
+				}, accounting_dimensions))
 
 			# Payable amount
 			exchange_rate, payable_amt = self.get_amount_and_exchange_rate_for_journal_entry(payroll_payable_account, payable_amount, company_currency, currencies)
-			accounts.append({
+			accounts.append(self.update_accounting_dimensions({
 				"account": payroll_payable_account,
 				"credit_in_account_currency": flt(payable_amt, precision),
 				"exchange_rate": flt(exchange_rate),
-				"party_type": '',
 				"cost_center": self.cost_center
-			})
+			}, accounting_dimensions))
 
 			journal_entry.set("accounts", accounts)
 			if len(currencies) > 1:
@@ -286,6 +285,12 @@
 
 		return jv_name
 
+	def update_accounting_dimensions(self, row, accounting_dimensions):
+		for dimension in accounting_dimensions:
+			row.update({dimension: self.get(dimension)})
+
+		return row
+
 	def get_amount_and_exchange_rate_for_journal_entry(self, account, amount, company_currency, currencies):
 		conversion_rate = 1
 		exchange_rate = self.exchange_rate
diff --git a/erpnext/payroll/doctype/salary_slip/test_salary_slip.py b/erpnext/payroll/doctype/salary_slip/test_salary_slip.py
index 9e7db97..ce88cc3 100644
--- a/erpnext/payroll/doctype/salary_slip/test_salary_slip.py
+++ b/erpnext/payroll/doctype/salary_slip/test_salary_slip.py
@@ -481,6 +481,7 @@
 	if not salary_structure:
 		salary_structure = payroll_frequency + " Salary Structure Test for Salary Slip"
 
+
 	employee = frappe.db.get_value("Employee", {"user_id": user})
 	salary_structure_doc = make_salary_structure(salary_structure, payroll_frequency, employee=employee)
 	salary_slip_name = frappe.db.get_value("Salary Slip", {"employee": frappe.db.get_value("Employee", {"user_id": user})})
diff --git a/erpnext/payroll/doctype/salary_structure/test_salary_structure.py b/erpnext/payroll/doctype/salary_structure/test_salary_structure.py
index dce6b7a..e7d123c 100644
--- a/erpnext/payroll/doctype/salary_structure/test_salary_structure.py
+++ b/erpnext/payroll/doctype/salary_structure/test_salary_structure.py
@@ -124,8 +124,8 @@
 			"doctype": "Salary Structure",
 			"name": salary_structure,
 			"company": company or erpnext.get_default_company(),
-			"earnings": make_earning_salary_component(test_tax=test_tax, company_list=["_Test Company"]),
-			"deductions": make_deduction_salary_component(test_tax=test_tax, company_list=["_Test Company"]),
+			"earnings": make_earning_salary_component(setup=True,  test_tax=test_tax, company_list=["_Test Company"]),
+			"deductions": make_deduction_salary_component(setup=True, test_tax=test_tax, company_list=["_Test Company"]),
 			"payroll_frequency": payroll_frequency,
 			"payment_account": get_random("Account", filters={'account_currency': currency}),
 			"currency": currency
diff --git a/erpnext/portal/product_configurator/test_product_configurator.py b/erpnext/portal/product_configurator/test_product_configurator.py
index 3521e7e..daaba67 100644
--- a/erpnext/portal/product_configurator/test_product_configurator.py
+++ b/erpnext/portal/product_configurator/test_product_configurator.py
@@ -43,6 +43,30 @@
 				"show_variant_in_website": 1
 			}).insert()
 
+	def create_regular_web_item(self, name, item_group=None):
+		if not frappe.db.exists('Item', name):
+			doc = frappe.get_doc({
+				"description": name,
+				"item_code": name,
+				"item_name": name,
+				"doctype": "Item",
+				"is_stock_item": 1,
+				"item_group": item_group or "_Test Item Group",
+				"stock_uom": "_Test UOM",
+				"item_defaults": [{
+					"company": "_Test Company",
+					"default_warehouse": "_Test Warehouse - _TC",
+					"expense_account": "_Test Account Cost for Goods Sold - _TC",
+					"buying_cost_center": "_Test Cost Center - _TC",
+					"selling_cost_center": "_Test Cost Center - _TC",
+					"income_account": "Sales - _TC"
+				}],
+				"show_in_website": 1
+			}).insert()
+		else:
+			doc = frappe.get_doc("Item", name)
+		return doc
+
 	def test_product_list(self):
 		template_items = frappe.get_all('Item', {'show_in_website': 1})
 		variant_items = frappe.get_all('Item', {'show_variant_in_website': 1})
@@ -79,3 +103,42 @@
 			'Test Size': ['2XL']
 		})
 		self.assertEqual(len(items), 1)
+
+	def test_products_in_multiple_item_groups(self):
+		"""Check if product is visible on multiple item group pages barring its own."""
+		from erpnext.shopping_cart.product_query import ProductQuery
+
+		if not frappe.db.exists("Item Group", {"name": "Tech Items"}):
+			item_group_doc = frappe.get_doc({
+				"doctype": "Item Group",
+				"item_group_name": "Tech Items",
+				"parent_item_group": "All Item Groups",
+				"show_in_website": 1
+			}).insert()
+		else:
+			item_group_doc = frappe.get_doc("Item Group", "Tech Items")
+
+		doc = self.create_regular_web_item("Portal Item", item_group="Tech Items")
+		if not frappe.db.exists("Website Item Group", {"parent": "Portal Item"}):
+			doc.append("website_item_groups", {
+				"item_group": "_Test Item Group Desktops"
+			})
+			doc.save()
+
+		# check if item is visible in its own Item Group's page
+		engine = ProductQuery()
+		items = engine.query({}, {"item_group": "Tech Items"}, None, start=0, item_group="Tech Items")
+		self.assertEqual(len(items), 1)
+		self.assertEqual(items[0].item_code, "Portal Item")
+
+		# check if item is visible in configured foreign Item Group's page
+		engine = ProductQuery()
+		items = engine.query({}, {"item_group": "_Test Item Group Desktops"}, None, start=0, item_group="_Test Item Group Desktops")
+		item_codes = [row.item_code for row in items]
+
+		self.assertIn(len(items), [2, 3])
+		self.assertIn("Portal Item", item_codes)
+
+		# teardown
+		doc.delete()
+		item_group_doc.delete()
\ No newline at end of file
diff --git a/erpnext/public/js/controllers/transaction.js b/erpnext/public/js/controllers/transaction.js
index 6dc40f0..b3af3d6 100644
--- a/erpnext/public/js/controllers/transaction.js
+++ b/erpnext/public/js/controllers/transaction.js
@@ -868,9 +868,6 @@
 
 		}
 
-		if (this.frm.doc.posting_date) var date = this.frm.doc.posting_date;
-		else var date = this.frm.doc.transaction_date;
-
 		if (frappe.meta.get_docfield(this.frm.doctype, "shipping_address") &&
 			in_list(['Purchase Order', 'Purchase Receipt', 'Purchase Invoice'], this.frm.doctype)) {
 			erpnext.utils.get_shipping_address(this.frm, function(){
diff --git a/erpnext/public/js/utils/party.js b/erpnext/public/js/utils/party.js
index 808dd5a..a79eadc 100644
--- a/erpnext/public/js/utils/party.js
+++ b/erpnext/public/js/utils/party.js
@@ -274,9 +274,9 @@
 	return true;
 }
 
-erpnext.utils.get_shipping_address = function(frm, callback){
+erpnext.utils.get_shipping_address = function(frm, callback) {
 	if (frm.doc.company) {
-		if (!(frm.doc.inter_com_order_reference || frm.doc.internal_invoice_reference ||
+		if ((frm.doc.inter_company_order_reference || frm.doc.internal_invoice_reference ||
 			frm.doc.internal_order_reference)) {
 			if (callback) {
 				return callback();
diff --git a/erpnext/public/scss/shopping_cart.scss b/erpnext/public/scss/shopping_cart.scss
index 9402cf9..5962859 100644
--- a/erpnext/public/scss/shopping_cart.scss
+++ b/erpnext/public/scss/shopping_cart.scss
@@ -467,11 +467,15 @@
 
 	.btn-change-address {
 		color: var(--blue-500);
-		box-shadow: none;
-		border: 1px solid var(--blue-500);
 	}
 }
 
+.btn-new-address:hover, .btn-change-address:hover {
+	box-shadow: none;
+	color: var(--blue-500) !important;
+	border: 1px solid var(--blue-500);
+}
+
 .modal .address-card {
 	.card-body {
 		padding: var(--padding-sm);
diff --git a/erpnext/regional/report/gstr_1/gstr_1.py b/erpnext/regional/report/gstr_1/gstr_1.py
index 80e2d72..1096159 100644
--- a/erpnext/regional/report/gstr_1/gstr_1.py
+++ b/erpnext/regional/report/gstr_1/gstr_1.py
@@ -201,7 +201,7 @@
 		elif self.filters.get("type_of_business") ==  "EXPORT":
 			conditions += """ AND is_return !=1 and gst_category = 'Overseas' """
 
-		conditions += " AND billing_address_gstin NOT IN %(company_gstins)s"
+		conditions += " AND IFNULL(billing_address_gstin, '') NOT IN %(company_gstins)s"
 
 		return conditions
 
diff --git a/erpnext/selling/doctype/product_bundle/product_bundle.py b/erpnext/selling/doctype/product_bundle/product_bundle.py
index d3281f7..ae3482f 100644
--- a/erpnext/selling/doctype/product_bundle/product_bundle.py
+++ b/erpnext/selling/doctype/product_bundle/product_bundle.py
@@ -4,6 +4,8 @@
 from __future__ import unicode_literals
 import frappe
 
+from frappe.utils import get_link_to_form
+
 from frappe import _
 
 from frappe.model.document import Document
@@ -18,6 +20,27 @@
 		from erpnext.utilities.transaction_base import validate_uom_is_integer
 		validate_uom_is_integer(self, "uom", "qty")
 
+	def on_trash(self):
+		linked_doctypes = ["Delivery Note", "Sales Invoice", "POS Invoice", "Purchase Receipt", "Purchase Invoice",
+			"Stock Entry", "Stock Reconciliation", "Sales Order", "Purchase Order", "Material Request"]
+
+		invoice_links = []
+		for doctype in linked_doctypes:
+			item_doctype = doctype + " Item"
+
+			if doctype == "Stock Entry":
+				item_doctype = doctype + " Detail"
+
+			invoices = frappe.db.get_all(item_doctype, {"item_code": self.new_item_code, "docstatus": 1}, ["parent"])
+
+			for invoice in invoices:
+				invoice_links.append(get_link_to_form(doctype, invoice['parent']))
+
+		if len(invoice_links):
+			frappe.throw(
+				"This Product Bundle is linked with {0}. You will have to cancel these documents in order to delete this Product Bundle"
+				.format(", ".join(invoice_links)), title=_("Not Allowed"))
+
 	def validate_main_item(self):
 		"""Validates, main Item is not a stock item"""
 		if frappe.db.get_value("Item", self.new_item_code, "is_stock_item"):
diff --git a/erpnext/selling/page/point_of_sale/pos_controller.js b/erpnext/selling/page/point_of_sale/pos_controller.js
index ae3f9e3..c827368 100644
--- a/erpnext/selling/page/point_of_sale/pos_controller.js
+++ b/erpnext/selling/page/point_of_sale/pos_controller.js
@@ -241,8 +241,8 @@
 			events: {
 				get_frm: () => this.frm,
 
-				cart_item_clicked: (item_code, batch_no, uom, rate) => {
-					const item_row = this.get_item_from_frm(item_code, batch_no, uom, rate);
+				cart_item_clicked: (item) => {
+					const item_row = this.get_item_from_frm(item);
 					this.item_details.toggle_item_details_section(item_row);
 				},
 
@@ -273,17 +273,15 @@
 					this.cart.toggle_numpad(minimize);
 				},
 
-				form_updated: (cdt, cdn, fieldname, value) => {
-					const item_row = frappe.model.get_doc(cdt, cdn);
-					if (item_row && item_row[fieldname] != value) {
-
-						const { item_code, batch_no, uom, rate } = this.item_details.current_item;
-						const event = {
-							field: fieldname,
+				form_updated: (item, field, value) => {
+					const item_row = frappe.model.get_doc(item.doctype, item.name);
+					if (item_row && item_row[field] != value) {
+						const args = {
+							field,
 							value,
-							item: { item_code, batch_no, uom, rate }
-						}
-						return this.on_cart_update(event)
+							item: this.item_details.current_item
+						};
+						return this.on_cart_update(args);
 					}
 
 					return Promise.resolve();
@@ -300,19 +298,18 @@
 				set_value_in_current_cart_item: (selector, value) => {
 					this.cart.update_selector_value_in_cart_item(selector, value, this.item_details.current_item);
 				},
-				clone_new_batch_item_in_frm: (batch_serial_map, current_item) => {
+				clone_new_batch_item_in_frm: (batch_serial_map, item) => {
 					// called if serial nos are 'auto_selected' and if those serial nos belongs to multiple batches
 					// for each unique batch new item row is added in the form & cart
 					Object.keys(batch_serial_map).forEach(batch => {
-						const { item_code, batch_no } = current_item;
-						const item_to_clone = this.frm.doc.items.find(i => i.item_code === item_code && i.batch_no === batch_no);
+						const item_to_clone = this.frm.doc.items.find(i => i.name == item.name);
 						const new_row = this.frm.add_child("items", { ...item_to_clone });
 						// update new serialno and batch
 						new_row.batch_no = batch;
 						new_row.serial_no = batch_serial_map[batch].join(`\n`);
 						new_row.qty = batch_serial_map[batch].length;
 						this.frm.doc.items.forEach(row => {
-							if (item_code === row.item_code) {
+							if (item.item_code === row.item_code) {
 								this.update_cart_html(row);
 							}
 						});
@@ -321,8 +318,8 @@
 				remove_item_from_cart: () => this.remove_item_from_cart(),
 				get_item_stock_map: () => this.item_stock_map,
 				close_item_details: () => {
-					this.item_details.toggle_item_details_section(undefined);
-					this.cart.prev_action = undefined;
+					this.item_details.toggle_item_details_section(null);
+					this.cart.prev_action = null;
 					this.cart.toggle_item_highlight();
 				},
 				get_available_stock: (item_code, warehouse) => this.get_available_stock(item_code, warehouse)
@@ -506,50 +503,47 @@
 		let item_row = undefined;
 		try {
 			let { field, value, item } = args;
-			const { item_code, batch_no, serial_no, uom, rate } = item;
-			item_row = this.get_item_from_frm(item_code, batch_no, uom, rate);
+			item_row = this.get_item_from_frm(item);
+			const item_row_exists = !$.isEmptyObject(item_row);
 
-			const item_selected_from_selector = field === 'qty' && value === "+1"
+			const from_selector = field === 'qty' && value === "+1";
+			if (from_selector)
+				value = flt(item_row.qty) + flt(value);
 
-			if (item_row) {
-				item_selected_from_selector && (value = item_row.qty + flt(value))
-
-				field === 'qty' && (value = flt(value));
+			if (item_row_exists) {
+				if (field === 'qty')
+					value = flt(value);
 
 				if (['qty', 'conversion_factor'].includes(field) && value > 0 && !this.allow_negative_stock) {
 					const qty_needed = field === 'qty' ? value * item_row.conversion_factor : item_row.qty * value;
 					await this.check_stock_availability(item_row, qty_needed, this.frm.doc.set_warehouse);
 				}
 
-				if (this.is_current_item_being_edited(item_row) || item_selected_from_selector) {
+				if (this.is_current_item_being_edited(item_row) || from_selector) {
 					await frappe.model.set_value(item_row.doctype, item_row.name, field, value);
 					this.update_cart_html(item_row);
 				}
 
 			} else {
-				if (!this.frm.doc.customer) {
-					frappe.dom.unfreeze();
-					frappe.show_alert({
-						message: __('You must select a customer before adding an item.'),
-						indicator: 'orange'
-					});
-					frappe.utils.play_sound("error");
+				if (!this.frm.doc.customer) 
+					return this.raise_customer_selection_alert();
+
+				const { item_code, batch_no, serial_no, rate } = item;
+
+				if (!item_code)
 					return;
-				}
-				if (!item_code) return;
 
-				item_selected_from_selector && (value = flt(value))
-
-				const args = { item_code, batch_no, rate, [field]: value };
+				const new_item = { item_code, batch_no, rate, [field]: value };
 
 				if (serial_no) {
 					await this.check_serial_no_availablilty(item_code, this.frm.doc.set_warehouse, serial_no);
-					args['serial_no'] = serial_no;
+					new_item['serial_no'] = serial_no;
 				}
 
-				if (field === 'serial_no') args['qty'] = value.split(`\n`).length || 0;
+				if (field === 'serial_no')
+					new_item['qty'] = value.split(`\n`).length || 0;
 
-				item_row = this.frm.add_child('items', args);
+				item_row = this.frm.add_child('items', new_item);
 
 				if (field === 'qty' && value !== 0 && !this.allow_negative_stock)
 					await this.check_stock_availability(item_row, value, this.frm.doc.set_warehouse);
@@ -558,8 +552,11 @@
 				
 				this.update_cart_html(item_row);
 
-				this.item_details.$component.is(':visible') && this.edit_item_details_of(item_row);
-				this.check_serial_batch_selection_needed(item_row) && this.edit_item_details_of(item_row);
+				if (this.item_details.$component.is(':visible'))
+					this.edit_item_details_of(item_row);
+
+				if (this.check_serial_batch_selection_needed(item_row))
+					this.edit_item_details_of(item_row);
 			}
 
 		} catch (error) {
@@ -570,14 +567,33 @@
 		}
 	}
 
-	get_item_from_frm(item_code, batch_no, uom, rate) {
-		const has_batch_no = batch_no;
-		return this.frm.doc.items.find(
-			i => i.item_code === item_code
-				&& (!has_batch_no || (has_batch_no && i.batch_no === batch_no))
-				&& (i.uom === uom)
-				&& (i.rate == rate)
-		);
+	raise_customer_selection_alert() {
+		frappe.dom.unfreeze();
+		frappe.show_alert({
+			message: __('You must select a customer before adding an item.'),
+			indicator: 'orange'
+		});
+		frappe.utils.play_sound("error");
+	}
+
+	get_item_from_frm({ name, item_code, batch_no, uom, rate }) {
+		let item_row = null;
+		if (name) {
+			item_row = this.frm.doc.items.find(i => i.name == name);
+		} else {
+			// if item is clicked twice from item selector
+			// then "item_code, batch_no, uom, rate" will help in getting the exact item
+			// to increase the qty by one
+			const has_batch_no = batch_no;
+			item_row = this.frm.doc.items.find(
+				i => i.item_code === item_code
+					&& (!has_batch_no || (has_batch_no && i.batch_no === batch_no))
+					&& (i.uom === uom)
+					&& (i.rate == rate)
+			);
+		}
+
+		return item_row || {};
 	}
 
 	edit_item_details_of(item_row) {
@@ -585,9 +601,7 @@
 	}
 
 	is_current_item_being_edited(item_row) {
-		const { item_code, batch_no } = this.item_details.current_item;
-
-		return item_code !== item_row.item_code || batch_no != item_row.batch_no ? false : true;
+		return item_row.name == this.item_details.current_item.name;
 	}
 
 	update_cart_html(item_row, remove_item) {
@@ -669,7 +683,7 @@
 
 	update_item_field(value, field_or_action) {
 		if (field_or_action === 'checkout') {
-			this.item_details.toggle_item_details_section(undefined);
+			this.item_details.toggle_item_details_section(null);
 		} else if (field_or_action === 'remove') {
 			this.remove_item_from_cart();
 		} else {
@@ -688,7 +702,7 @@
 			.then(() => {
 				frappe.model.clear_doc(doctype, name);
 				this.update_cart_html(current_item, true);
-				this.item_details.toggle_item_details_section(undefined);
+				this.item_details.toggle_item_details_section(null);
 				frappe.dom.unfreeze();
 			})
 			.catch(e => console.log(e));
diff --git a/erpnext/selling/page/point_of_sale/pos_item_cart.js b/erpnext/selling/page/point_of_sale/pos_item_cart.js
index f5019f5..7cae0e4 100644
--- a/erpnext/selling/page/point_of_sale/pos_item_cart.js
+++ b/erpnext/selling/page/point_of_sale/pos_item_cart.js
@@ -181,11 +181,8 @@
 				me.$totals_section.find(".edit-cart-btn").click();
 			}
 
-			const item_code = unescape($cart_item.attr('data-item-code'));
-			const batch_no = unescape($cart_item.attr('data-batch-no'));
-			const uom = unescape($cart_item.attr('data-uom'));
-			const rate = unescape($cart_item.attr('data-rate'));
-			me.events.cart_item_clicked(item_code, batch_no, uom, rate);
+			const item_row_name = unescape($cart_item.attr('data-row-name'));
+			me.events.cart_item_clicked({ name: item_row_name });
 			this.numpad_value = '';
 		});
 
@@ -521,25 +518,14 @@
 		}
 	}
 
-	get_cart_item({ item_code, batch_no, uom, rate }) {
-		const batch_attr = `[data-batch-no="${escape(batch_no)}"]`;
-		const item_code_attr = `[data-item-code="${escape(item_code)}"]`;
-		const uom_attr = `[data-uom="${escape(uom)}"]`;
-		const rate_attr = `[data-rate="${escape(rate)}"]`;
-
-		const item_selector = batch_no ?
-			`.cart-item-wrapper${batch_attr}${uom_attr}${rate_attr}` : `.cart-item-wrapper${item_code_attr}${uom_attr}${rate_attr}`;
-
+	get_cart_item({ name }) {
+		const item_selector = `.cart-item-wrapper[data-row-name="${escape(name)}"]`;
 		return this.$cart_items_wrapper.find(item_selector);
 	}
 
 	get_item_from_frm(item) {
 		const doc = this.events.get_frm().doc;
-		const { item_code, batch_no, uom, rate } = item;
-		const search_field = batch_no ? 'batch_no' : 'item_code';
-		const search_value = batch_no || item_code;
-
-		return doc.items.find(i => i[search_field] === search_value && i.uom === uom && i.rate === rate);
+		return doc.items.find(i => i.name == item.name);
 	}
 
 	update_item_html(item, remove_item) {
@@ -564,10 +550,7 @@
 
 		if (!$item_to_update.length) {
 			this.$cart_items_wrapper.append(
-				`<div class="cart-item-wrapper"
-						data-item-code="${escape(item_data.item_code)}" data-uom="${escape(item_data.uom)}"
-						data-batch-no="${escape(item_data.batch_no || '')}" data-rate="${escape(item_data.rate)}">
-				</div>
+				`<div class="cart-item-wrapper" data-row-name="${escape(item_data.name)}"></div>
 				<div class="seperator"></div>`
 			)
 			$item_to_update = this.get_cart_item(item_data);
@@ -642,7 +625,7 @@
 
 		function get_item_image_html() {
 			const { image, item_name } = item_data;
-			if (image) {
+			if (!me.hide_images && image) {
 				return `
 					<div class="item-image">
 						<img
diff --git a/erpnext/selling/page/point_of_sale/pos_item_details.js b/erpnext/selling/page/point_of_sale/pos_item_details.js
index 5e09df8..6a4d3d5 100644
--- a/erpnext/selling/page/point_of_sale/pos_item_details.js
+++ b/erpnext/selling/page/point_of_sale/pos_item_details.js
@@ -2,6 +2,7 @@
 	constructor({ wrapper, events, settings }) {
 		this.wrapper = wrapper;
 		this.events = events;
+		this.hide_images = settings.hide_images;
 		this.allow_rate_change = settings.allow_rate_change;
 		this.allow_discount_change = settings.allow_discount_change;
 		this.current_item = {};
@@ -54,36 +55,28 @@
 		this.$dicount_section = this.$component.find('.discount-section');
 	}
 
-	has_item_has_changed(item) {
-		const { item_code, batch_no, uom, rate } = this.current_item;
-		const item_code_is_same = item && item_code === item.item_code;
-		const batch_is_same = item && batch_no == item.batch_no;
-		const uom_is_same = item && uom === item.uom;
-		const rate_is_same = item && rate === item.rate;
-		
-		if (!item)
-			return false;
-
-		if (item_code_is_same && batch_is_same && uom_is_same && rate_is_same)
-			return false;
-
-		return true;
+	compare_with_current_item(item) {
+		// returns true if `item` is currently being edited
+		return item && item.name == this.current_item.name;
 	}
 
 	toggle_item_details_section(item) {
-		this.item_has_changed = this.has_item_has_changed(item);
+		const current_item_changed = !this.compare_with_current_item(item);
 
-		this.events.toggle_item_selector(this.item_has_changed);
-		this.toggle_component(this.item_has_changed);
+		// if item is null or highlighted cart item is clicked twice
+		const hide_item_details = !Boolean(item) || !current_item_changed;
+		
+		this.events.toggle_item_selector(!hide_item_details);
+		this.toggle_component(!hide_item_details);
 
-		if (this.item_has_changed) {
+		if (item && current_item_changed) {
 			this.doctype = item.doctype;
 			this.item_meta = frappe.get_meta(this.doctype);
 			this.name = item.name;
 			this.item_row = item;
 			this.currency = this.events.get_frm().doc.currency;
 
-			this.current_item = { item_code: item.item_code, batch_no: item.batch_no, uom: item.uom, rate: item.rate };
+			this.current_item = item;
 
 			this.render_dom(item);
 			this.render_discount_dom(item);
@@ -132,7 +125,7 @@
 		this.$item_name.html(item_name);
 		this.$item_description.html(get_description_html());
 		this.$item_price.html(format_currency(price_list_rate, this.currency));
-		if (image) {
+		if (!this.hide_images && image) {
 			this.$item_image.html(
 				`<img 
 					onerror="cur_pos.item_details.handle_broken_image(this)"
@@ -180,7 +173,7 @@
 				df: {
 					...field_meta,
 					onchange: function() {
-						me.events.form_updated(me.doctype, me.name, fieldname, this.value);
+						me.events.form_updated(me.current_item, fieldname, this.value);
 					}
 				},
 				parent: this.$form_container.find(`.${fieldname}-control`),
@@ -218,22 +211,17 @@
 	bind_custom_control_change_event() {
 		const me = this;
 		if (this.rate_control) {
-			if (this.allow_rate_change) {
-				this.rate_control.df.onchange = function() {
-					if (this.value || flt(this.value) === 0) {
-						me.events.set_value_in_current_cart_item('rate', this.value);
-						me.events.form_updated(me.doctype, me.name, 'rate', this.value).then(() => {
-							const item_row = frappe.get_doc(me.doctype, me.name);
-							const doc = me.events.get_frm().doc;
-							me.$item_price.html(format_currency(item_row.rate, doc.currency));
-							me.render_discount_dom(item_row);
-						});
-						me.current_item.rate = this.value;
-					}
-				};
-			} else {
-				this.rate_control.df.read_only = 1;
-			}
+			this.rate_control.df.onchange = function() {
+				if (this.value || flt(this.value) === 0) {
+					me.events.form_updated(me.current_item, 'rate', this.value).then(() => {
+						const item_row = frappe.get_doc(me.doctype, me.name);
+						const doc = me.events.get_frm().doc;
+						me.$item_price.html(format_currency(item_row.rate, doc.currency));
+						me.render_discount_dom(item_row);
+					});
+				}
+			};
+			this.rate_control.df.read_only = !this.allow_rate_change;
 			this.rate_control.refresh();
 		}
 
@@ -246,7 +234,7 @@
 			this.warehouse_control.df.reqd = 1;
 			this.warehouse_control.df.onchange = function() {
 				if (this.value) {
-					me.events.form_updated(me.doctype, me.name, 'warehouse', this.value).then(() => {
+					me.events.form_updated(me.current_item, 'warehouse', this.value).then(() => {
 						me.item_stock_map = me.events.get_item_stock_map();
 						const available_qty = me.item_stock_map[me.item_row.item_code][this.value];
 						if (available_qty === undefined) {
@@ -278,7 +266,7 @@
 			this.serial_no_control.df.reqd = 1;
 			this.serial_no_control.df.onchange = async function() {
 				!me.current_item.batch_no && await me.auto_update_batch_no();
-				me.events.form_updated(me.doctype, me.name, 'serial_no', this.value);
+				me.events.form_updated(me.current_item, 'serial_no', this.value);
 			}
 			this.serial_no_control.refresh();
 		}
@@ -295,19 +283,12 @@
 					}
 				}
 			};
-			this.batch_no_control.df.onchange = function() {
-				me.events.set_value_in_current_cart_item('batch-no', this.value);
-				me.events.form_updated(me.doctype, me.name, 'batch_no', this.value);
-				me.current_item.batch_no = this.value;
-			}
 			this.batch_no_control.refresh();
 		}
 
 		if (this.uom_control) {
 			this.uom_control.df.onchange = function() {
-				me.events.set_value_in_current_cart_item('uom', this.value);
-				me.events.form_updated(me.doctype, me.name, 'uom', this.value);
-				me.current_item.uom = this.value;
+				me.events.form_updated(me.current_item, 'uom', this.value);
 
 				const item_row = frappe.get_doc(me.doctype, me.name);
 				me.conversion_factor_control.df.read_only = (item_row.stock_uom == this.value);
@@ -317,9 +298,9 @@
 
 		frappe.model.on("POS Invoice Item", "*", (fieldname, value, item_row) => {
 			const field_control = this[`${fieldname}_control`];
-			const item_is_same = !this.has_item_has_changed(item_row);
+			const item_row_is_being_edited = this.compare_with_current_item(item_row);
 
-			if (item_is_same && field_control && field_control.get_value() !== value) {
+			if (item_row_is_being_edited && field_control && field_control.get_value() !== value) {
 				field_control.set_value(value);
 				cur_pos.update_cart_html(item_row);
 			}
@@ -337,7 +318,9 @@
 				fields: ["batch_no", "name"]
 			});
 			const batch_serial_map = serials_with_batch_no.reduce((acc, r) => {
-				acc[r.batch_no] || (acc[r.batch_no] = []);
+				if (!acc[r.batch_no]) {
+					acc[r.batch_no] = [];
+				}
 				acc[r.batch_no] = [...acc[r.batch_no], r.name];
 				return acc;
 			}, {});
@@ -353,12 +336,10 @@
 			if (serial_nos_belongs_to_other_batch) {
 				this.serial_no_control.set_value(batch_serial_nos);
 				this.qty_control.set_value(batch_serial_map[batch_no].length);
-			}
 
-			delete batch_serial_map[batch_no];
-
-			if (serial_nos_belongs_to_other_batch)
+				delete batch_serial_map[batch_no];
 				this.events.clone_new_batch_item_in_frm(batch_serial_map, this.current_item);
+			}
 		}
 	}
 
diff --git a/erpnext/selling/page/point_of_sale/pos_item_selector.js b/erpnext/selling/page/point_of_sale/pos_item_selector.js
index 64c529e..dd7f143 100644
--- a/erpnext/selling/page/point_of_sale/pos_item_selector.js
+++ b/erpnext/selling/page/point_of_sale/pos_item_selector.js
@@ -232,7 +232,11 @@
 			uom = uom === "undefined" ? undefined : uom;
 			rate = rate === "undefined" ? undefined : rate;
 
-			me.events.item_selected({ field: 'qty', value: "+1", item: { item_code, batch_no, serial_no, uom, rate }});
+			me.events.item_selected({
+				field: 'qty',
+				value: "+1",
+				item: { item_code, batch_no, serial_no, uom, rate }
+			});
 			me.set_search_value('');
 		});
 
diff --git a/erpnext/selling/report/sales_order_analysis/sales_order_analysis.py b/erpnext/selling/report/sales_order_analysis/sales_order_analysis.py
index f5feb95..8cb2446 100644
--- a/erpnext/selling/report/sales_order_analysis/sales_order_analysis.py
+++ b/erpnext/selling/report/sales_order_analysis/sales_order_analysis.py
@@ -59,7 +59,7 @@
 			IF(so.status in ('Completed','To Bill'), 0, (SELECT delay_days)) as delay,
 			soi.qty, soi.delivered_qty,
 			(soi.qty - soi.delivered_qty) AS pending_qty,
-			IFNULL(sii.qty, 0) as billed_qty,
+			IFNULL(SUM(sii.qty), 0) as billed_qty,
 			soi.base_amount as amount,
 			(soi.delivered_qty * soi.base_rate) as delivered_qty_amount,
 			(soi.billed_amt * IFNULL(so.conversion_rate, 1)) as billed_amount,
diff --git a/erpnext/setup/doctype/company/company.py b/erpnext/setup/doctype/company/company.py
index 27e023c..0427abe 100644
--- a/erpnext/setup/doctype/company/company.py
+++ b/erpnext/setup/doctype/company/company.py
@@ -407,8 +407,6 @@
 
 	frappe.only_for("System Manager")
 
-	frappe.db.set_value("Company", company, "abbr", new)
-
 	def _rename_record(doc):
 		parts = doc[0].rsplit(" - ", 1)
 		if len(parts) == 1 or parts[1].lower() == old.lower():
@@ -419,11 +417,18 @@
 		doc = (d for d in frappe.db.sql("select name from `tab%s` where company=%s" % (dt, '%s'), company))
 		for d in doc:
 			_rename_record(d)
+	try:
+		frappe.db.auto_commit_on_many_writes = 1
+		frappe.db.set_value("Company", company, "abbr", new)
+		for dt in ["Warehouse", "Account", "Cost Center", "Department",
+				"Sales Taxes and Charges Template", "Purchase Taxes and Charges Template"]:
+			_rename_records(dt)
+			frappe.db.commit()
 
-	for dt in ["Warehouse", "Account", "Cost Center", "Department",
-			"Sales Taxes and Charges Template", "Purchase Taxes and Charges Template"]:
-		_rename_records(dt)
-		frappe.db.commit()
+	except Exception:
+		frappe.log_error(title=_('Abbreviation Rename Error'))
+	finally:
+		frappe.db.auto_commit_on_many_writes = 0
 
 
 def get_name_with_abbr(name, company):
diff --git a/erpnext/setup/doctype/item_group/item_group.py b/erpnext/setup/doctype/item_group/item_group.py
index db31d6d..1c72ceb 100644
--- a/erpnext/setup/doctype/item_group/item_group.py
+++ b/erpnext/setup/doctype/item_group/item_group.py
@@ -91,7 +91,7 @@
 		field_filters['item_group'] = self.name
 
 		engine = ProductQuery()
-		context.items = engine.query(attribute_filters, field_filters, search, start)
+		context.items = engine.query(attribute_filters, field_filters, search, start, item_group=self.name)
 
 		filter_engine = ProductFiltersBuilder(self.name)
 
diff --git a/erpnext/shopping_cart/filters.py b/erpnext/shopping_cart/filters.py
index 6c63d87..7dfa09e 100644
--- a/erpnext/shopping_cart/filters.py
+++ b/erpnext/shopping_cart/filters.py
@@ -22,12 +22,15 @@
 
 		filter_data = []
 		for df in fields:
-			filters = {}
+			filters, or_filters = {}, []
 			if df.fieldtype == "Link":
 				if self.item_group:
-					filters['item_group'] = self.item_group
+					or_filters.extend([
+						["item_group", "=", self.item_group],
+						["Website Item Group", "item_group", "=", self.item_group]
+					])
 
-				values =  frappe.get_all("Item", fields=[df.fieldname], filters=filters, distinct="True", pluck=df.fieldname)
+				values = frappe.get_all("Item", fields=[df.fieldname], filters=filters, or_filters=or_filters, distinct="True", pluck=df.fieldname)
 			else:
 				doctype = df.get_link_doctype()
 
@@ -44,7 +47,9 @@
 				values = [d.name for d in frappe.get_all(doctype, filters)]
 
 			# Remove None
-			values = values.remove(None) if None in values else values
+			if None in values:
+				values.remove(None)
+
 			if values:
 				filter_data.append([df, values])
 
@@ -61,14 +66,18 @@
 		for attr_doc in attribute_docs:
 			selected_attributes = []
 			for attr in attr_doc.item_attribute_values:
+				or_filters = []
 				filters= [
 					["Item Variant Attribute", "attribute", "=", attr.parent],
 					["Item Variant Attribute", "attribute_value", "=", attr.attribute_value]
 				]
 				if self.item_group:
-					filters.append(["item_group", "=", self.item_group])
+					or_filters.extend([
+						["item_group", "=", self.item_group],
+						["Website Item Group", "item_group", "=", self.item_group]
+					])
 
-				if frappe.db.get_all("Item", filters, limit=1):
+				if frappe.db.get_all("Item", filters, or_filters=or_filters, limit=1):
 					selected_attributes.append(attr)
 
 			if selected_attributes:
diff --git a/erpnext/shopping_cart/product_query.py b/erpnext/shopping_cart/product_query.py
index dd94c26..d96d803 100644
--- a/erpnext/shopping_cart/product_query.py
+++ b/erpnext/shopping_cart/product_query.py
@@ -22,13 +22,14 @@
 		self.settings = frappe.get_doc("Products Settings")
 		self.cart_settings = frappe.get_doc("Shopping Cart Settings")
 		self.page_length = self.settings.products_per_page or 20
-		self.fields = ['name', 'item_name', 'item_code', 'website_image', 'variant_of', 'has_variants', 'item_group', 'image', 'web_long_description', 'description', 'route']
+		self.fields = ['name', 'item_name', 'item_code', 'website_image', 'variant_of', 'has_variants',
+			'item_group', 'image', 'web_long_description', 'description', 'route', 'weightage']
 		self.filters = []
 		self.or_filters = [['show_in_website', '=', 1]]
 		if not self.settings.get('hide_variants'):
 			self.or_filters.append(['show_variant_in_website', '=', 1])
 
-	def query(self, attributes=None, fields=None, search_term=None, start=0):
+	def query(self, attributes=None, fields=None, search_term=None, start=0, item_group=None):
 		"""Summary
 
 		Args:
@@ -44,6 +45,15 @@
 		if search_term: self.build_search_filters(search_term)
 
 		result = []
+		website_item_groups = []
+
+		# if from item group page consider website item group table
+		if item_group:
+			website_item_groups = frappe.db.get_all(
+				"Item",
+				fields=self.fields + ["`tabWebsite Item Group`.parent as wig_parent"],
+				filters=[["Website Item Group", "item_group", "=", item_group]]
+			)
 
 		if attributes:
 			all_items = []
@@ -61,12 +71,10 @@
 					],
 					or_filters=self.or_filters,
 					start=start,
-					limit=self.page_length,
-					order_by="weightage desc"
+					limit=self.page_length
 				)
 
 				items_dict = {item.name: item for item in items}
-				# TODO: Replace Variants by their parent templates
 
 				all_items.append(set(items_dict.keys()))
 
@@ -78,14 +86,22 @@
 				filters=self.filters,
 				or_filters=self.or_filters,
 				start=start,
-				limit=self.page_length,
-				order_by="weightage desc"
+				limit=self.page_length
 			)
 
+		# Combine results having context of website item groups into item results
+		if item_group and website_item_groups:
+			items_list = {row.name for row in result}
+			for row in website_item_groups:
+				if row.wig_parent not in items_list:
+					result.append(row)
+
+		result = sorted(result, key=lambda x: x.get("weightage"), reverse=True)
+
 		for item in result:
 			product_info = get_product_info_for_website(item.item_code, skip_quotation_creation=True).get('product_info')
 			if product_info:
-				item.formatted_price = product_info['price'].get('formatted_price') if product_info['price'] else None
+				item.formatted_price = (product_info.get('price') or {}).get('formatted_price')
 
 		return result
 
@@ -99,7 +115,16 @@
 			if not values:
 				continue
 
-			if isinstance(values, list):
+			# handle multiselect fields in filter addition
+			meta = frappe.get_meta('Item', cached=True)
+			df = meta.get_field(field)
+			if df.fieldtype == 'Table MultiSelect':
+				child_doctype = df.options
+				child_meta = frappe.get_meta(child_doctype, cached=True)
+				fields = child_meta.get("fields")
+				if fields:
+					self.filters.append([child_doctype, fields[0].fieldname, 'IN', values])
+			elif isinstance(values, list):
 				# If value is a list use `IN` query
 				self.filters.append([field, 'IN', values])
 			else:
diff --git a/erpnext/stock/doctype/batch/batch.json b/erpnext/stock/doctype/batch/batch.json
index 943cb34..e6d2e13 100644
--- a/erpnext/stock/doctype/batch/batch.json
+++ b/erpnext/stock/doctype/batch/batch.json
@@ -1,4 +1,5 @@
 {
+ "actions": [],
  "allow_import": 1,
  "autoname": "field:batch_id",
  "creation": "2013-03-05 14:50:38",
@@ -25,7 +26,11 @@
   "reference_doctype",
   "reference_name",
   "section_break_7",
-  "description"
+  "description",
+  "manufacturing_section",
+  "qty_to_produce",
+  "column_break_23",
+  "produced_qty"
  ],
  "fields": [
   {
@@ -160,13 +165,35 @@
    "label": "Batch UOM",
    "options": "UOM",
    "read_only": 1
+  },
+  {
+   "fieldname": "manufacturing_section",
+   "fieldtype": "Section Break",
+   "label": "Manufacturing"
+  },
+  {
+   "fieldname": "qty_to_produce",
+   "fieldtype": "Float",
+   "label": "Qty To Produce",
+   "read_only": 1
+  },
+  {
+   "fieldname": "column_break_23",
+   "fieldtype": "Column Break"
+  },
+  {
+   "fieldname": "produced_qty",
+   "fieldtype": "Float",
+   "label": "Produced Qty",
+   "read_only": 1
   }
  ],
  "icon": "fa fa-archive",
  "idx": 1,
  "image_field": "image",
+ "links": [],
  "max_attachments": 5,
- "modified": "2020-09-18 17:26:09.703215",
+ "modified": "2021-01-07 11:10:09.149170",
  "modified_by": "Administrator",
  "module": "Stock",
  "name": "Batch",
diff --git a/erpnext/stock/doctype/batch/batch.py b/erpnext/stock/doctype/batch/batch.py
index 508e17c..cd441b5 100644
--- a/erpnext/stock/doctype/batch/batch.py
+++ b/erpnext/stock/doctype/batch/batch.py
@@ -226,9 +226,9 @@
 	return batch.name
 
 
-def set_batch_nos(doc, warehouse_field, throw=False):
+def set_batch_nos(doc, warehouse_field, throw=False, child_table="items"):
 	"""Automatically select `batch_no` for outgoing items in item table"""
-	for d in doc.items:
+	for d in doc.get(child_table):
 		qty = d.get('stock_qty') or d.get('transfer_qty') or d.get('qty') or 0
 		has_batch_no = frappe.db.get_value('Item', d.item_code, 'has_batch_no')
 		warehouse = d.get(warehouse_field, None)
@@ -308,4 +308,9 @@
 
 	message = "Serial Nos" if len(serial_nos) > 1 else "Serial No"
 	frappe.throw(_("There is no batch found against the {0}: {1}")
-		.format(message, serial_no_link))
\ No newline at end of file
+		.format(message, serial_no_link))
+
+def make_batch(args):
+	if frappe.db.get_value("Item", args.item, "has_batch_no"):
+		args.doctype = "Batch"
+		frappe.get_doc(args).insert().name
\ No newline at end of file
diff --git a/erpnext/stock/doctype/delivery_note/delivery_note.js b/erpnext/stock/doctype/delivery_note/delivery_note.js
index 7875b9c..74cb3fc 100644
--- a/erpnext/stock/doctype/delivery_note/delivery_note.js
+++ b/erpnext/stock/doctype/delivery_note/delivery_note.js
@@ -78,6 +78,9 @@
 		});
 
 		erpnext.accounts.dimensions.setup_dimension_filters(frm, frm.doctype);
+
+		frm.set_df_property('packed_items', 'cannot_add_rows', true);
+		frm.set_df_property('packed_items', 'cannot_delete_rows', true);
 	},
 
 	print_without_amount: function(frm) {
diff --git a/erpnext/stock/doctype/delivery_note/delivery_note.json b/erpnext/stock/doctype/delivery_note/delivery_note.json
index 280fde1..f20e76f 100644
--- a/erpnext/stock/doctype/delivery_note/delivery_note.json
+++ b/erpnext/stock/doctype/delivery_note/delivery_note.json
@@ -554,8 +554,7 @@
    "oldfieldname": "packing_details",
    "oldfieldtype": "Table",
    "options": "Packed Item",
-   "print_hide": 1,
-   "read_only": 1
+   "print_hide": 1
   },
   {
    "fieldname": "product_bundle_help",
@@ -1289,7 +1288,7 @@
  "idx": 146,
  "is_submittable": 1,
  "links": [],
- "modified": "2021-04-15 23:55:49.620641",
+ "modified": "2021-06-11 19:27:30.901112",
  "modified_by": "Administrator",
  "module": "Stock",
  "name": "Delivery Note",
diff --git a/erpnext/stock/doctype/delivery_note/delivery_note.py b/erpnext/stock/doctype/delivery_note/delivery_note.py
index dd31965..fcdb5f3 100644
--- a/erpnext/stock/doctype/delivery_note/delivery_note.py
+++ b/erpnext/stock/doctype/delivery_note/delivery_note.py
@@ -129,12 +129,13 @@
 		self.validate_uom_is_integer("uom", "qty")
 		self.validate_with_previous_doc()
 
-		if self._action != 'submit' and not self.is_return:
-			set_batch_nos(self, 'warehouse', True)
-
 		from erpnext.stock.doctype.packed_item.packed_item import make_packing_list
 		make_packing_list(self)
 
+		if self._action != 'submit' and not self.is_return:
+			set_batch_nos(self, 'warehouse', throw=True)
+			set_batch_nos(self, 'warehouse', throw=True, child_table="packed_items")
+
 		self.update_current_stock()
 
 		if not self.installation_status: self.installation_status = 'Not Installed'
diff --git a/erpnext/stock/doctype/delivery_note/test_delivery_note.py b/erpnext/stock/doctype/delivery_note/test_delivery_note.py
index 0c63df0..f981aeb 100644
--- a/erpnext/stock/doctype/delivery_note/test_delivery_note.py
+++ b/erpnext/stock/doctype/delivery_note/test_delivery_note.py
@@ -7,7 +7,7 @@
 import frappe
 import json
 import frappe.defaults
-from frappe.utils import cint, nowdate, nowtime, cstr, add_days, flt, today
+from frappe.utils import nowdate, nowtime, cstr, flt
 from erpnext.stock.stock_ledger import get_previous_sle
 from erpnext.accounts.utils import get_balance_on
 from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import get_gl_entries
@@ -18,9 +18,11 @@
 from erpnext.stock.doctype.stock_reconciliation.test_stock_reconciliation \
 	import create_stock_reconciliation, set_valuation_method
 from erpnext.selling.doctype.sales_order.test_sales_order import make_sales_order, create_dn_against_so
-from erpnext.accounts.doctype.account.test_account import get_inventory_account, create_account
+from erpnext.accounts.doctype.account.test_account import get_inventory_account
 from erpnext.stock.doctype.warehouse.test_warehouse import get_warehouse
-from erpnext.stock.doctype.item.test_item import create_item
+from erpnext.stock.doctype.item.test_item import make_item
+from erpnext.selling.doctype.product_bundle.test_product_bundle import make_product_bundle
+
 
 class TestDeliveryNote(unittest.TestCase):
 	def test_over_billing_against_dn(self):
@@ -277,8 +279,6 @@
 		dn.cancel()
 
 	def test_sales_return_for_non_bundled_items_full(self):
-		from erpnext.stock.doctype.item.test_item import make_item
-
 		company = frappe.db.get_value('Warehouse', 'Stores - TCP1', 'company')
 
 		make_item("Box", {'is_stock_item': 1})
@@ -741,6 +741,25 @@
 		self.assertEqual(si2.items[0].qty, 2)
 		self.assertEqual(si2.items[1].qty, 1)
 
+
+	def test_delivery_note_bundle_with_batched_item(self):
+		batched_bundle = make_item("_Test Batched bundle", {"is_stock_item": 0})
+		batched_item = make_item("_Test Batched Item",
+				{"is_stock_item": 1, "has_batch_no": 1, "create_new_batch": 1, "batch_number_series": "TESTBATCH.#####"}
+				)
+		make_product_bundle(parent=batched_bundle.name, items=[batched_item.name])
+		make_stock_entry(item_code=batched_item.name, target="_Test Warehouse - _TC", qty=10, basic_rate=42)
+
+		try:
+			dn = create_delivery_note(item_code=batched_bundle.name, qty=1)
+		except frappe.ValidationError as e:
+			if "batch" in str(e).lower():
+				self.fail("Batch numbers not getting added to bundled items in DN.")
+			raise e
+
+		self.assertTrue("TESTBATCH" in dn.packed_items[0].batch_no, "Batch number not added in packed item")
+
+
 def create_delivery_note(**args):
 	dn = frappe.new_doc("Delivery Note")
 	args = frappe._dict(args)
diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py
index b8580f9..e488b69 100644
--- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py
+++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py
@@ -581,7 +581,6 @@
 
 @frappe.whitelist()
 def make_purchase_invoice(source_name, target_doc=None):
-	from frappe.model.mapper import get_mapped_doc
 	from erpnext.accounts.party import get_payment_terms_template
 
 	doc = frappe.get_doc('Purchase Receipt', source_name)
@@ -601,11 +600,16 @@
 
 	def update_item(source_doc, target_doc, source_parent):
 		target_doc.qty, returned_qty = get_pending_qty(source_doc)
+		if frappe.db.get_single_value("Buying Settings", "bill_for_rejected_quantity_in_purchase_invoice"):
+			target_doc.rejected_qty = 0
 		target_doc.stock_qty = flt(target_doc.qty) * flt(target_doc.conversion_factor, target_doc.precision("conversion_factor"))
 		returned_qty_map[source_doc.name] = returned_qty
 
 	def get_pending_qty(item_row):
-		pending_qty = item_row.qty - invoiced_qty_map.get(item_row.name, 0)
+		qty = item_row.qty
+		if frappe.db.get_single_value("Buying Settings", "bill_for_rejected_quantity_in_purchase_invoice"):
+			qty = item_row.received_qty
+		pending_qty = qty - invoiced_qty_map.get(item_row.name, 0)
 		returned_qty = flt(returned_qty_map.get(item_row.name, 0))
 		if returned_qty:
 			if returned_qty >= pending_qty:
diff --git a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py
index 95096d7..2eb8bfd 100644
--- a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py
+++ b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py
@@ -421,11 +421,18 @@
 		self.assertEqual(return_pr_2.items[0].qty, -3)
 
 		# Make PI against unreturned amount
+		buying_settings = frappe.get_single("Buying Settings")
+		buying_settings.bill_for_rejected_quantity_in_purchase_invoice = 0
+		buying_settings.save()
+
 		pi = make_purchase_invoice(pr.name)
 		pi.submit()
 
 		self.assertEqual(pi.items[0].qty, 3)
 
+		buying_settings.bill_for_rejected_quantity_in_purchase_invoice = 1
+		buying_settings.save()
+
 		pr.load_from_db()
 		# PR should be completed on billing all unreturned amount
 		self.assertEqual(pr.items[0].billed_amt, 150)
@@ -1004,6 +1011,47 @@
 		self.assertEqual(pr.status, "To Bill")
 		self.assertAlmostEqual(pr.per_billed, 50.0, places=2)
 
+	def test_service_item_purchase_with_perpetual_inventory(self):
+		company = '_Test Company with perpetual inventory'
+		service_item = '_Test Non Stock Item'
+
+		before_test_value = frappe.db.get_value('Company', company, 'enable_perpetual_inventory_for_non_stock_items')
+		frappe.db.set_value('Company', company, 'enable_perpetual_inventory_for_non_stock_items', 1)
+		srbnb_account = 'Stock Received But Not Billed - TCP1'
+		frappe.db.set_value('Company', company, 'service_received_but_not_billed', srbnb_account)
+
+		pr = make_purchase_receipt(
+			company=company, item=service_item,
+			warehouse='Finished Goods - TCP1', do_not_save=1
+		)
+		item_row_with_diff_rate = frappe.copy_doc(pr.items[0])
+		item_row_with_diff_rate.rate = 100
+		pr.append('items', item_row_with_diff_rate)
+
+		pr.save()
+		pr.submit()
+
+		item_one_gl_entry = frappe.db.get_all("GL Entry", {
+			'voucher_type': pr.doctype,
+			'voucher_no': pr.name,
+			'account': srbnb_account,
+			'voucher_detail_no': pr.items[0].name
+		}, pluck="name")
+
+		item_two_gl_entry = frappe.db.get_all("GL Entry", {
+			'voucher_type': pr.doctype,
+			'voucher_no': pr.name,
+			'account': srbnb_account,
+			'voucher_detail_no': pr.items[1].name
+		}, pluck="name")
+		
+		# check if the entries are not merged into one
+		# seperate entries should be made since voucher_detail_no is different
+		self.assertEqual(len(item_one_gl_entry), 1)
+		self.assertEqual(len(item_two_gl_entry), 1)
+
+		frappe.db.set_value('Company', company, 'enable_perpetual_inventory_for_non_stock_items', before_test_value)
+
 def get_sl_entries(voucher_type, voucher_no):
 	return frappe.db.sql(""" select actual_qty, warehouse, stock_value_difference
 		from `tabStock Ledger Entry` where voucher_type=%s and voucher_no=%s
diff --git a/erpnext/stock/doctype/serial_no/serial_no.json b/erpnext/stock/doctype/serial_no/serial_no.json
index 3acf3a9..a3d44af 100644
--- a/erpnext/stock/doctype/serial_no/serial_no.json
+++ b/erpnext/stock/doctype/serial_no/serial_no.json
@@ -57,7 +57,8 @@
   "more_info",
   "serial_no_details",
   "company",
-  "status"
+  "status",
+  "work_order"
  ],
  "fields": [
   {
@@ -422,12 +423,18 @@
    "label": "Status",
    "options": "\nActive\nInactive\nDelivered\nExpired",
    "read_only": 1
+  },
+  {
+   "fieldname": "work_order",
+   "fieldtype": "Link",
+   "label": "Work Order",
+   "options": "Work Order"
   }
  ],
  "icon": "fa fa-barcode",
  "idx": 1,
  "links": [],
- "modified": "2020-07-20 20:50:16.660433",
+ "modified": "2021-01-08 14:31:15.375996",
  "modified_by": "Administrator",
  "module": "Stock",
  "name": "Serial No",
diff --git a/erpnext/stock/doctype/serial_no/serial_no.py b/erpnext/stock/doctype/serial_no/serial_no.py
index b236f6a..bad7b60 100644
--- a/erpnext/stock/doctype/serial_no/serial_no.py
+++ b/erpnext/stock/doctype/serial_no/serial_no.py
@@ -473,16 +473,13 @@
 		if s.strip()]
 
 def update_args_for_serial_no(serial_no_doc, serial_no, args, is_new=False):
-	serial_no_doc.update({
-		"item_code": args.get("item_code"),
-		"company": args.get("company"),
-		"batch_no": args.get("batch_no"),
-		"via_stock_ledger": args.get("via_stock_ledger") or True,
-		"supplier": args.get("supplier"),
-		"location": args.get("location"),
-		"warehouse": (args.get("warehouse")
-			if args.get("actual_qty", 0) > 0 else None)
-	})
+	for field in ["item_code", "work_order", "company", "batch_no", "supplier", "location"]:
+		if args.get(field):
+			serial_no_doc.set(field, args.get(field))
+
+	serial_no_doc.via_stock_ledger = args.get("via_stock_ledger") or True
+	serial_no_doc.warehouse = (args.get("warehouse")
+		if args.get("actual_qty", 0) > 0 else None)
 
 	if is_new:
 		serial_no_doc.serial_no = serial_no
diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.py b/erpnext/stock/doctype/stock_entry/stock_entry.py
index 66f8b63..8f27ef4 100644
--- a/erpnext/stock/doctype/stock_entry/stock_entry.py
+++ b/erpnext/stock/doctype/stock_entry/stock_entry.py
@@ -498,6 +498,7 @@
 				d.basic_amount = flt(flt(d.transfer_qty) * flt(d.basic_rate), d.precision("basic_amount"))
 				if not d.t_warehouse:
 					outgoing_items_cost += flt(d.basic_amount)
+
 		return outgoing_items_cost
 
 	def get_args_for_incoming_rate(self, item):
@@ -854,6 +855,7 @@
 				pro_doc.run_method("update_work_order_qty")
 				if self.purpose == "Manufacture":
 					pro_doc.run_method("update_planned_qty")
+					pro_doc.update_batch_produced_qty(self)
 
 			if not pro_doc.operations:
 				pro_doc.set_actual_dates()
@@ -1076,18 +1078,54 @@
 			# in case of BOM
 			to_warehouse = item.get("default_warehouse")
 
+		args = {
+			"to_warehouse": to_warehouse,
+			"from_warehouse": "",
+			"qty": self.fg_completed_qty,
+			"item_name": item.item_name,
+			"description": item.description,
+			"stock_uom": item.stock_uom,
+			"expense_account": item.get("expense_account"),
+			"cost_center": item.get("buying_cost_center"),
+			"is_finished_item": 1
+		}
+
+		if self.work_order and self.pro_doc.has_batch_no:
+			self.set_batchwise_finished_goods(args, item)
+		else:
+			self.add_finisged_goods(args, item)
+
+	def set_batchwise_finished_goods(self, args, item):
+		qty = flt(self.fg_completed_qty)
+		filters = {
+			"reference_name": self.pro_doc.name,
+			"reference_doctype": self.pro_doc.doctype,
+			"qty_to_produce": (">", 0)
+		}
+
+		fields = ["qty_to_produce as qty", "produced_qty", "name"]
+
+		for row in frappe.get_all("Batch", filters = filters, fields = fields, order_by="creation asc"):
+			batch_qty = flt(row.qty) - flt(row.produced_qty)
+			if not batch_qty:
+				continue
+
+			if qty <=0:
+				break
+
+			fg_qty = batch_qty
+			if batch_qty >= qty:
+				fg_qty = qty
+
+			qty -= batch_qty
+			args["qty"] = fg_qty
+			args["batch_no"] = row.name
+
+			self.add_finisged_goods(args, item)
+
+	def add_finisged_goods(self, args, item):
 		self.add_to_stock_entry_detail({
-			item.name: {
-				"to_warehouse": to_warehouse,
-				"from_warehouse": "",
-				"qty": self.fg_completed_qty,
-				"item_name": item.item_name,
-				"description": item.description,
-				"stock_uom": item.stock_uom,
-				"expense_account": item.get("expense_account"),
-				"cost_center": item.get("buying_cost_center"),
-				"is_finished_item": 1
-			}
+			item.name: args
 		}, bom_no = self.bom_no)
 
 	def get_bom_raw_materials(self, qty):
@@ -1524,6 +1562,36 @@
 				material_requests.append(material_request)
 				frappe.db.set_value('Material Request', material_request, 'transfer_status', status)
 
+	def set_serial_no_batch_for_finished_good(self):
+		args = {}
+		if self.pro_doc.serial_no:
+			self.get_serial_nos_for_fg(args)
+
+		for row in self.items:
+			if row.is_finished_item and row.item_code == self.pro_doc.production_item:
+				if args.get("serial_no"):
+					row.serial_no = '\n'.join(args["serial_no"][0: cint(row.qty)])
+
+	def get_serial_nos_for_fg(self, args):
+		fields = ["`tabStock Entry`.`name`", "`tabStock Entry Detail`.`qty`",
+			"`tabStock Entry Detail`.`serial_no`", "`tabStock Entry Detail`.`batch_no`"]
+
+		filters = [["Stock Entry","work_order","=",self.work_order], ["Stock Entry","purpose","=","Manufacture"],
+			["Stock Entry","docstatus","=",1], ["Stock Entry Detail","item_code","=",self.pro_doc.production_item]]
+
+		stock_entries = frappe.get_all("Stock Entry", fields=fields, filters=filters)
+
+		if self.pro_doc.serial_no:
+			args["serial_no"] = self.get_available_serial_nos(stock_entries)
+
+	def get_available_serial_nos(self, stock_entries):
+		used_serial_nos = []
+		for row in stock_entries:
+			if row.serial_no:
+				used_serial_nos.extend(get_serial_nos(row.serial_no))
+
+		return sorted(list(set(get_serial_nos(self.pro_doc.serial_no)) - set(used_serial_nos)))
+
 @frappe.whitelist()
 def move_sample_to_retention_warehouse(company, items):
 	if isinstance(items, string_types):
@@ -1635,6 +1703,10 @@
 		if bom.quantity:
 			operating_cost_per_unit = flt(bom.operating_cost) / flt(bom.quantity)
 
+	if work_order and work_order.produced_qty and cint(frappe.db.get_single_value('Manufacturing Settings',
+		'add_corrective_operation_cost_in_finished_good_valuation')):
+		operating_cost_per_unit += flt(work_order.corrective_operation_cost) / flt(work_order.produced_qty)
+
 	return operating_cost_per_unit
 
 def get_used_alternative_items(purchase_order=None, work_order=None):
diff --git a/erpnext/stock/doctype/stock_entry_detail/stock_entry_detail.json b/erpnext/stock/doctype/stock_entry_detail/stock_entry_detail.json
index 864ff48..a178283 100644
--- a/erpnext/stock/doctype/stock_entry_detail/stock_entry_detail.json
+++ b/erpnext/stock/doctype/stock_entry_detail/stock_entry_detail.json
@@ -18,6 +18,7 @@
   "col_break2",
   "is_finished_item",
   "is_scrap_item",
+  "quality_inspection",
   "subcontracted_item",
   "section_break_8",
   "description",
@@ -69,7 +70,6 @@
   "putaway_rule",
   "column_break_51",
   "reference_purchase_receipt",
-  "quality_inspection",
   "job_card_item"
  ],
  "fields": [
@@ -548,7 +548,7 @@
  "index_web_pages_for_search": 1,
  "istable": 1,
  "links": [],
- "modified": "2021-02-11 13:47:50.158754",
+ "modified": "2021-04-22 20:08:23.799715",
  "modified_by": "Administrator",
  "module": "Stock",
  "name": "Stock Entry Detail",
diff --git a/erpnext/stock/report/incorrect_balance_qty_after_transaction/__init__.py b/erpnext/stock/report/incorrect_balance_qty_after_transaction/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/erpnext/stock/report/incorrect_balance_qty_after_transaction/__init__.py
diff --git a/erpnext/stock/report/incorrect_balance_qty_after_transaction/incorrect_balance_qty_after_transaction.js b/erpnext/stock/report/incorrect_balance_qty_after_transaction/incorrect_balance_qty_after_transaction.js
new file mode 100644
index 0000000..bf11277
--- /dev/null
+++ b/erpnext/stock/report/incorrect_balance_qty_after_transaction/incorrect_balance_qty_after_transaction.js
@@ -0,0 +1,27 @@
+// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors
+// For license information, please see license.txt
+/* eslint-disable */
+
+frappe.query_reports["Incorrect Balance Qty After Transaction"] = {
+	"filters": [
+		{
+			label: __("Company"),
+			fieldtype: "Link",
+			fieldname: "company",
+			options: "Company",
+			default: frappe.defaults.get_user_default("Company"),
+			reqd: 1
+		},
+		{
+			label: __('Item Code'),
+			fieldtype: 'Link',
+			fieldname: 'item_code',
+			options: 'Item'
+		},
+		{
+			label: __('Warehouse'),
+			fieldtype: 'Link',
+			fieldname: 'warehouse'
+		}
+	]
+};
diff --git a/erpnext/stock/report/incorrect_balance_qty_after_transaction/incorrect_balance_qty_after_transaction.json b/erpnext/stock/report/incorrect_balance_qty_after_transaction/incorrect_balance_qty_after_transaction.json
new file mode 100644
index 0000000..a5815bc
--- /dev/null
+++ b/erpnext/stock/report/incorrect_balance_qty_after_transaction/incorrect_balance_qty_after_transaction.json
@@ -0,0 +1,32 @@
+{
+ "add_total_row": 0,
+ "columns": [],
+ "creation": "2021-05-12 16:47:58.717853",
+ "disable_prepared_report": 0,
+ "disabled": 0,
+ "docstatus": 0,
+ "doctype": "Report",
+ "filters": [],
+ "idx": 0,
+ "is_standard": "Yes",
+ "modified": "2021-05-12 16:48:28.347575",
+ "modified_by": "Administrator",
+ "module": "Stock",
+ "name": "Incorrect Balance Qty After Transaction",
+ "owner": "Administrator",
+ "prepared_report": 0,
+ "ref_doctype": "Stock Ledger Entry",
+ "report_name": "Incorrect Balance Qty After Transaction",
+ "report_type": "Script Report",
+ "roles": [
+  {
+   "role": "Stock User"
+  },
+  {
+   "role": "Stock Manager"
+  },
+  {
+   "role": "Purchase User"
+  }
+ ]
+}
\ No newline at end of file
diff --git a/erpnext/stock/report/incorrect_balance_qty_after_transaction/incorrect_balance_qty_after_transaction.py b/erpnext/stock/report/incorrect_balance_qty_after_transaction/incorrect_balance_qty_after_transaction.py
new file mode 100644
index 0000000..cf174c9
--- /dev/null
+++ b/erpnext/stock/report/incorrect_balance_qty_after_transaction/incorrect_balance_qty_after_transaction.py
@@ -0,0 +1,111 @@
+# Copyright (c) 2013, Frappe Technologies Pvt. Ltd. and contributors
+# For license information, please see license.txt
+
+import frappe
+from frappe import _
+from six import iteritems
+from frappe.utils import flt
+
+def execute(filters=None):
+	columns, data = [], []
+	columns = get_columns()
+	data = get_data(filters)
+	return columns, data
+
+def get_data(filters):
+	data = get_stock_ledger_entries(filters)
+	itewise_balance_qty = {}
+
+	for row in data:
+		key = (row.item_code, row.warehouse)
+		itewise_balance_qty.setdefault(key, []).append(row)
+
+	res = validate_data(itewise_balance_qty)
+	return res
+
+def validate_data(itewise_balance_qty):
+	res = []
+	for key, data in iteritems(itewise_balance_qty):
+		row = get_incorrect_data(data)
+		if row:
+			res.append(row)
+			res.append({})
+
+	return res
+
+def get_incorrect_data(data):
+	balance_qty = 0.0
+	for row in data:
+		balance_qty += row.actual_qty
+		if row.voucher_type == "Stock Reconciliation" and not row.batch_no:
+			balance_qty = flt(row.qty_after_transaction)
+
+		row.expected_balance_qty = balance_qty
+		if abs(flt(row.expected_balance_qty) - flt(row.qty_after_transaction)) > 0.5:
+			row.differnce = abs(flt(row.expected_balance_qty) - flt(row.qty_after_transaction))
+			return row
+
+def get_stock_ledger_entries(report_filters):
+	filters = {}
+	fields = ['name', 'voucher_type', 'voucher_no', 'item_code', 'actual_qty',
+		'posting_date', 'posting_time', 'company', 'warehouse', 'qty_after_transaction', 'batch_no']
+
+	for field in ['warehouse', 'item_code', 'company']:
+		if report_filters.get(field):
+			filters[field] = report_filters.get(field)
+
+	return frappe.get_all('Stock Ledger Entry', fields = fields, filters = filters,
+		order_by = 'timestamp(posting_date, posting_time) asc, creation asc')
+
+def get_columns():
+	return [{
+		'label': _('Id'),
+		'fieldtype': 'Link',
+		'fieldname': 'name',
+		'options': 'Stock Ledger Entry',
+		'width': 120
+	}, {
+		'label': _('Posting Date'),
+		'fieldtype': 'Date',
+		'fieldname': 'posting_date',
+		'width': 110
+	}, {
+		'label': _('Voucher Type'),
+		'fieldtype': 'Link',
+		'fieldname': 'voucher_type',
+		'options': 'DocType',
+		'width': 120
+	}, {
+		'label': _('Voucher No'),
+		'fieldtype': 'Dynamic Link',
+		'fieldname': 'voucher_no',
+		'options': 'voucher_type',
+		'width': 120
+	}, {
+		'label': _('Item Code'),
+		'fieldtype': 'Link',
+		'fieldname': 'item_code',
+		'options': 'Item',
+		'width': 120
+	}, {
+		'label': _('Warehouse'),
+		'fieldtype': 'Link',
+		'fieldname': 'warehouse',
+		'options': 'Warehouse',
+		'width': 120
+	}, {
+		'label': _('Expected Balance Qty'),
+		'fieldtype': 'Float',
+		'fieldname': 'expected_balance_qty',
+		'width': 170
+	}, {
+		'label': _('Actual Balance Qty'),
+		'fieldtype': 'Float',
+		'fieldname': 'qty_after_transaction',
+		'width': 150
+	}, {
+		'label': _('Difference'),
+		'fieldtype': 'Float',
+		'fieldname': 'differnce',
+		'width': 110
+	}]
\ No newline at end of file
diff --git a/erpnext/stock/report/incorrect_serial_no_valuation/__init__.py b/erpnext/stock/report/incorrect_serial_no_valuation/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/erpnext/stock/report/incorrect_serial_no_valuation/__init__.py
diff --git a/erpnext/stock/report/incorrect_serial_no_valuation/incorrect_serial_no_valuation.js b/erpnext/stock/report/incorrect_serial_no_valuation/incorrect_serial_no_valuation.js
new file mode 100644
index 0000000..c62d480
--- /dev/null
+++ b/erpnext/stock/report/incorrect_serial_no_valuation/incorrect_serial_no_valuation.js
@@ -0,0 +1,35 @@
+// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors
+// For license information, please see license.txt
+/* eslint-disable */
+
+frappe.query_reports["Incorrect Serial No Valuation"] = {
+	"filters": [
+		{
+			label: __('Item Code'),
+			fieldtype: 'Link',
+			fieldname: 'item_code',
+			options: 'Item',
+			get_query: function() {
+				return {
+					filters: {
+						'has_serial_no': 1
+					}
+				}
+			}
+		},
+		{
+			label: __('From Date'),
+			fieldtype: 'Date',
+			fieldname: 'from_date',
+			reqd: 1,
+			default: frappe.defaults.get_user_default("year_start_date")
+		},
+		{
+			label: __('To Date'),
+			fieldtype: 'Date',
+			fieldname: 'to_date',
+			reqd: 1,
+			default: frappe.defaults.get_user_default("year_end_date")
+		}
+	]
+};
diff --git a/erpnext/stock/report/incorrect_serial_no_valuation/incorrect_serial_no_valuation.json b/erpnext/stock/report/incorrect_serial_no_valuation/incorrect_serial_no_valuation.json
new file mode 100644
index 0000000..cc384a5
--- /dev/null
+++ b/erpnext/stock/report/incorrect_serial_no_valuation/incorrect_serial_no_valuation.json
@@ -0,0 +1,36 @@
+{
+ "add_total_row": 0,
+ "columns": [],
+ "creation": "2021-05-13 13:07:00.767845",
+ "disable_prepared_report": 0,
+ "disabled": 0,
+ "docstatus": 0,
+ "doctype": "Report",
+ "filters": [],
+ "idx": 0,
+ "is_standard": "Yes",
+ "json": "{}",
+ "modified": "2021-05-13 13:07:00.767845",
+ "modified_by": "Administrator",
+ "module": "Stock",
+ "name": "Incorrect Serial No Valuation",
+ "owner": "Administrator",
+ "prepared_report": 0,
+ "ref_doctype": "Stock Ledger Entry",
+ "report_name": "Incorrect Serial No Valuation",
+ "report_type": "Script Report",
+ "roles": [
+  {
+   "role": "Stock User"
+  },
+  {
+   "role": "Accounts Manager"
+  },
+  {
+   "role": "Accounts User"
+  },
+  {
+   "role": "Stock Manager"
+  }
+ ]
+}
\ No newline at end of file
diff --git a/erpnext/stock/report/incorrect_serial_no_valuation/incorrect_serial_no_valuation.py b/erpnext/stock/report/incorrect_serial_no_valuation/incorrect_serial_no_valuation.py
new file mode 100644
index 0000000..e54cf4c
--- /dev/null
+++ b/erpnext/stock/report/incorrect_serial_no_valuation/incorrect_serial_no_valuation.py
@@ -0,0 +1,148 @@
+# Copyright (c) 2013, Frappe Technologies Pvt. Ltd. and contributors
+# For license information, please see license.txt
+
+import frappe
+import copy
+from frappe import _
+from six import iteritems
+from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos
+
+def execute(filters=None):
+	columns, data = [], []
+	columns = get_columns()
+	data = get_data(filters)
+	return columns, data
+
+def get_data(filters):
+	data = get_stock_ledger_entries(filters)
+	serial_nos_data = prepare_serial_nos(data)
+	data = get_incorrect_serial_nos(serial_nos_data)
+
+	return data
+
+def prepare_serial_nos(data):
+	serial_no_wise_data = {}
+	for row in data:
+		if not row.serial_nos:
+			continue
+
+		for serial_no in get_serial_nos(row.serial_nos):
+			sle = copy.deepcopy(row)
+			sle.serial_no = serial_no
+			sle.qty = 1 if sle.actual_qty > 0 else -1
+			sle.valuation_rate = sle.valuation_rate if sle.actual_qty > 0 else sle.valuation_rate * -1
+			serial_no_wise_data.setdefault(serial_no, []).append(sle)
+
+	return serial_no_wise_data
+
+def get_incorrect_serial_nos(serial_nos_data):
+	result = []
+
+	total_value = frappe._dict({'qty': 0, 'valuation_rate': 0, 'serial_no': frappe.bold(_('Balance'))})
+
+	for serial_no, data in iteritems(serial_nos_data):
+		total_dict = frappe._dict({'qty': 0, 'valuation_rate': 0, 'serial_no': frappe.bold(_('Total'))})
+
+		if check_incorrect_serial_data(data, total_dict):
+			result.extend(data)
+
+			total_value.qty += total_dict.qty
+			total_value.valuation_rate += total_dict.valuation_rate
+
+			result.append(total_dict)
+			result.append({})
+
+	result.append(total_value)
+
+	return result
+
+def check_incorrect_serial_data(data, total_dict):
+	incorrect_data = False
+	for row in data:
+		total_dict.qty += row.qty
+		total_dict.valuation_rate += row.valuation_rate
+
+		if ((total_dict.qty == 0 and abs(total_dict.valuation_rate) > 0) or total_dict.qty < 0):
+			incorrect_data = True
+
+	return incorrect_data
+
+def get_stock_ledger_entries(report_filters):
+	fields = ['name', 'voucher_type', 'voucher_no', 'item_code', 'serial_no as serial_nos', 'actual_qty',
+		'posting_date', 'posting_time', 'company', 'warehouse', '(stock_value_difference / actual_qty) as valuation_rate']
+
+	filters = {'serial_no': ("is", "set")}
+
+	if report_filters.get('item_code'):
+		filters['item_code'] = report_filters.get('item_code')
+
+	if report_filters.get('from_date') and report_filters.get('to_date'):
+		filters['posting_date'] = ('between', [report_filters.get('from_date'), report_filters.get('to_date')])
+
+	return frappe.get_all('Stock Ledger Entry', fields = fields, filters = filters,
+		order_by = 'timestamp(posting_date, posting_time) asc, creation asc')
+
+def get_columns():
+	return [{
+		'label': _('Company'),
+		'fieldtype': 'Link',
+		'fieldname': 'company',
+		'options': 'Company',
+		'width': 120
+	}, {
+		'label': _('Id'),
+		'fieldtype': 'Link',
+		'fieldname': 'name',
+		'options': 'Stock Ledger Entry',
+		'width': 120
+	}, {
+		'label': _('Posting Date'),
+		'fieldtype': 'Date',
+		'fieldname': 'posting_date',
+		'width': 90
+	}, {
+		'label': _('Posting Time'),
+		'fieldtype': 'Time',
+		'fieldname': 'posting_time',
+		'width': 90
+	}, {
+		'label': _('Voucher Type'),
+		'fieldtype': 'Link',
+		'fieldname': 'voucher_type',
+		'options': 'DocType',
+		'width': 100
+	}, {
+		'label': _('Voucher No'),
+		'fieldtype': 'Dynamic Link',
+		'fieldname': 'voucher_no',
+		'options': 'voucher_type',
+		'width': 110
+	}, {
+		'label': _('Item Code'),
+		'fieldtype': 'Link',
+		'fieldname': 'item_code',
+		'options': 'Item',
+		'width': 120
+	}, {
+		'label': _('Warehouse'),
+		'fieldtype': 'Link',
+		'fieldname': 'warehouse',
+		'options': 'Warehouse',
+		'width': 120
+	}, {
+		'label': _('Serial No'),
+		'fieldtype': 'Link',
+		'fieldname': 'serial_no',
+		'options': 'Serial No',
+		'width': 100
+	}, {
+		'label': _('Qty'),
+		'fieldtype': 'Float',
+		'fieldname': 'qty',
+		'width': 80
+	}, {
+		'label': _('Valuation Rate (In / Out)'),
+		'fieldtype': 'Currency',
+		'fieldname': 'valuation_rate',
+		'width': 110
+	}]
\ No newline at end of file
diff --git a/erpnext/stock/workspace/stock/stock.json b/erpnext/stock/workspace/stock/stock.json
index 3221dc4..529ce8e 100644
--- a/erpnext/stock/workspace/stock/stock.json
+++ b/erpnext/stock/workspace/stock/stock.json
@@ -15,6 +15,7 @@
  "hide_custom": 0,
  "icon": "stock",
  "idx": 0,
+ "is_default": 0,
  "is_standard": 1,
  "label": "Stock",
  "links": [
@@ -653,9 +654,44 @@
    "link_type": "Report",
    "onboard": 0,
    "type": "Link"
+  },
+  {
+   "hidden": 0,
+   "is_query_report": 0,
+   "label": "Incorrect Data Report",
+   "link_type": "DocType",
+   "onboard": 0,
+   "type": "Card Break"
+  },
+  {
+   "hidden": 0,
+   "is_query_report": 0,
+   "label": "Incorrect Serial No Qty and Valuation",
+   "link_to": "Incorrect Serial No Valuation",
+   "link_type": "Report",
+   "onboard": 0,
+   "type": "Link"
+  },
+  {
+   "hidden": 0,
+   "is_query_report": 0,
+   "label": "Incorrect Balance Qty After Transaction",
+   "link_to": "Incorrect Balance Qty After Transaction",
+   "link_type": "Report",
+   "onboard": 0,
+   "type": "Link"
+  },
+  {
+   "hidden": 0,
+   "is_query_report": 0,
+   "label": "Stock and Account Value Comparison",
+   "link_to": "Stock and Account Value Comparison",
+   "link_type": "Report",
+   "onboard": 0,
+   "type": "Link"
   }
  ],
- "modified": "2020-12-01 13:38:36.282890",
+ "modified": "2021-05-13 13:10:24.914983",
  "modified_by": "Administrator",
  "module": "Stock",
  "name": "Stock",
diff --git a/erpnext/support/report/first_response_time_for_issues/first_response_time_for_issues.js b/erpnext/support/report/first_response_time_for_issues/first_response_time_for_issues.js
index 576e0b7..18691fe 100644
--- a/erpnext/support/report/first_response_time_for_issues/first_response_time_for_issues.js
+++ b/erpnext/support/report/first_response_time_for_issues/first_response_time_for_issues.js
@@ -22,10 +22,10 @@
 	get_chart_data: function(_columns, result) {
 		return {
 			data: {
-				labels: result.map(d => d[0]),
+				labels: result.map(d => d.creation_date),
 				datasets: [{
 					name: 'First Response Time',
-					values: result.map(d => d[1])
+					values: result.map(d => d.first_response_time)
 				}]
 			},
 			type: "line",
@@ -35,8 +35,7 @@
 						hide_days: 0,
 						hide_seconds: 0
 					};
-					value = frappe.utils.get_formatted_duration(d, duration_options);
-					return value;
+					return frappe.utils.get_formatted_duration(d, duration_options);
 				}
 			}
 		}
diff --git a/erpnext/templates/includes/cart/cart_address.html b/erpnext/templates/includes/cart/cart_address.html
index 84a9430..4482bc1 100644
--- a/erpnext/templates/includes/cart/cart_address.html
+++ b/erpnext/templates/includes/cart/cart_address.html
@@ -99,6 +99,7 @@
 					fieldname: 'country',
 					fieldtype: 'Link',
 					options: 'Country',
+					only_select: true,
 					reqd: 1
 				},
 				{