Merge pull request #25109 from ankush/po_to_pr_fixes

fix: incorrect status creating PR from PO after creating PI
diff --git a/erpnext/accounts/doctype/pos_invoice/test_pos_invoice.py b/erpnext/accounts/doctype/pos_invoice/test_pos_invoice.py
index 054afe5..6d388c4 100644
--- a/erpnext/accounts/doctype/pos_invoice/test_pos_invoice.py
+++ b/erpnext/accounts/doctype/pos_invoice/test_pos_invoice.py
@@ -12,6 +12,10 @@
 from erpnext.stock.doctype.item.test_item import make_item
 
 class TestPOSInvoice(unittest.TestCase):
+	@classmethod
+	def setUpClass(cls):
+		frappe.db.sql("delete from `tabTax Rule`")
+
 	def tearDown(self):
 		if frappe.session.user != "Administrator":
 			frappe.set_user("Administrator")
diff --git a/erpnext/accounts/doctype/purchase_invoice/test_records.json b/erpnext/accounts/doctype/purchase_invoice/test_records.json
index e7166c5..9f9e90d 100644
--- a/erpnext/accounts/doctype/purchase_invoice/test_records.json
+++ b/erpnext/accounts/doctype/purchase_invoice/test_records.json
@@ -43,7 +43,7 @@
    }
   ],
   "grand_total": 0,
-  "naming_series": "_T-BILL",
+  "naming_series": "T-PINV-",
   "taxes": [
    {
     "account_head": "_Test Account Shipping Charges - _TC",
@@ -167,7 +167,7 @@
    }
   ],
   "grand_total": 0,
