Merge pull request #30338 from deepeshgarg007/bank_transaction_currency_symbol_fixes

fix: Currency symbol in bank transactions
diff --git a/erpnext/accounts/doctype/opening_invoice_creation_tool_item/opening_invoice_creation_tool_item.json b/erpnext/accounts/doctype/opening_invoice_creation_tool_item/opening_invoice_creation_tool_item.json
index 5c19091..ed8ff7c 100644
--- a/erpnext/accounts/doctype/opening_invoice_creation_tool_item/opening_invoice_creation_tool_item.json
+++ b/erpnext/accounts/doctype/opening_invoice_creation_tool_item/opening_invoice_creation_tool_item.json
@@ -110,13 +110,12 @@
    "description": "Reference number of the invoice from the previous system",
    "fieldname": "invoice_number",
    "fieldtype": "Data",
-   "in_list_view": 1,
    "label": "Invoice Number"
   }
  ],
  "istable": 1,
  "links": [],
- "modified": "2021-12-17 19:25:06.053187",
+ "modified": "2022-03-21 19:31:45.382656",
  "modified_by": "Administrator",
  "module": "Accounts",
  "name": "Opening Invoice Creation Tool Item",
@@ -125,5 +124,6 @@
  "quick_entry": 1,
  "sort_field": "modified",
  "sort_order": "DESC",
+ "states": [],
  "track_changes": 1
 }
\ No newline at end of file
diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py
index e107912..9433489 100644
--- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py
+++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py
@@ -1711,6 +1711,7 @@
 		}
 	}, target_doc, set_missing_values)
 
+	doclist.set_onload('ignore_price_list', True)
 	return doclist
 
 @frappe.whitelist()
diff --git a/erpnext/buying/doctype/purchase_order/purchase_order.py b/erpnext/buying/doctype/purchase_order/purchase_order.py
index 2c66542..f93f9fe 100644
--- a/erpnext/buying/doctype/purchase_order/purchase_order.py
+++ b/erpnext/buying/doctype/purchase_order/purchase_order.py
@@ -442,6 +442,8 @@
 		}
 	}, target_doc, set_missing_values)
 
+	doc.set_onload('ignore_price_list', True)
+
 	return doc
 
 @frappe.whitelist()
@@ -509,6 +511,7 @@
 
 	doc = get_mapped_doc("Purchase Order", source_name,	fields,
 		target_doc, postprocess, ignore_permissions=ignore_permissions)
+	doc.set_onload('ignore_price_list', True)
 
 	return doc
 
diff --git a/erpnext/buying/doctype/supplier_quotation/supplier_quotation.py b/erpnext/buying/doctype/supplier_quotation/supplier_quotation.py
index 81fc324..d578863 100644
--- a/erpnext/buying/doctype/supplier_quotation/supplier_quotation.py
+++ b/erpnext/buying/doctype/supplier_quotation/supplier_quotation.py
@@ -139,6 +139,7 @@
 		},
 	}, target_doc, set_missing_values)
 
+	doclist.set_onload('ignore_price_list', True)
 	return doclist
 
 @frappe.whitelist()
diff --git a/erpnext/controllers/queries.py b/erpnext/controllers/queries.py
index dd9b45c..d870823 100644
--- a/erpnext/controllers/queries.py
+++ b/erpnext/controllers/queries.py
@@ -168,7 +168,7 @@
 				{account_type_condition}
 				AND is_group = 0
 				AND company = %(company)s
-				AND account_currency = %(currency)s
+				AND (account_currency = %(currency)s or ifnull(account_currency, '') = '')
 				AND `{searchfield}` LIKE %(txt)s
 				{mcond}
 			ORDER BY idx DESC, name
diff --git a/erpnext/controllers/sales_and_purchase_return.py b/erpnext/controllers/sales_and_purchase_return.py
index 2166633..d6296eb 100644
--- a/erpnext/controllers/sales_and_purchase_return.py
+++ b/erpnext/controllers/sales_and_purchase_return.py
@@ -399,6 +399,8 @@
 		}
 	}, target_doc, set_missing_values)
 
+	doclist.set_onload('ignore_price_list', True)
+
 	return doclist
 
 def get_rate_for_return(voucher_type, voucher_no, item_code, return_against=None,
diff --git a/erpnext/controllers/taxes_and_totals.py b/erpnext/controllers/taxes_and_totals.py
index 19acc10..29da5f1 100644
--- a/erpnext/controllers/taxes_and_totals.py
+++ b/erpnext/controllers/taxes_and_totals.py
@@ -114,20 +114,16 @@
 			for item in self.doc.get("items"):
 				self.doc.round_floats_in(item)
 
-				if not item.rate:
-					item.rate = item.price_list_rate
-
 				if item.discount_percentage == 100:
 					item.rate = 0.0
 				elif item.price_list_rate:
-					if item.pricing_rules or abs(item.discount_percentage) > 0:
+					if not item.rate or (item.pricing_rules and item.discount_percentage > 0):
 						item.rate = flt(item.price_list_rate *
 							(1.0 - (item.discount_percentage / 100.0)), item.precision("rate"))
 
-						if abs(item.discount_percentage) > 0:
-							item.discount_amount = item.price_list_rate * (item.discount_percentage / 100.0)
+						item.discount_amount = item.price_list_rate * (item.discount_percentage / 100.0)
 
-					elif item.discount_amount or item.pricing_rules:
+					elif item.discount_amount and item.pricing_rules:
 						item.rate =  item.price_list_rate - item.discount_amount
 
 				if item.doctype in ['Quotation Item', 'Sales Order Item', 'Delivery Note Item', 'Sales Invoice Item',
diff --git a/erpnext/e_commerce/doctype/website_item/website_item.js b/erpnext/e_commerce/doctype/website_item/website_item.js
index 741e78f..7108cab 100644
--- a/erpnext/e_commerce/doctype/website_item/website_item.js
+++ b/erpnext/e_commerce/doctype/website_item/website_item.js
@@ -5,6 +5,12 @@
 	onload: function(frm) {
 		// should never check Private
 		frm.fields_dict["website_image"].df.is_private = 0;
+
+		frm.set_query("website_warehouse", () => {
+			return {
+				filters: {"is_group": 0}
+			};
+		});
 	},
 
 	image: function() {
diff --git a/erpnext/e_commerce/variant_selector/test_variant_selector.py b/erpnext/e_commerce/variant_selector/test_variant_selector.py
index ee098e1..3eb75cb 100644
--- a/erpnext/e_commerce/variant_selector/test_variant_selector.py
+++ b/erpnext/e_commerce/variant_selector/test_variant_selector.py
@@ -15,6 +15,7 @@
 
 	@classmethod
 	def setUpClass(cls):
+		super().setUpClass()
 		template_item = make_item("Test-Tshirt-Temp", {
 			"has_variant": 1,
 			"variant_based_on": "Item Attribute",
diff --git a/erpnext/manufacturing/doctype/job_card/job_card.py b/erpnext/manufacturing/doctype/job_card/job_card.py
index 5f492d7..960d0e5 100644
--- a/erpnext/manufacturing/doctype/job_card/job_card.py
+++ b/erpnext/manufacturing/doctype/job_card/job_card.py
@@ -500,6 +500,9 @@
 			2: "Cancelled"
 		}[self.docstatus or 0]
 
+		if self.for_quantity <= self.transferred_qty:
+			self.status = 'Material Transferred'
+
 		if self.time_logs:
 			self.status = 'Work In Progress'
 
@@ -507,10 +510,6 @@
 			(self.for_quantity <= self.total_completed_qty or not self.items)):
 			self.status = 'Completed'
 
-		if self.status != 'Completed':
-			if self.for_quantity <= self.transferred_qty:
-				self.status = 'Material Transferred'
-
 		if update_status:
 			self.db_set('status', self.status)
 
diff --git a/erpnext/manufacturing/doctype/job_card/test_job_card.py b/erpnext/manufacturing/doctype/job_card/test_job_card.py
index 33425d2..c5841c1 100644
--- a/erpnext/manufacturing/doctype/job_card/test_job_card.py
+++ b/erpnext/manufacturing/doctype/job_card/test_job_card.py
@@ -169,6 +169,7 @@
 
 		job_card_name = frappe.db.get_value("Job Card", {'work_order': self.work_order.name})
 		job_card = frappe.get_doc("Job Card", job_card_name)
+		self.assertEqual(job_card.status, "Open")
 
 		# fully transfer both RMs
 		transfer_entry_1 = make_stock_entry_from_jc(job_card_name)
diff --git a/erpnext/manufacturing/doctype/production_plan/production_plan.js b/erpnext/manufacturing/doctype/production_plan/production_plan.js
index e8759f5..59ddf1f 100644
--- a/erpnext/manufacturing/doctype/production_plan/production_plan.js
+++ b/erpnext/manufacturing/doctype/production_plan/production_plan.js
@@ -2,6 +2,13 @@
 // For license information, please see license.txt
 
 frappe.ui.form.on('Production Plan', {
+
+	before_save: function(frm) {
+		// preserve temporary names on production plan item to re-link sub-assembly items
+		frm.doc.po_items.forEach(item => {
+			item.temporary_name = item.name;
+		});
+	},
 	setup: function(frm) {
 		frm.custom_make_buttons = {
 			'Work Order': 'Work Order / Subcontract PO',
diff --git a/erpnext/manufacturing/doctype/production_plan/production_plan.py b/erpnext/manufacturing/doctype/production_plan/production_plan.py
index 2b6e696..349f40e 100644
--- a/erpnext/manufacturing/doctype/production_plan/production_plan.py
+++ b/erpnext/manufacturing/doctype/production_plan/production_plan.py
@@ -32,6 +32,7 @@
 		self.set_pending_qty_in_row_without_reference()
 		self.calculate_total_planned_qty()
 		self.set_status()
+		self._rename_temporary_references()
 
 	def set_pending_qty_in_row_without_reference(self):
 		"Set Pending Qty in independent rows (not from SO or MR)."
@@ -57,6 +58,18 @@
 			if not flt(d.planned_qty):
 				frappe.throw(_("Please enter Planned Qty for Item {0} at row {1}").format(d.item_code, d.idx))
 
+	def _rename_temporary_references(self):
+		""" po_items and sub_assembly_items items are both constructed client side without saving.
+
+			Attempt to fix linkages by using temporary names to map final row names.
+		"""
+		new_name_map = {d.temporary_name: d.name for d in self.po_items if d.temporary_name}
+		actual_names = {d.name for d in self.po_items}
+
+		for sub_assy in self.sub_assembly_items:
+			if sub_assy.production_plan_item not in actual_names:
+				sub_assy.production_plan_item = new_name_map.get(sub_assy.production_plan_item)
+
 	@frappe.whitelist()
 	def get_open_sales_orders(self):
 		""" Pull sales orders  which are pending to deliver based on criteria selected"""
diff --git a/erpnext/manufacturing/doctype/production_plan/test_production_plan.py b/erpnext/manufacturing/doctype/production_plan/test_production_plan.py
index 6425374..ec49703 100644
--- a/erpnext/manufacturing/doctype/production_plan/test_production_plan.py
+++ b/erpnext/manufacturing/doctype/production_plan/test_production_plan.py
@@ -667,6 +667,39 @@
 		wo_doc.submit()
 		self.assertEqual(wo_doc.qty, 0.55)
 
+	def test_temporary_name_relinking(self):
+
+		pp = frappe.new_doc("Production Plan")
+
+		# this can not be unittested so mocking data that would be expected
+		# from client side.
+		for _ in range(10):
+			po_item = pp.append("po_items", {
+				"name": frappe.generate_hash(length=10),
+				"temporary_name": frappe.generate_hash(length=10),
+			})
+			pp.append("sub_assembly_items", {
+				"production_plan_item": po_item.temporary_name
+			})
+		pp._rename_temporary_references()
+
+		for po_item, subassy_item in zip(pp.po_items, pp.sub_assembly_items):
+			self.assertEqual(po_item.name, subassy_item.production_plan_item)
+
+		# bad links should be erased
+		pp.append("sub_assembly_items", {
+			"production_plan_item": frappe.generate_hash(length=16)
+		})
+		pp._rename_temporary_references()
+		self.assertIsNone(pp.sub_assembly_items[-1].production_plan_item)
+		pp.sub_assembly_items.pop()
+
+		# reattempting on same doc shouldn't change anything
+		pp._rename_temporary_references()
+		for po_item, subassy_item in zip(pp.po_items, pp.sub_assembly_items):
+			self.assertEqual(po_item.name, subassy_item.production_plan_item)
+
+
 def create_production_plan(**args):
 	"""
 	sales_order (obj): Sales Order Doc Object
diff --git a/erpnext/manufacturing/doctype/production_plan_item/production_plan_item.json b/erpnext/manufacturing/doctype/production_plan_item/production_plan_item.json
index f829d57..df5862f 100644
--- a/erpnext/manufacturing/doctype/production_plan_item/production_plan_item.json
+++ b/erpnext/manufacturing/doctype/production_plan_item/production_plan_item.json
@@ -27,7 +27,8 @@
   "material_request",
   "material_request_item",
   "product_bundle_item",
-  "item_reference"
+  "item_reference",
+  "temporary_name"
  ],
  "fields": [
   {
@@ -204,17 +205,25 @@
    "fieldtype": "Data",
    "hidden": 1,
    "label": "Item Reference"
+  },
+  {
+   "fieldname": "temporary_name",
+   "fieldtype": "Data",
+   "hidden": 1,
+   "label": "temporary name"
   }
  ],
  "idx": 1,
  "istable": 1,
  "links": [],
- "modified": "2021-06-28 18:31:06.822168",
+ "modified": "2022-03-24 04:54:09.940224",
  "modified_by": "Administrator",
  "module": "Manufacturing",
  "name": "Production Plan Item",
+ "naming_rule": "Random",
  "owner": "Administrator",
  "permissions": [],
  "sort_field": "modified",
- "sort_order": "ASC"
+ "sort_order": "ASC",
+ "states": []
 }
\ No newline at end of file
diff --git a/erpnext/manufacturing/doctype/work_order/work_order.py b/erpnext/manufacturing/doctype/work_order/work_order.py
index 7eb40ec..e832ac9 100644
--- a/erpnext/manufacturing/doctype/work_order/work_order.py
+++ b/erpnext/manufacturing/doctype/work_order/work_order.py
@@ -457,7 +457,8 @@
 			mr_obj.update_requested_qty([self.material_request_item])
 
 	def update_ordered_qty(self):
-		if self.production_plan and self.production_plan_item:
+		if self.production_plan and self.production_plan_item \
+			and not self.production_plan_sub_assembly_item:
 			qty = frappe.get_value("Production Plan Item", self.production_plan_item, "ordered_qty") or 0.0
 
 			if self.docstatus == 1:
@@ -644,9 +645,13 @@
 		if not self.qty > 0:
 			frappe.throw(_("Quantity to Manufacture must be greater than 0."))
 
-		if self.production_plan and self.production_plan_item:
+		if self.production_plan and self.production_plan_item \
+			and not self.production_plan_sub_assembly_item:
 			qty_dict = frappe.db.get_value("Production Plan Item", self.production_plan_item, ["planned_qty", "ordered_qty"], as_dict=1)
 
+			if not qty_dict:
+				return
+
 			allowance_qty = flt(frappe.db.get_single_value("Manufacturing Settings",
 			"overproduction_percentage_for_work_order"))/100 * qty_dict.get("planned_qty", 0)
 
@@ -1150,6 +1155,10 @@
 		doc.insert()
 		frappe.msgprint(_("Job card {0} created").format(get_link_to_form("Job Card", doc.name)), alert=True)
 
+	if enable_capacity_planning:
+		# automatically added scheduling rows shouldn't change status to WIP
+		doc.db_set("status", "Open")
+
 	return doc
 
 def get_work_order_operation_data(work_order, operation, workstation):
diff --git a/erpnext/patches.txt b/erpnext/patches.txt
index 3199912..ab16045 100644
--- a/erpnext/patches.txt
+++ b/erpnext/patches.txt
@@ -1,7 +1,7 @@
 [pre_model_sync]
 erpnext.patches.v12_0.update_is_cancelled_field
-erpnext.patches.v13_0.add_bin_unique_constraint
 erpnext.patches.v11_0.rename_production_order_to_work_order
+erpnext.patches.v13_0.add_bin_unique_constraint
 erpnext.patches.v11_0.refactor_naming_series
 erpnext.patches.v11_0.refactor_autoname_naming
 execute:frappe.reload_doc("accounts", "doctype", "POS Payment Method") #2020-05-28
@@ -360,4 +360,4 @@
 erpnext.patches.v14_0.delete_non_profit_doctypes
 erpnext.patches.v14_0.update_employee_advance_status
 erpnext.patches.v13_0.add_cost_center_in_loans
-erpnext.patches.v13_0.remove_unknown_links_to_prod_plan_items
+erpnext.patches.v13_0.remove_unknown_links_to_prod_plan_items # 24-03-2022
diff --git a/erpnext/patches/v13_0/rename_issue_doctype_fields.py b/erpnext/patches/v13_0/rename_issue_doctype_fields.py
index bf5438c..80d5165 100644
--- a/erpnext/patches/v13_0/rename_issue_doctype_fields.py
+++ b/erpnext/patches/v13_0/rename_issue_doctype_fields.py
@@ -60,7 +60,7 @@
 
 def convert_to_seconds(value, unit):
 	seconds = 0
-	if value == 0:
+	if not value:
 		return seconds
 	if unit == 'Hours':
 		seconds = value * 3600
diff --git a/erpnext/payroll/doctype/payroll_entry/payroll_entry.py b/erpnext/payroll/doctype/payroll_entry/payroll_entry.py
index 32b0f0f..9061c5f 100644
--- a/erpnext/payroll/doctype/payroll_entry/payroll_entry.py
+++ b/erpnext/payroll/doctype/payroll_entry/payroll_entry.py
@@ -708,6 +708,8 @@
 	if not_submitted_ss:
 		frappe.msgprint(_("Could not submit some Salary Slips"))
 
+	frappe.flags.via_payroll_entry = False
+
 @frappe.whitelist()
 @frappe.validate_and_sanitize_search_inputs
 def get_payroll_entries_for_jv(doctype, txt, searchfield, start, page_len, filters):
diff --git a/erpnext/payroll/doctype/salary_slip/test_salary_slip.py b/erpnext/payroll/doctype/salary_slip/test_salary_slip.py
index 5e41b66..f072135 100644
--- a/erpnext/payroll/doctype/salary_slip/test_salary_slip.py
+++ b/erpnext/payroll/doctype/salary_slip/test_salary_slip.py
@@ -38,6 +38,8 @@
 class TestSalarySlip(unittest.TestCase):
 	def setUp(self):
 		setup_test()
+		frappe.flags.pop("via_payroll_entry", None)
+
 
 	def tearDown(self):
 		frappe.db.rollback()
@@ -409,15 +411,17 @@
 		"email_salary_slip_to_employee": 1
 	})
 	def test_email_salary_slip(self):
-		frappe.db.sql("delete from `tabEmail Queue`")
+		frappe.db.delete("Email Queue")
 
-		make_employee("test_email_salary_slip@salary.com", company="_Test Company")
-		ss = make_employee_salary_slip("test_email_salary_slip@salary.com", "Monthly", "Test Salary Slip Email")
+		user_id = "test_email_salary_slip@salary.com"
+
+		make_employee(user_id, company="_Test Company")
+		ss = make_employee_salary_slip(user_id, "Monthly", "Test Salary Slip Email")
 		ss.company = "_Test Company"
 		ss.save()
 		ss.submit()
 
-		email_queue = frappe.db.sql("""select name from `tabEmail Queue`""")
+		email_queue = frappe.db.a_row_exists("Email Queue")
 		self.assertTrue(email_queue)
 
 	def test_loan_repayment_salary_slip(self):
diff --git a/erpnext/public/js/controllers/transaction.js b/erpnext/public/js/controllers/transaction.js
index 00373a6..19e12e3 100644
--- a/erpnext/public/js/controllers/transaction.js
+++ b/erpnext/public/js/controllers/transaction.js
@@ -1042,9 +1042,9 @@
 		var me = this;
 		this.set_dynamic_labels();
 		var company_currency = this.get_company_currency();
-		// Added `ignore_pricing_rule` to determine if document is loading after mapping from another doc
+		// Added `ignore_price_list` to determine if document is loading after mapping from another doc
 		if(this.frm.doc.currency && this.frm.doc.currency !== company_currency
-				&& !this.frm.doc.ignore_pricing_rule) {
+				&& !this.frm.doc.__onload.ignore_price_list) {
 
 			this.get_exchange_rate(transaction_date, this.frm.doc.currency, company_currency,
 				function(exchange_rate) {
@@ -1070,7 +1070,7 @@
 		}
 
 		if(flt(this.frm.doc.conversion_rate)>0.0) {
-			if(this.frm.doc.ignore_pricing_rule) {
+			if(this.frm.doc.__onload && this.frm.doc.__onload.ignore_price_list) {
 				this.calculate_taxes_and_totals();
 			} else if (!this.in_apply_price_list){
 				this.apply_price_list();
@@ -1144,8 +1144,8 @@
 		this.set_dynamic_labels();
 
 		var company_currency = this.get_company_currency();
-		// Added `ignore_pricing_rule` to determine if document is loading after mapping from another doc
-		if(this.frm.doc.price_list_currency !== company_currency  && !this.frm.doc.ignore_pricing_rule) {
+		// Added `ignore_price_list` to determine if document is loading after mapping from another doc
+		if(this.frm.doc.price_list_currency !== company_currency  && !this.frm.doc.__onload.ignore_price_list) {
 			this.get_exchange_rate(this.frm.doc.posting_date, this.frm.doc.price_list_currency, company_currency,
 				function(exchange_rate) {
 					me.frm.set_value("plc_conversion_rate", exchange_rate);
@@ -1884,6 +1884,7 @@
 				callback: function(r) {
 					if(!r.exc) {
 						item.item_tax_rate = r.message;
+						me.add_taxes_from_item_tax_template(item.item_tax_rate);
 						me.calculate_taxes_and_totals();
 					}
 				}
diff --git a/erpnext/public/js/utils/serial_no_batch_selector.js b/erpnext/public/js/utils/serial_no_batch_selector.js
index 81ff351..f484545 100644
--- a/erpnext/public/js/utils/serial_no_batch_selector.js
+++ b/erpnext/public/js/utils/serial_no_batch_selector.js
@@ -608,6 +608,11 @@
 		&& doc.fg_completed_qty
 		&& erpnext.stock.bom
 		&& erpnext.stock.bom.name === doc.bom_no;
-	const itemChecks = !!item  && !item.allow_alternative_item;
+	const itemChecks = !!item
+		&& !item.allow_alternative_item
+		&& erpnext.stock.bom && erpnext.stock.items
+		&& (item.item_code in erpnext.stock.bom.items);
 	return docChecks && itemChecks;
 }
+
+//# sourceURL=serial_no_batch_selector.js
diff --git a/erpnext/regional/india/utils.py b/erpnext/regional/india/utils.py
index d443f9c..55b563e 100644
--- a/erpnext/regional/india/utils.py
+++ b/erpnext/regional/india/utils.py
@@ -19,8 +19,9 @@
 
 
 def validate_gstin_for_india(doc, method):
-	if hasattr(doc, 'gst_state') and doc.gst_state:
-		doc.gst_state_number = state_numbers[doc.gst_state]
+	if hasattr(doc, 'gst_state'):
+		set_gst_state_and_state_number(doc)
+
 	if not hasattr(doc, 'gstin') or not doc.gstin:
 		return
 
@@ -50,7 +51,6 @@
 			frappe.throw(_("The input you've entered doesn't match the format of GSTIN."), title=_("Invalid GSTIN"))
 
 		validate_gstin_check_digit(doc.gstin)
-		set_gst_state_and_state_number(doc)
 
 		if not doc.gst_state:
 			frappe.throw(_("Please enter GST state"), title=_("Invalid State"))
@@ -82,17 +82,14 @@
 				frappe.db.set_value(link.link_doctype, {'name': link.link_name, 'gst_category': 'Unregistered'}, 'gst_category', 'Registered Regular')
 
 def set_gst_state_and_state_number(doc):
-	if not doc.gst_state:
-		if not doc.state:
-			return
+	if not doc.gst_state and doc.state:
 		state = doc.state.lower()
 		states_lowercase = {s.lower():s for s in states}
 		if state in states_lowercase:
 			doc.gst_state = states_lowercase[state]
 		else:
 			return
-
-	doc.gst_state_number = state_numbers[doc.gst_state]
+	doc.gst_state_number = state_numbers.get(doc.gst_state)
 
 def validate_gstin_check_digit(gstin, label='GSTIN'):
 	''' Function to validate the check digit of the GSTIN.'''
diff --git a/erpnext/selling/doctype/quotation/quotation.py b/erpnext/selling/doctype/quotation/quotation.py
index 06b4ff1..d602f0c 100644
--- a/erpnext/selling/doctype/quotation/quotation.py
+++ b/erpnext/selling/doctype/quotation/quotation.py
@@ -206,6 +206,7 @@
 		}, target_doc, set_missing_values, ignore_permissions=ignore_permissions)
 
 	# postprocess: fetch shipping address, set missing values
+	doclist.set_onload('ignore_price_list', True)
 
 	return doclist
 
@@ -269,6 +270,8 @@
 			}
 		}, target_doc, set_missing_values, ignore_permissions=ignore_permissions)
 
+	doclist.set_onload('ignore_price_list', True)
+
 	return doclist
 
 def _make_customer(source_name, ignore_permissions=False):
diff --git a/erpnext/selling/doctype/sales_order/sales_order.py b/erpnext/selling/doctype/sales_order/sales_order.py
index 73e3d19..b906ec0 100755
--- a/erpnext/selling/doctype/sales_order/sales_order.py
+++ b/erpnext/selling/doctype/sales_order/sales_order.py
@@ -584,6 +584,8 @@
 
 	target_doc = get_mapped_doc("Sales Order", source_name, mapper, target_doc, set_missing_values)
 
+	target_doc.set_onload('ignore_price_list', True)
+
 	return target_doc
 
 @frappe.whitelist()
@@ -664,6 +666,8 @@
 	if automatically_fetch_payment_terms:
 		doclist.set_payment_schedule()
 
+	doclist.set_onload('ignore_price_list', True)
+
 	return doclist
 
 @frappe.whitelist()
diff --git a/erpnext/setup/doctype/sales_person/sales_person.js b/erpnext/setup/doctype/sales_person/sales_person.js
index b71a92f..d86a8f3 100644
--- a/erpnext/setup/doctype/sales_person/sales_person.js
+++ b/erpnext/setup/doctype/sales_person/sales_person.js
@@ -4,8 +4,12 @@
 frappe.ui.form.on('Sales Person', {
 	refresh: function(frm) {
 		if(frm.doc.__onload && frm.doc.__onload.dashboard_info) {
-			var info = frm.doc.__onload.dashboard_info;
-			frm.dashboard.add_indicator(__('Total Contribution Amount: {0}', [format_currency(info.allocated_amount, info.currency)]), 'blue');
+			let info = frm.doc.__onload.dashboard_info;
+			frm.dashboard.add_indicator(__('Total Contribution Amount Against Orders: {0}',
+				[format_currency(info.allocated_amount_against_order, info.currency)]), 'blue');
+
+			frm.dashboard.add_indicator(__('Total Contribution Amount Against Invoices: {0}',
+				[format_currency(info.allocated_amount_against_invoice, info.currency)]), 'blue');
 		}
 	},
 
diff --git a/erpnext/setup/doctype/sales_person/sales_person.py b/erpnext/setup/doctype/sales_person/sales_person.py
index b79a566..6af1b31 100644
--- a/erpnext/setup/doctype/sales_person/sales_person.py
+++ b/erpnext/setup/doctype/sales_person/sales_person.py
@@ -28,14 +28,17 @@
 	def load_dashboard_info(self):
 		company_default_currency = get_default_currency()
 
-		allocated_amount = frappe.db.sql("""
-			select sum(allocated_amount)
-			from `tabSales Team`
-			where sales_person = %s and docstatus=1 and parenttype = 'Sales Order'
-		""",(self.sales_person_name))
+		allocated_amount_against_order = flt(frappe.db.get_value('Sales Team',
+			{'docstatus': 1, 'parenttype': 'Sales Order', 'sales_person': self.sales_person_name},
+			'sum(allocated_amount)'))
+
+		allocated_amount_against_invoice = flt(frappe.db.get_value('Sales Team',
+			{'docstatus': 1, 'parenttype': 'Sales Invoice', 'sales_person': self.sales_person_name},
+			'sum(allocated_amount)'))
 
 		info = {}
-		info["allocated_amount"] = flt(allocated_amount[0][0]) if allocated_amount else 0
+		info["allocated_amount_against_order"] = allocated_amount_against_order
+		info["allocated_amount_against_invoice"] = allocated_amount_against_invoice
 		info["currency"] = company_default_currency
 
 		self.set_onload('dashboard_info', info)
diff --git a/erpnext/stock/dashboard/item_dashboard.py b/erpnext/stock/dashboard/item_dashboard.py
index 57d78a2..75aa1d3 100644
--- a/erpnext/stock/dashboard/item_dashboard.py
+++ b/erpnext/stock/dashboard/item_dashboard.py
@@ -40,18 +40,15 @@
 		filters=filters,
 		order_by=sort_by + ' ' + sort_order,
 		limit_start=start,
-		limit_page_length='21')
+		limit_page_length=21)
 
 	precision = cint(frappe.db.get_single_value("System Settings", "float_precision"))
 
 	for item in items:
 		item.update({
-			'item_name': frappe.get_cached_value(
-				"Item", item.item_code, 'item_name'),
-			'disable_quick_entry': frappe.get_cached_value(
-				"Item", item.item_code, 'has_batch_no')
-			or frappe.get_cached_value(
-				"Item", item.item_code, 'has_serial_no'),
+			'item_name': frappe.get_cached_value("Item", item.item_code, 'item_name'),
+			'disable_quick_entry': frappe.get_cached_value( "Item", item.item_code, 'has_batch_no')
+							or frappe.get_cached_value( "Item", item.item_code, 'has_serial_no'),
 			'projected_qty': flt(item.projected_qty, precision),
 			'reserved_qty': flt(item.reserved_qty, precision),
 			'reserved_qty_for_production': flt(item.reserved_qty_for_production, precision),
diff --git a/erpnext/stock/doctype/delivery_note/delivery_note.py b/erpnext/stock/doctype/delivery_note/delivery_note.py
index ffa2f93..492f90b 100644
--- a/erpnext/stock/doctype/delivery_note/delivery_note.py
+++ b/erpnext/stock/doctype/delivery_note/delivery_note.py
@@ -519,6 +519,8 @@
 	if automatically_fetch_payment_terms:
 		doc.set_payment_schedule()
 
+	doc.set_onload('ignore_price_list', True)
+
 	return doc
 
 @frappe.whitelist()
diff --git a/erpnext/stock/doctype/item/test_item.py b/erpnext/stock/doctype/item/test_item.py
index 112420f..05e6e76 100644
--- a/erpnext/stock/doctype/item/test_item.py
+++ b/erpnext/stock/doctype/item/test_item.py
@@ -685,6 +685,12 @@
 		# standalone return
 		make_purchase_receipt(is_return=True, qty=-1, **typical_args)
 
+	def test_item_dashboard(self):
+		from erpnext.stock.dashboard.item_dashboard import get_data
+
+		self.assertTrue(get_data(item_code="_Test Item"))
+		self.assertTrue(get_data(warehouse="_Test Warehouse - _TC"))
+		self.assertTrue(get_data(item_group="All Item Groups"))
 
 
 def set_item_variant_settings(fields):
diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py
index 5bb337e..4bf37fe 100644
--- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py
+++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py
@@ -823,6 +823,7 @@
 		}
 	}, target_doc, set_missing_values)
 
+	doclist.set_onload('ignore_price_list', True)
 	return doclist
 
 def get_invoiced_qty_map(purchase_receipt):
diff --git a/erpnext/stock/doctype/putaway_rule/test_putaway_rule.py b/erpnext/stock/doctype/putaway_rule/test_putaway_rule.py
index 4e8d71f..0ec812c 100644
--- a/erpnext/stock/doctype/putaway_rule/test_putaway_rule.py
+++ b/erpnext/stock/doctype/putaway_rule/test_putaway_rule.py
@@ -396,6 +396,21 @@
 		rule_1.delete()
 		rule_2.delete()
 
+	def test_warehouse_capacity_dashbord(self):
+		from erpnext.stock.dashboard.warehouse_capacity_dashboard import get_data
+
+		item = "_Rice"
+		rule = create_putaway_rule(item_code=item, warehouse=self.warehouse_1, capacity=500,
+			uom="Kg")
+
+		capacities = get_data(warehouse=self.warehouse_1)
+		for capacity in capacities:
+			if capacity.item_code == item and capacity.warehouse == self.warehouse_1:
+				self.assertEqual(capacity.stock_capacity, 500)
+
+		get_data(warehouse=self.warehouse_1)
+		rule.delete()
+
 def create_putaway_rule(**args):
 	args = frappe._dict(args)
 	putaway = frappe.new_doc("Putaway Rule")
diff --git a/erpnext/stock/doctype/serial_no/serial_no.py b/erpnext/stock/doctype/serial_no/serial_no.py
index c77c6c3..4c06012 100644
--- a/erpnext/stock/doctype/serial_no/serial_no.py
+++ b/erpnext/stock/doctype/serial_no/serial_no.py
@@ -3,6 +3,7 @@
 
 
 import json
+from typing import List, Optional, Union
 
 import frappe
 from frappe import ValidationError, _
@@ -574,22 +575,30 @@
 	return serial_nos
 
 @frappe.whitelist()
-def auto_fetch_serial_number(qty, item_code, warehouse,
-		posting_date=None, batch_nos=None, for_doctype=None, exclude_sr_nos=None):
+def auto_fetch_serial_number(
+		qty: float,
+		item_code: str,
+		warehouse: str,
+		posting_date: Optional[str] = None,
+		batch_nos: Optional[Union[str, List[str]]] = None,
+		for_doctype: Optional[str] = None,
+		exclude_sr_nos: Optional[List[str]] = None
+	) -> List[str]:
 
 	filters = frappe._dict({"item_code": item_code, "warehouse": warehouse})
 
 	if exclude_sr_nos is None:
 		exclude_sr_nos = []
 	else:
+		exclude_sr_nos = safe_json_loads(exclude_sr_nos)
 		exclude_sr_nos = get_serial_nos(clean_serial_no_string("\n".join(exclude_sr_nos)))
 
 	if batch_nos:
 		batch_nos = safe_json_loads(batch_nos)
 		if isinstance(batch_nos, list):
 			filters.batch_no = batch_nos
-		elif isinstance(batch_nos, str):
-			filters.batch_no = [batch_nos]
+		else:
+			filters.batch_no = [str(batch_nos)]
 
 	if posting_date:
 		filters.expiry_date = posting_date
diff --git a/erpnext/stock/doctype/serial_no/test_serial_no.py b/erpnext/stock/doctype/serial_no/test_serial_no.py
index cca6307..7df0a56 100644
--- a/erpnext/stock/doctype/serial_no/test_serial_no.py
+++ b/erpnext/stock/doctype/serial_no/test_serial_no.py
@@ -274,7 +274,8 @@
 				msg=f"{partial_fetch} should be subset of {first_fetch}")
 
 		# exclusion
-		remaining = auto_fetch_serial_number(3, item_code, warehouse, exclude_sr_nos=partial_fetch)
+		remaining = auto_fetch_serial_number(3, item_code, warehouse,
+				exclude_sr_nos=json.dumps(partial_fetch))
 		self.assertEqual(sorted(remaining + partial_fetch), first_fetch)
 
 		# batchwise
diff --git a/erpnext/tests/test_zform_loads.py b/erpnext/tests/test_zform_loads.py
index b6fb636..5b82c7b 100644
--- a/erpnext/tests/test_zform_loads.py
+++ b/erpnext/tests/test_zform_loads.py
@@ -1,13 +1,14 @@
-""" dumb test to check all function calls on known form loads """
-
-import unittest
+""" smoak tests to check basic functionality calls on known form loads."""
 
 import frappe
 from frappe.desk.form.load import getdoc
+from frappe.tests.utils import FrappeTestCase, change_settings
+from frappe.www.printview import get_html_and_style
 
 
-class TestFormLoads(unittest.TestCase):
+class TestFormLoads(FrappeTestCase):
 
+	@change_settings("Print Settings", {"allow_print_for_cancelled": 1})
 	def test_load(self):
 		erpnext_modules = frappe.get_all("Module Def", filters={"app_name": "erpnext"}, pluck="name")
 		doctypes = frappe.get_all("DocType", {"istable": 0, "issingle": 0, "is_virtual": 0, "module": ("in", erpnext_modules)}, pluck="name")
@@ -17,14 +18,35 @@
 			if not last_doc:
 				continue
 			with self.subTest(msg=f"Loading {doctype} - {last_doc}", doctype=doctype, last_doc=last_doc):
-				try:
-					# reset previous response
-					frappe.response = frappe._dict({"docs":[]})
-					frappe.response.docinfo = None
+				self.assertFormLoad(doctype, last_doc)
+				self.assertDocPrint(doctype, last_doc)
 
-					getdoc(doctype, last_doc)
-				except Exception as e:
-					self.fail(f"Failed to load {doctype} - {last_doc}: {e}")
+	def assertFormLoad(self, doctype, docname):
+		# reset previous response
+		frappe.response = frappe._dict({"docs":[]})
+		frappe.response.docinfo = None
 
-				self.assertTrue(frappe.response.docs, msg=f"expected document in reponse, found: {frappe.response.docs}")
-				self.assertTrue(frappe.response.docinfo, msg=f"expected docinfo in reponse, found: {frappe.response.docinfo}")
+		try:
+			getdoc(doctype, docname)
+		except Exception as e:
+			self.fail(f"Failed to load {doctype}-{docname}: {e}")
+
+		self.assertTrue(frappe.response.docs, msg=f"expected document in reponse, found: {frappe.response.docs}")
+		self.assertTrue(frappe.response.docinfo, msg=f"expected docinfo in reponse, found: {frappe.response.docinfo}")
+
+	def assertDocPrint(self, doctype, docname):
+		doc = frappe.get_doc(doctype, docname)
+		doc.set("__onload", frappe._dict())
+		doc.run_method("onload")
+
+		messages_before = frappe.get_message_log()
+		ret = get_html_and_style(doc=doc.as_json(), print_format="Standard", no_letterhead=1)
+		messages_after = frappe.get_message_log()
+
+		if len(messages_after) > len(messages_before):
+			new_messages = messages_after[len(messages_before):]
+			self.fail("Print view showing error/warnings: \n"
+					+ "\n".join(str(msg) for msg in new_messages))
+
+		# html should exist
+		self.assertTrue(bool(ret["html"]))