-  "naming_series": "_T-Purchase Invoice-",
+  "naming_series": "T-PINV-",
   "taxes": [
    {
     "account_head": "_Test Account Shipping Charges - _TC",
diff --git a/erpnext/accounts/doctype/sales_invoice/test_records.json b/erpnext/accounts/doctype/sales_invoice/test_records.json
index e00a58f..3781f8c 100644
--- a/erpnext/accounts/doctype/sales_invoice/test_records.json
+++ b/erpnext/accounts/doctype/sales_invoice/test_records.json
@@ -31,7 +31,7 @@
   "base_grand_total": 561.8,
   "grand_total": 561.8,
   "is_pos": 0,
-  "naming_series": "_T-Sales Invoice-",
+  "naming_series": "T-SINV-",
   "base_net_total": 500.0,
   "taxes": [
    {
@@ -104,7 +104,7 @@
   "base_grand_total": 630.0,
   "grand_total": 630.0,
   "is_pos": 0,
-  "naming_series": "_T-Sales Invoice-",
+  "naming_series": "T-SINV-",
   "base_net_total": 500.0,
   "taxes": [
    {
@@ -175,7 +175,7 @@
   ],
   "grand_total": 0,
   "is_pos": 0,
-  "naming_series": "_T-Sales Invoice-",
+  "naming_series": "T-SINV-",
   "taxes": [
    {
     "account_head": "_Test Account Shipping Charges - _TC",
@@ -301,7 +301,7 @@
   ],
   "grand_total": 0,
   "is_pos": 0,
-  "naming_series": "_T-Sales Invoice-",
+  "naming_series": "T-SINV-",
   "taxes": [
    {
     "account_head": "_Test Account Excise Duty - _TC",
diff --git a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py
index 90e2144..f09cc5a 100644
--- a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py
+++ b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py
@@ -2115,6 +2115,7 @@
 	si.return_against = args.return_against
 	si.currency=args.currency or "INR"
 	si.conversion_rate = args.conversion_rate or 1
+	si.naming_series = args.naming_series or "T-SINV-"
 
 	si.append("items", {
 		"item_code": args.item or args.item_code or "_Test Item",
diff --git a/erpnext/buying/doctype/supplier_scorecard/test_supplier_scorecard.py b/erpnext/buying/doctype/supplier_scorecard/test_supplier_scorecard.py
index 6e6eaed..2528240 100644
--- a/erpnext/buying/doctype/supplier_scorecard/test_supplier_scorecard.py
+++ b/erpnext/buying/doctype/supplier_scorecard/test_supplier_scorecard.py
@@ -9,9 +9,7 @@
 class TestSupplierScorecard(unittest.TestCase):
 
 	def test_create_scorecard(self):
-		delete_test_scorecards()
-		my_doc = make_supplier_scorecard()
-		doc = my_doc.insert()
+		doc = make_supplier_scorecard().insert()
 		self.assertEqual(doc.name, valid_scorecard[0].get("supplier"))
 
 	def test_criteria_weight(self):
@@ -121,7 +119,8 @@
 			{
 				"weight":100.0,
 				"doctype":"Supplier Scorecard Scoring Criteria",
-				"criteria_name":"Delivery"
+				"criteria_name":"Delivery",
+				"formula": "100"
 			}
 		],
 		"supplier":"_Test Supplier",
diff --git a/erpnext/healthcare/doctype/inpatient_medication_order/test_inpatient_medication_order.py b/erpnext/healthcare/doctype/inpatient_medication_order/test_inpatient_medication_order.py
index a21caca..21776d2 100644
--- a/erpnext/healthcare/doctype/inpatient_medication_order/test_inpatient_medication_order.py
+++ b/erpnext/healthcare/doctype/inpatient_medication_order/test_inpatient_medication_order.py
@@ -81,15 +81,8 @@
 			self.ip_record.reload()
 			discharge_patient(self.ip_record)
 
-		for entry in frappe.get_all('Inpatient Medication Entry'):
-			doc = frappe.get_doc('Inpatient Medication Entry', entry.name)
-			doc.cancel()
-			doc.delete()
-
-		for entry in frappe.get_all('Inpatient Medication Order'):
-			doc = frappe.get_doc('Inpatient Medication Order', entry.name)
-			doc.cancel()
-			doc.delete()
+		for doctype in ["Inpatient Medication Entry", "Inpatient Medication Order"]:
+			frappe.db.sql("delete from `tab{doctype}`".format(doctype=doctype))
 
 def create_dosage_form():
 	if not frappe.db.exists('Dosage Form', 'Tablet'):
diff --git a/erpnext/payroll/doctype/salary_slip/salary_slip.js b/erpnext/payroll/doctype/salary_slip/salary_slip.js
index d527839..3e8a213 100644
--- a/erpnext/payroll/doctype/salary_slip/salary_slip.js
+++ b/erpnext/payroll/doctype/salary_slip/salary_slip.js
@@ -216,7 +216,7 @@
 });
 
 var set_totals = function(frm) {
-	if (frm.doc.docstatus === 0) {
+	if (frm.doc.docstatus === 0 && frm.doc.doctype === "Salary Slip") {
 		if (frm.doc.earnings || frm.doc.deductions) {
 			frappe.call({
 				method: "set_totals",
diff --git a/erpnext/payroll/doctype/salary_slip/salary_slip.py b/erpnext/payroll/doctype/salary_slip/salary_slip.py
index 9abe57c..aa9acd8 100644
--- a/erpnext/payroll/doctype/salary_slip/salary_slip.py
+++ b/erpnext/payroll/doctype/salary_slip/salary_slip.py
@@ -618,13 +618,16 @@
 
 			component_row = self.append(component_type)
 			for attr in (
-				'depends_on_payment_days', 'salary_component', 'abbr'
+				'depends_on_payment_days', 'salary_component',
 				'do_not_include_in_total', 'is_tax_applicable',
 				'is_flexible_benefit', 'variable_based_on_taxable_salary',
 				'exempted_from_income_tax'
 			):
 				component_row.set(attr, component_data.get(attr))
 
+			abbr = component_data.get('abbr') or component_data.get('salary_component_abbr')
+			component_row.set('abbr', abbr)
+
 		if additional_salary:
 			component_row.default_amount = 0
 			component_row.additional_amount = amount
diff --git a/erpnext/payroll/doctype/salary_structure/salary_structure.py b/erpnext/payroll/doctype/salary_structure/salary_structure.py
index 1712081..352c180 100644
--- a/erpnext/payroll/doctype/salary_structure/salary_structure.py
+++ b/erpnext/payroll/doctype/salary_structure/salary_structure.py
@@ -100,7 +100,7 @@
 					from_date=from_date, base=base, variable=variable, income_tax_slab=income_tax_slab)
 			else:
 				assign_salary_structure_for_employees(employees, self,
-					payroll_payable_account=payroll_payable_account, 
+					payroll_payable_account=payroll_payable_account,
 					from_date=from_date, base=base, variable=variable, income_tax_slab=income_tax_slab)
 		else:
 			frappe.msgprint(_("No Employee Found"))
diff --git a/erpnext/projects/doctype/project/project.js b/erpnext/projects/doctype/project/project.js
index 077011a..c5265e2 100644
--- a/erpnext/projects/doctype/project/project.js
+++ b/erpnext/projects/doctype/project/project.js
@@ -18,8 +18,8 @@
 		};
 	},
 	onload: function (frm) {
-		var so = frappe.meta.get_docfield("Project", "sales_order");
-		so.get_route_options_for_new_doc = function (field) {
+		const so = frm.get_docfield("sales_order");
+		so.get_route_options_for_new_doc = () => {
 			if (frm.is_new()) return;
 			return {
 				"customer": frm.doc.customer,
diff --git a/erpnext/regional/india/e_invoice/einv_validation.json b/erpnext/regional/india/e_invoice/einv_validation.json
index 86290cf..f4a3542 100644
--- a/erpnext/regional/india/e_invoice/einv_validation.json
+++ b/erpnext/regional/india/e_invoice/einv_validation.json
@@ -919,7 +919,8 @@
         "minLength": 1,
         "maxLength": 15,
         "pattern": "^([0-9A-Z/-]){1,15}$",
-        "description": "Tranport Document Number"
+        "description": "Tranport Document Number",
+        "validationMsg": "Transport Receipt No is invalid"
       },
       "TransDocDt": {
         "type": "string",
diff --git a/erpnext/regional/india/e_invoice/utils.py b/erpnext/regional/india/e_invoice/utils.py
index 96f7f1b..8eccc3f 100644
--- a/erpnext/regional/india/e_invoice/utils.py
+++ b/erpnext/regional/india/e_invoice/utils.py
@@ -87,10 +87,10 @@
 		invoice_date=invoice_date
 	))
 
-def get_party_details(address_name):
+def get_party_details(address_name, company_address=None, billing_address=None, shipping_address=None):
 	d = frappe.get_all('Address', filters={'name': address_name}, fields=['*'])[0]
 
-	if (not d.gstin
+	if ((not d.gstin and not shipping_address)
 		or not d.city
 		or not d.pincode
 		or not d.address_title
@@ -108,8 +108,7 @@
 		# according to einvoice standard
 		pincode = 999999
 
-	return frappe._dict(dict(
-		gstin=d.gstin,
+	party_address_details = frappe._dict(dict(
 		legal_name=sanitize_for_json(d.address_title),
 		location=sanitize_for_json(d.city),
 		pincode=d.pincode,
@@ -117,6 +116,9 @@
 		address_line1=sanitize_for_json(d.address_line1),
 		address_line2=sanitize_for_json(d.address_line2)
 	))
+	if d.gstin:
+		party_address_details.gstin = d.gstin
+	return party_address_details
 
 def get_gstin_details(gstin):
 	if not hasattr(frappe.local, 'gstin_cache'):
@@ -328,14 +330,17 @@
 	item_list = get_item_list(invoice)
 	doc_details = get_doc_details(invoice)
 	invoice_value_details = get_invoice_value_details(invoice)
-	seller_details = get_party_details(invoice.company_address)
+	seller_details = get_party_details(invoice.company_address, company_address=1)
 
 	if invoice.gst_category == 'Overseas':
 		buyer_details = get_overseas_address_details(invoice.customer_address)
 	else:
-		buyer_details = get_party_details(invoice.customer_address)
-		place_of_supply = get_place_of_supply(invoice, invoice.doctype) or sanitize_for_json(invoice.billing_address_gstin)
-		place_of_supply = place_of_supply[:2]
+		buyer_details = get_party_details(invoice.customer_address, billing_address=1)
+		place_of_supply = get_place_of_supply(invoice, invoice.doctype)
+		if place_of_supply:
+			place_of_supply = place_of_supply.split('-')[0]
+		else:
+			place_of_supply = sanitize_for_json(invoice.billing_address_gstin)[:2]
 		buyer_details.update(dict(place_of_supply=place_of_supply))
 
 	shipping_details = payment_details = prev_doc_details = eway_bill_details = frappe._dict({})
@@ -343,7 +348,7 @@
 		if invoice.gst_category == 'Overseas':
 			shipping_details = get_overseas_address_details(invoice.shipping_address_name)
 		else:
-			shipping_details = get_party_details(invoice.shipping_address_name)
+			shipping_details = get_party_details(invoice.shipping_address_name, shipping_address=1)
 
 	if invoice.is_pos and invoice.base_paid_amount:
 		payment_details = get_payment_details(invoice)
@@ -391,7 +396,9 @@
 		snippet = json_string[start:end]
 		frappe.throw(_("Error in input data. Please check for any special characters near following input: <br> {}").format(snippet))
 
-def validate_einvoice(validations, einvoice, errors=[]):
+def validate_einvoice(validations, einvoice, errors=None):
+	if errors is None:
+		errors = []
 	for fieldname, field_validation in validations.items():
 		value = einvoice.get(fieldname, None)
 		if not value or value == "None":
diff --git a/erpnext/regional/india/setup.py b/erpnext/regional/india/setup.py
index ee49aae..f7689cf 100644
--- a/erpnext/regional/india/setup.py
+++ b/erpnext/regional/india/setup.py
@@ -5,6 +5,7 @@
 
 import frappe, os, json
 from frappe.custom.doctype.custom_field.custom_field import create_custom_fields
+from frappe.custom.doctype.property_setter.property_setter import make_property_setter
 from frappe.permissions import add_permission, update_permission_property
 from erpnext.regional.india import states
 from erpnext.accounts.utils import get_fiscal_year, FiscalYearError
@@ -18,6 +19,7 @@
 # TODO: for all countries
 def setup_company_independent_fixtures():
 	make_custom_fields()
+	make_property_setters()
 	add_permissions()
 	add_custom_roles_for_reports()
 	frappe.enqueue('erpnext.regional.india.setup.add_hsn_sac_codes', now=frappe.flags.in_test)
@@ -110,6 +112,11 @@
 	frappe.db.set_value("Print Format", "GST Tax Invoice", "disabled", 0)
 	frappe.db.set_value("Print Format", "GST E-Invoice", "disabled", 0)
 
+def make_property_setters():
+	# GST rules do not allow for an invoice no. bigger than 16 characters
+	make_property_setter('Sales Invoice', 'naming_series', 'options', 'SINV-.YY.-\nSRET-.YY.-', '')
+	make_property_setter('Purchase Invoice', 'naming_series', 'options', 'PINV-.YY.-\nPRET-.YY.-', '')
+
 def make_custom_fields(update=True):
 	hsn_sac_field = dict(fieldname='gst_hsn_code', label='HSN/SAC',
 		fieldtype='Data', fetch_from='item_code.gst_hsn_code', insert_after='description',
@@ -860,4 +867,4 @@
 		})
 
 		rule.flags.ignore_mandatory = True
-		rule.save()
\ No newline at end of file
+		rule.save()
diff --git a/erpnext/selling/doctype/customer/customer.py b/erpnext/selling/doctype/customer/customer.py
index c452594..96b3fa4 100644
--- a/erpnext/selling/doctype/customer/customer.py
+++ b/erpnext/selling/doctype/customer/customer.py
@@ -230,13 +230,20 @@
 			frappe.db.set(self, "customer_name", newdn)
 
 	def set_loyalty_program(self):
-		if self.loyalty_program: return
+		if self.loyalty_program:
+			return
+
 		loyalty_program = get_loyalty_programs(self)
-		if not loyalty_program: return
+		if not loyalty_program:
+			return
+
 		if len(loyalty_program) == 1:
 			self.loyalty_program = loyalty_program[0]
 		else:
-			frappe.msgprint(_("Multiple Loyalty Program found for the Customer. Please select manually."))
+			frappe.msgprint(
+				_("Multiple Loyalty Programs found for Customer {}. Please select manually.")
+				.format(frappe.bold(self.customer_name))
+			)
 
 	def create_onboarding_docs(self, args):
 		defaults = frappe.defaults.get_defaults()
@@ -340,7 +347,6 @@
 @frappe.whitelist()
 def get_loyalty_programs(doc):
 	''' returns applicable loyalty programs for a customer '''
-	from frappe.desk.treeview import get_children
 
 	lp_details = []
 	loyalty_programs = frappe.get_all("Loyalty Program",
@@ -349,15 +355,33 @@
 			"ifnull(to_date, '2500-01-01')": [">=", today()]})
 
 	for loyalty_program in loyalty_programs:
-		customer_groups = [d.value for d in get_children("Customer Group", loyalty_program.customer_group)] + [loyalty_program.customer_group]
-		customer_territories = [d.value for d in get_children("Territory", loyalty_program.customer_territory)] + [loyalty_program.customer_territory]
-
-		if (not loyalty_program.customer_group or doc.customer_group in customer_groups)\
-			and (not loyalty_program.customer_territory or doc.territory in customer_territories):
+		if (
+			(not loyalty_program.customer_group
+			or doc.customer_group in get_nested_links(
+				"Customer Group",
+				loyalty_program.customer_group,
+				doc.flags.ignore_permissions
+			))
+			and (not loyalty_program.customer_territory
+			or doc.territory in get_nested_links(
+				"Territory",
+				loyalty_program.customer_territory,
+				doc.flags.ignore_permissions
+			))
+		):
 			lp_details.append(loyalty_program.name)
 
 	return lp_details
 
+def get_nested_links(link_doctype, link_name, ignore_permissions=False):
+	from frappe.desk.treeview import _get_children
+
+	links = [link_name]
+	for d in _get_children(link_doctype, link_name, ignore_permissions):
+		links.append(d.value)
+
+	return links
+
 @frappe.whitelist()
 @frappe.validate_and_sanitize_search_inputs
 def get_customer_list(doctype, txt, searchfield, start, page_len, filters=None):
@@ -572,4 +596,4 @@
 		""", {
 			'customer': customer,
 			'txt': '%%%s%%' % txt
-		})
\ No newline at end of file
+		})
diff --git a/erpnext/stock/doctype/item/test_records.json b/erpnext/stock/doctype/item/test_records.json
index 909c4ee..c1f20a4 100644
--- a/erpnext/stock/doctype/item/test_records.json
+++ b/erpnext/stock/doctype/item/test_records.json
@@ -12,6 +12,7 @@
   "item_name": "_Test Item",
   "apply_warehouse_wise_reorder_level": 1,
   "gst_hsn_code": "999800",
+  "opening_stock": 10,
   "valuation_rate": 100,
   "item_defaults": [{
     "company": "_Test Company",
diff --git a/erpnext/stock/doctype/quality_inspection/quality_inspection.py b/erpnext/stock/doctype/quality_inspection/quality_inspection.py
index 05819ab..469511a 100644
--- a/erpnext/stock/doctype/quality_inspection/quality_inspection.py
+++ b/erpnext/stock/doctype/quality_inspection/quality_inspection.py
@@ -64,17 +64,21 @@
 					(quality_inspection, self.modified, self.reference_name, self.item_code))
 
 		else:
+			args = [quality_inspection, self.modified, self.reference_name, self.item_code]
 			doctype = self.reference_type + ' Item'
+
 			if self.reference_type == 'Stock Entry':
 				doctype = 'Stock Entry Detail'
 
 			if self.reference_type and self.reference_name:
 				conditions = ""
 				if self.batch_no and self.docstatus == 1:
-					conditions += " and t1.batch_no = '%s'"%(self.batch_no)
+					conditions += " and t1.batch_no = %s"
+					args.append(self.batch_no)
 
 				if self.docstatus == 2: # if cancel, then remove qi link wherever same name
-					conditions += " and t1.quality_inspection = '%s'"%(self.name)
+					conditions += " and t1.quality_inspection = %s"
+					args.append(self.name)
 
 				frappe.db.sql("""
 					UPDATE
@@ -87,7 +91,7 @@
 						and t1.parent = t2.name
 						{conditions}
 				""".format(parent_doc=self.reference_type, child_doc=doctype, conditions=conditions),
-					(quality_inspection, self.modified, self.reference_name, self.item_code))
+					args)
 
 	def inspect_and_set_status(self):
 		for reading in self.readings: