Merge branch 'develop' into partially-submit-drop-ship-items-issue
diff --git a/erpnext/buying/report/purchase_analytics/purchase_analytics.js b/erpnext/buying/report/purchase_analytics/purchase_analytics.js
index e17973c..ba8535a 100644
--- a/erpnext/buying/report/purchase_analytics/purchase_analytics.js
+++ b/erpnext/buying/report/purchase_analytics/purchase_analytics.js
@@ -75,62 +75,70 @@
 		return Object.assign(options, {
 			checkboxColumn: true,
 			events: {
-				onCheckRow: function(data) {
+				onCheckRow: function (data) {
+					if (!data) return;
+
+					const data_doctype = $(
+						data[2].html
+					)[0].attributes.getNamedItem("data-doctype").value;
+					const tree_type = frappe.query_report.filters[0].value;
+					if (data_doctype != tree_type) return;
+
 					row_name = data[2].content;
 					length = data.length;
 
-					var tree_type = frappe.query_report.filters[0].value;
-
-					if(tree_type == "Supplier" || tree_type == "Item") {
-						row_values = data.slice(4,length-1).map(function (column) {
-							return column.content;
-						})
-					}
-					else {
-						row_values = data.slice(3,length-1).map(function (column) {
-							return column.content;
-						})
+					if (tree_type == "Supplier") {
+						row_values = data
+							.slice(4, length - 1)
+							.map(function (column) {
+								return column.content;
+							});
+					} else if (tree_type == "Item") {
+						row_values = data
+							.slice(5, length - 1)
+							.map(function (column) {
+								return column.content;
+							});
+					} else {
+						row_values = data
+							.slice(3, length - 1)
+							.map(function (column) {
+								return column.content;
+							});
 					}
 
-					entry  = {
-						'name':row_name,
-						'values':row_values
-					}
+					entry = {
+						name: row_name,
+						values: row_values,
+					};
 
 					let raw_data = frappe.query_report.chart.data;
 					let new_datasets = raw_data.datasets;
 
-					var found = false;
-
-					for(var i=0; i < new_datasets.length;i++){
-						if(new_datasets[i].name == row_name){
-							found = true;
-							new_datasets.splice(i,1);
-							break;
+					let element_found = new_datasets.some((element, index, array)=>{
+						if(element.name == row_name){
+							array.splice(index, 1)
+							return true
 						}
-					}
+						return false
+					})
 
-					if(!found){
+					if (!element_found) {
 						new_datasets.push(entry);
 					}
-
 					let new_data = {
 						labels: raw_data.labels,
-						datasets: new_datasets
-					}
-
-					setTimeout(() => {
-						frappe.query_report.chart.update(new_data)
-					},500)
-
-
-					setTimeout(() => {
-						frappe.query_report.chart.draw(true);
-					}, 1000)
+						datasets: new_datasets,
+					};
+					chart_options = {
+						data: new_data,
+						type: "line",
+					};
+					frappe.query_report.render_chart(chart_options);
 
 					frappe.query_report.raw_chart_data = new_data;
 				},
-			}
+			},
 		});
 	}
 }
diff --git a/erpnext/controllers/sales_and_purchase_return.py b/erpnext/controllers/sales_and_purchase_return.py
index 7979226..a048d6e 100644
--- a/erpnext/controllers/sales_and_purchase_return.py
+++ b/erpnext/controllers/sales_and_purchase_return.py
@@ -328,6 +328,7 @@
 			target_doc.po_detail = source_doc.po_detail
 			target_doc.pr_detail = source_doc.pr_detail
 			target_doc.purchase_invoice_item = source_doc.name
+			target_doc.price_list_rate = 0
 
 		elif doctype == "Delivery Note":
 			returned_qty_map = get_returned_qty_map_for_row(source_doc.name, doctype)
@@ -353,6 +354,7 @@
 			target_doc.dn_detail = source_doc.dn_detail
 			target_doc.expense_account = source_doc.expense_account
 			target_doc.sales_invoice_item = source_doc.name
+			target_doc.price_list_rate = 0
 			if default_warehouse_for_sales_return:
 				target_doc.warehouse = default_warehouse_for_sales_return
 
diff --git a/erpnext/controllers/selling_controller.py b/erpnext/controllers/selling_controller.py
index 85cfb95..812021f 100644
--- a/erpnext/controllers/selling_controller.py
+++ b/erpnext/controllers/selling_controller.py
@@ -233,7 +233,7 @@
 							'allow_zero_valuation': d.allow_zero_valuation_rate,
 							'sales_invoice_item': d.get("sales_invoice_item"),
 							'dn_detail': d.get("dn_detail"),
-							'incoming_rate': p.incoming_rate
+							'incoming_rate': p.get("incoming_rate")
 						}))
 			else:
 				il.append(frappe._dict({
@@ -252,7 +252,7 @@
 					'allow_zero_valuation': d.allow_zero_valuation_rate,
 					'sales_invoice_item': d.get("sales_invoice_item"),
 					'dn_detail': d.get("dn_detail"),
-					'incoming_rate': d.incoming_rate
+					'incoming_rate': d.get("incoming_rate")
 				}))
 		return il
 
diff --git a/erpnext/controllers/tests/test_item_variant.py b/erpnext/controllers/tests/test_item_variant.py
index c257215..813f0a0 100644
--- a/erpnext/controllers/tests/test_item_variant.py
+++ b/erpnext/controllers/tests/test_item_variant.py
@@ -6,6 +6,7 @@
 
 from erpnext.stock.doctype.item.test_item import set_item_variant_settings
 from erpnext.controllers.item_variant import copy_attributes_to_variant, make_variant_item_code
+from erpnext.stock.doctype.quality_inspection.test_quality_inspection import create_quality_inspection_parameter
 
 from six import string_types
 
@@ -56,6 +57,8 @@
 
 	qc = frappe.new_doc("Quality Inspection Template")
 	qc.quality_inspection_template_name = qc_template
+
+	create_quality_inspection_parameter("Moisture")
 	qc.append('item_quality_inspection_parameter', {
 		"specification": "Moisture",
 		"value": "&lt; 5%",
diff --git a/erpnext/healthcare/doctype/healthcare_settings/healthcare_settings.json b/erpnext/healthcare/doctype/healthcare_settings/healthcare_settings.json
index 0104386..b33c326 100644
--- a/erpnext/healthcare/doctype/healthcare_settings/healthcare_settings.json
+++ b/erpnext/healthcare/doctype/healthcare_settings/healthcare_settings.json
@@ -17,6 +17,8 @@
   "enable_free_follow_ups",
   "max_visits",
   "valid_days",
+  "inpatient_settings_section",
+  "allow_discharge_despite_unbilled_services",
   "healthcare_service_items",
   "inpatient_visit_charge_item",
   "op_consulting_charge_item",
@@ -302,11 +304,22 @@
    "fieldname": "enable_free_follow_ups",
    "fieldtype": "Check",
    "label": "Enable Free Follow-ups"
+  },
+  {
+   "fieldname": "inpatient_settings_section",
+   "fieldtype": "Section Break",
+   "label": "Inpatient Settings"
+  },
+  {
+   "default": "0",
+   "fieldname": "allow_discharge_despite_unbilled_services",
+   "fieldtype": "Check",
+   "label": "Allow Discharge Despite Unbilled Healthcare Services"
   }
  ],
  "issingle": 1,
  "links": [],
- "modified": "2020-07-08 15:17:21.543218",
+ "modified": "2021-01-04 10:19:22.329272",
  "modified_by": "Administrator",
  "module": "Healthcare",
  "name": "Healthcare Settings",
diff --git a/erpnext/healthcare/doctype/inpatient_record/inpatient_record.py b/erpnext/healthcare/doctype/inpatient_record/inpatient_record.py
index bc76970..dc549a6 100644
--- a/erpnext/healthcare/doctype/inpatient_record/inpatient_record.py
+++ b/erpnext/healthcare/doctype/inpatient_record/inpatient_record.py
@@ -5,7 +5,7 @@
 from __future__ import unicode_literals
 import frappe, json
 from frappe import _
-from frappe.utils import today, now_datetime, getdate, get_datetime
+from frappe.utils import today, now_datetime, getdate, get_datetime, get_link_to_form
 from frappe.model.document import Document
 from frappe.desk.reportview import get_match_cond
 
@@ -113,6 +113,7 @@
 	inpatient_record.status = 'Admission Scheduled'
 	inpatient_record.save(ignore_permissions = True)
 
+
 @frappe.whitelist()
 def schedule_discharge(args):
 	discharge_order = json.loads(args)
@@ -126,16 +127,19 @@
 		frappe.db.set_value('Patient', discharge_order['patient'], 'inpatient_status', inpatient_record.status)
 		frappe.db.set_value('Patient Encounter', inpatient_record.discharge_encounter, 'inpatient_status', inpatient_record.status)
 
+
 def set_details_from_ip_order(inpatient_record, ip_order):
 	for key in ip_order:
 		inpatient_record.set(key, ip_order[key])
 
+
 def set_ip_child_records(inpatient_record, inpatient_record_child, encounter_child):
 	for item in encounter_child:
 		table = inpatient_record.append(inpatient_record_child)
 		for df in table.meta.get('fields'):
 			table.set(df.fieldname, item.get(df.fieldname))
 
+
 def check_out_inpatient(inpatient_record):
 	if inpatient_record.inpatient_occupancies:
 		for inpatient_occupancy in inpatient_record.inpatient_occupancies:
@@ -144,54 +148,88 @@
 				inpatient_occupancy.check_out = now_datetime()
 				frappe.db.set_value("Healthcare Service Unit", inpatient_occupancy.service_unit, "occupancy_status", "Vacant")
 
+
 def discharge_patient(inpatient_record):
-	validate_invoiced_inpatient(inpatient_record)
+	validate_inpatient_invoicing(inpatient_record)
 	inpatient_record.discharge_date = today()
 	inpatient_record.status = "Discharged"
 
 	inpatient_record.save(ignore_permissions = True)
 
-def validate_invoiced_inpatient(inpatient_record):
-	pending_invoices = []
+
+def validate_inpatient_invoicing(inpatient_record):
+	if frappe.db.get_single_value("Healthcare Settings", "allow_discharge_despite_unbilled_services"):
+		return
+
+	pending_invoices = get_pending_invoices(inpatient_record)
+
+	if pending_invoices:
+		message = _("Cannot mark Inpatient Record as Discharged since there are unbilled services. ")
+
+		formatted_doc_rows = ''
+
+		for doctype, docnames in pending_invoices.items():
+			formatted_doc_rows += """
+				<td>{0}</td>
+				<td>{1}</td>
+			</tr>""".format(doctype, docnames)
+
+		message += """
+			<table class='table'>
+				<thead>
+					<th>{0}</th>
+					<th>{1}</th>
+				</thead>
+				{2}
+			</table>
+		""".format(_("Healthcare Service"), _("Documents"), formatted_doc_rows)
+
+		frappe.throw(message, title=_("Unbilled Services"), is_minimizable=True, wide=True)
+
+
+def get_pending_invoices(inpatient_record):
+	pending_invoices = {}
 	if inpatient_record.inpatient_occupancies:
 		service_unit_names = False
 		for inpatient_occupancy in inpatient_record.inpatient_occupancies:
-			if inpatient_occupancy.invoiced != 1:
+			if not inpatient_occupancy.invoiced:
 				if service_unit_names:
 					service_unit_names += ", " + inpatient_occupancy.service_unit
 				else:
 					service_unit_names = inpatient_occupancy.service_unit
 		if service_unit_names:
-			pending_invoices.append("Inpatient Occupancy (" + service_unit_names + ")")
+			pending_invoices["Inpatient Occupancy"] = service_unit_names
 
 	docs = ["Patient Appointment", "Patient Encounter", "Lab Test", "Clinical Procedure"]
 
 	for doc in docs:
-		doc_name_list = get_inpatient_docs_not_invoiced(doc, inpatient_record)
+		doc_name_list = get_unbilled_inpatient_docs(doc, inpatient_record)
 		if doc_name_list:
 			pending_invoices = get_pending_doc(doc, doc_name_list, pending_invoices)
 
-	if pending_invoices:
-		frappe.throw(_("Can not mark Inpatient Record Discharged, there are Unbilled Invoices {0}").format(", "
-			.join(pending_invoices)), title=_('Unbilled Invoices'))
+	return pending_invoices
+
 
 def get_pending_doc(doc, doc_name_list, pending_invoices):
 	if doc_name_list:
 		doc_ids = False
 		for doc_name in doc_name_list:
+			doc_link = get_link_to_form(doc, doc_name.name)
 			if doc_ids:
-				doc_ids += ", "+doc_name.name
+				doc_ids += ", " + doc_link
 			else:
-				doc_ids = doc_name.name
+				doc_ids = doc_link
 		if doc_ids:
-			pending_invoices.append(doc + " (" + doc_ids + ")")
+			pending_invoices[doc] =  doc_ids
 
 	return pending_invoices
 
-def get_inpatient_docs_not_invoiced(doc, inpatient_record):
+
+def get_unbilled_inpatient_docs(doc, inpatient_record):
 	return frappe.db.get_list(doc, filters = {'patient': inpatient_record.patient,
 					'inpatient_record': inpatient_record.name, 'docstatus': 1, 'invoiced': 0})
 
+
 def admit_patient(inpatient_record, service_unit, check_in, expected_discharge=None):
 	inpatient_record.admitted_datetime = check_in
 	inpatient_record.status = 'Admitted'
@@ -203,6 +241,7 @@
 	frappe.db.set_value('Patient', inpatient_record.patient, 'inpatient_status', 'Admitted')
 	frappe.db.set_value('Patient', inpatient_record.patient, 'inpatient_record', inpatient_record.name)
 
+
 def transfer_patient(inpatient_record, service_unit, check_in):
 	item_line = inpatient_record.append('inpatient_occupancies', {})
 	item_line.service_unit = service_unit
@@ -212,6 +251,7 @@
 
 	frappe.db.set_value("Healthcare Service Unit", service_unit, "occupancy_status", "Occupied")
 
+
 def patient_leave_service_unit(inpatient_record, check_out, leave_from):
 	if inpatient_record.inpatient_occupancies:
 		for inpatient_occupancy in inpatient_record.inpatient_occupancies:
@@ -221,6 +261,7 @@
 				frappe.db.set_value("Healthcare Service Unit", inpatient_occupancy.service_unit, "occupancy_status", "Vacant")
 	inpatient_record.save(ignore_permissions = True)
 
+
 @frappe.whitelist()
 @frappe.validate_and_sanitize_search_inputs
 def get_leave_from(doctype, txt, searchfield, start, page_len, filters):
diff --git a/erpnext/healthcare/doctype/inpatient_record/test_inpatient_record.py b/erpnext/healthcare/doctype/inpatient_record/test_inpatient_record.py
index 70706ad..e8a9444 100644
--- a/erpnext/healthcare/doctype/inpatient_record/test_inpatient_record.py
+++ b/erpnext/healthcare/doctype/inpatient_record/test_inpatient_record.py
@@ -40,6 +40,31 @@
 		self.assertEqual(None, frappe.db.get_value("Patient", patient, "inpatient_record"))
 		self.assertEqual(None, frappe.db.get_value("Patient", patient, "inpatient_status"))
 
+	def test_allow_discharge_despite_unbilled_services(self):
+		frappe.db.sql("""delete from `tabInpatient Record`""")
+		setup_inpatient_settings()
+		patient = create_patient()
+		# Schedule Admission
+		ip_record = create_inpatient(patient)
+		ip_record.expected_length_of_stay = 0
+		ip_record.save(ignore_permissions = True)
+
+		# Admit
+		service_unit = get_healthcare_service_unit()
+		admit_patient(ip_record, service_unit, now_datetime())
+
+		# Discharge
+		schedule_discharge(frappe.as_json({"patient": patient}))
+		self.assertEqual("Vacant", frappe.db.get_value("Healthcare Service Unit", service_unit, "occupancy_status"))
+
+		ip_record = frappe.get_doc("Inpatient Record", ip_record.name)
+		# Should not validate Pending Invoices
+		ip_record.discharge()
+
+		self.assertEqual(None, frappe.db.get_value("Patient", patient, "inpatient_record"))
+		self.assertEqual(None, frappe.db.get_value("Patient", patient, "inpatient_status"))
+
+
 	def test_validate_overlap_admission(self):
 		frappe.db.sql("""delete from `tabInpatient Record`""")
 		patient = create_patient()
@@ -63,6 +88,13 @@
 			inpatient_occupancy.invoiced = 1
 		ip_record.save(ignore_permissions = True)
 
+
+def setup_inpatient_settings():
+	settings = frappe.get_single("Healthcare Settings")
+	settings.allow_discharge_despite_unbilled_services = 1
+	settings.save()
+
+
 def create_inpatient(patient):
 	patient_obj = frappe.get_doc('Patient', patient)
 	inpatient_record = frappe.new_doc('Inpatient Record')
@@ -78,6 +110,7 @@
 	inpatient_record.scheduled_date = today()
 	return inpatient_record
 
+
 def get_healthcare_service_unit():
 	service_unit = get_random("Healthcare Service Unit", filters={"inpatient_occupancy": 1})
 	if not service_unit:
@@ -105,6 +138,7 @@
 		return service_unit.name
 	return service_unit
 
+
 def get_service_unit_type():
 	service_unit_type = get_random("Healthcare Service Unit Type", filters={"inpatient_occupancy": 1})
 
@@ -116,6 +150,7 @@
 		return service_unit_type.name
 	return service_unit_type
 
+
 def create_patient():
 	patient = frappe.db.exists('Patient', '_Test IPD Patient')
 	if not patient:
diff --git a/erpnext/healthcare/doctype/patient_appointment/test_patient_appointment.py b/erpnext/healthcare/doctype/patient_appointment/test_patient_appointment.py
index 3df7ba1..b681ed1 100644
--- a/erpnext/healthcare/doctype/patient_appointment/test_patient_appointment.py
+++ b/erpnext/healthcare/doctype/patient_appointment/test_patient_appointment.py
@@ -23,8 +23,10 @@
 		self.assertEquals(appointment.status, 'Open')
 		appointment = create_appointment(patient, practitioner, add_days(nowdate(), 2))
 		self.assertEquals(appointment.status, 'Scheduled')
-		create_encounter(appointment)
+		encounter = create_encounter(appointment)
 		self.assertEquals(frappe.db.get_value('Patient Appointment', appointment.name, 'status'), 'Closed')
+		encounter.cancel()
+		self.assertEquals(frappe.db.get_value('Patient Appointment', appointment.name, 'status'), 'Open')
 
 	def test_start_encounter(self):
 		patient, medical_department, practitioner = create_healthcare_docs()
diff --git a/erpnext/healthcare/doctype/therapy_plan/test_therapy_plan.py b/erpnext/healthcare/doctype/therapy_plan/test_therapy_plan.py
index a061c66..7fb159d 100644
--- a/erpnext/healthcare/doctype/therapy_plan/test_therapy_plan.py
+++ b/erpnext/healthcare/doctype/therapy_plan/test_therapy_plan.py
@@ -5,10 +5,10 @@
 
 import frappe
 import unittest
-from frappe.utils import getdate, flt
+from frappe.utils import getdate, flt, nowdate
 from erpnext.healthcare.doctype.therapy_type.test_therapy_type import create_therapy_type
 from erpnext.healthcare.doctype.therapy_plan.therapy_plan import make_therapy_session, make_sales_invoice
-from erpnext.healthcare.doctype.patient_appointment.test_patient_appointment import create_healthcare_docs, create_patient
+from erpnext.healthcare.doctype.patient_appointment.test_patient_appointment import create_healthcare_docs, create_patient, create_appointment
 
 class TestTherapyPlan(unittest.TestCase):
 	def test_creation_on_encounter_submission(self):
@@ -28,6 +28,15 @@
 		frappe.get_doc(session).submit()
 		self.assertEquals(frappe.db.get_value('Therapy Plan', plan.name, 'status'), 'Completed')
 
+		patient, medical_department, practitioner = create_healthcare_docs()
+		appointment = create_appointment(patient, practitioner, nowdate())		
+		session = make_therapy_session(plan.name, plan.patient, 'Basic Rehab', '_Test Company', appointment.name)
+		session = frappe.get_doc(session)
+		session.submit()
+		self.assertEquals(frappe.db.get_value('Patient Appointment', appointment.name, 'status'), 'Closed')
+		session.cancel()
+		self.assertEquals(frappe.db.get_value('Patient Appointment', appointment.name, 'status'), 'Open')
+
 	def test_therapy_plan_from_template(self):
 		patient = create_patient()
 		template = create_therapy_plan_template()
diff --git a/erpnext/healthcare/doctype/therapy_plan/therapy_plan.py b/erpnext/healthcare/doctype/therapy_plan/therapy_plan.py
index bc0ff1a..ac01c60 100644
--- a/erpnext/healthcare/doctype/therapy_plan/therapy_plan.py
+++ b/erpnext/healthcare/doctype/therapy_plan/therapy_plan.py
@@ -47,7 +47,7 @@
 
 
 @frappe.whitelist()
-def make_therapy_session(therapy_plan, patient, therapy_type, company):
+def make_therapy_session(therapy_plan, patient, therapy_type, company, appointment=None):
 	therapy_type = frappe.get_doc('Therapy Type', therapy_type)
 
 	therapy_session = frappe.new_doc('Therapy Session')
@@ -58,6 +58,7 @@
 	therapy_session.duration = therapy_type.default_duration
 	therapy_session.rate = therapy_type.rate
 	therapy_session.exercises = therapy_type.exercises
+	therapy_session.appointment = appointment
 
 	if frappe.flags.in_test:
 		therapy_session.start_date = today()
diff --git a/erpnext/healthcare/doctype/therapy_session/therapy_session.js b/erpnext/healthcare/doctype/therapy_session/therapy_session.js
index a2b01c9..fd20003 100644
--- a/erpnext/healthcare/doctype/therapy_session/therapy_session.js
+++ b/erpnext/healthcare/doctype/therapy_session/therapy_session.js
@@ -19,6 +19,15 @@
 				}
 			};
 		});
+
+		frm.set_query('appointment', function() {
+
+			return {
+				filters: {
+					'status': ['in', ['Open', 'Scheduled']]
+				}
+			};
+		});
 	},
 
 	refresh: function(frm) {
diff --git a/erpnext/healthcare/doctype/therapy_session/therapy_session.py b/erpnext/healthcare/doctype/therapy_session/therapy_session.py
index 85d0970..c000544 100644
--- a/erpnext/healthcare/doctype/therapy_session/therapy_session.py
+++ b/erpnext/healthcare/doctype/therapy_session/therapy_session.py
@@ -43,7 +43,14 @@
 		self.update_sessions_count_in_therapy_plan()
 		insert_session_medical_record(self)
 
+	def on_update(self):
+		if self.appointment:
+			frappe.db.set_value('Patient Appointment', self.appointment, 'status', 'Closed')
+
 	def on_cancel(self):
+		if self.appointment:
+			frappe.db.set_value('Patient Appointment', self.appointment, 'status', 'Open')
+
 		self.update_sessions_count_in_therapy_plan(on_cancel=True)
 
 	def update_sessions_count_in_therapy_plan(self, on_cancel=False):
diff --git a/erpnext/hr/doctype/employee/employee.json b/erpnext/hr/doctype/employee/employee.json
index 4f1c04f..dc2aaa4 100644
--- a/erpnext/hr/doctype/employee/employee.json
+++ b/erpnext/hr/doctype/employee/employee.json
@@ -813,7 +813,7 @@
  "idx": 24,
  "image_field": "image",
  "links": [],
- "modified": "2020-10-16 15:02:04.283657",
+ "modified": "2021-01-01 16:54:33.477439",
  "modified_by": "Administrator",
  "module": "HR",
  "name": "Employee",
@@ -855,7 +855,6 @@
    "write": 1
   }
  ],
- "quick_entry": 1,
  "search_fields": "employee_name",
  "show_name_in_global_search": 1,
  "sort_field": "modified",
diff --git a/erpnext/hr/doctype/leave_allocation/leave_allocation.json b/erpnext/hr/doctype/leave_allocation/leave_allocation.json
index 4b31501..3a300c0 100644
--- a/erpnext/hr/doctype/leave_allocation/leave_allocation.json
+++ b/erpnext/hr/doctype/leave_allocation/leave_allocation.json
@@ -11,6 +11,7 @@
   "employee",
   "employee_name",
   "department",
+  "company",
   "column_break1",
   "leave_type",
   "from_date",
@@ -219,6 +220,15 @@
    "label": "Leave Policy Assignment",
    "options": "Leave Policy Assignment",
    "read_only": 1
+  },
+  {
+   "fetch_from": "employee.company",
+   "fieldname": "company",
+   "fieldtype": "Link",
+   "label": "Company",
+   "options": "Company",
+   "read_only": 1,
+   "reqd": 1
   }
  ],
  "icon": "fa fa-ok",
@@ -226,7 +236,7 @@
  "index_web_pages_for_search": 1,
  "is_submittable": 1,
  "links": [],
- "modified": "2020-08-20 14:25:10.314323",
+ "modified": "2021-01-04 18:46:13.184104",
  "modified_by": "Administrator",
  "module": "HR",
  "name": "Leave Allocation",
diff --git a/erpnext/hr/doctype/leave_ledger_entry/leave_ledger_entry.json b/erpnext/hr/doctype/leave_ledger_entry/leave_ledger_entry.json
index 4abba5f..d74760a 100644
--- a/erpnext/hr/doctype/leave_ledger_entry/leave_ledger_entry.json
+++ b/erpnext/hr/doctype/leave_ledger_entry/leave_ledger_entry.json
@@ -1,4 +1,5 @@
 {
+ "actions": [],
  "creation": "2019-05-09 15:47:39.760406",
  "doctype": "DocType",
  "engine": "InnoDB",
@@ -8,6 +9,7 @@
   "leave_type",
   "transaction_type",
   "transaction_name",
+  "company",
   "leaves",
   "column_break_7",
   "from_date",
@@ -106,12 +108,22 @@
    "fieldtype": "Link",
    "label": "Holiday List",
    "options": "Holiday List"
+  },
+  {
+   "fetch_from": "employee.company",
+   "fieldname": "company",
+   "fieldtype": "Link",
+   "label": "Company",
+   "options": "Company",
+   "read_only": 1,
+   "reqd": 1
   }
  ],
  "in_create": 1,
  "index_web_pages_for_search": 1,
  "is_submittable": 1,
- "modified": "2020-09-04 12:16:36.569066",
+ "links": [],
+ "modified": "2021-01-04 18:47:45.146652",
  "modified_by": "Administrator",
  "module": "HR",
  "name": "Leave Ledger Entry",
diff --git a/erpnext/loan_management/doctype/loan/test_loan.py b/erpnext/loan_management/doctype/loan/test_loan.py
index 8b1f9a2..2abd7d8 100644
--- a/erpnext/loan_management/doctype/loan/test_loan.py
+++ b/erpnext/loan_management/doctype/loan/test_loan.py
@@ -362,6 +362,27 @@
 		unpledge_request.load_from_db()
 		self.assertEqual(unpledge_request.docstatus, 1)
 
+	def test_santined_loan_security_unpledge(self):
+		pledge = [{
+			"loan_security": "Test Security 1",
+			"qty": 4000.00
+		}]
+
+		loan_application = create_loan_application('_Test Company', self.applicant2, 'Demand Loan', pledge)
+		create_pledge(loan_application)
+
+		loan = create_demand_loan(self.applicant2, "Demand Loan", loan_application, posting_date='2019-10-01')
+		loan.submit()
+
+		self.assertEquals(loan.loan_amount, 1000000)
+
+		unpledge_map = {'Test Security 1': 4000}
+		unpledge_request = unpledge_security(loan=loan.name, security_map = unpledge_map, save=1)
+		unpledge_request.submit()
+		unpledge_request.status = 'Approved'
+		unpledge_request.save()
+		unpledge_request.submit()
+
 	def test_disbursal_check_with_shortfall(self):
 		pledges = [{
 			"loan_security": "Test Security 2",
diff --git a/erpnext/loan_management/doctype/loan_security_unpledge/loan_security_unpledge.py b/erpnext/loan_management/doctype/loan_security_unpledge/loan_security_unpledge.py
index 61c418d..c4c2d68 100644
--- a/erpnext/loan_management/doctype/loan_security_unpledge/loan_security_unpledge.py
+++ b/erpnext/loan_management/doctype/loan_security_unpledge/loan_security_unpledge.py
@@ -44,10 +44,16 @@
 				"valid_upto": (">=", get_datetime())
 			}, as_list=1))
 
-		total_payment, principal_paid, interest_payable, written_off_amount = frappe.get_value("Loan", self.loan, ['total_payment', 'total_principal_paid',
-			'total_interest_payable', 'written_off_amount'])
+		loan_details = frappe.get_value("Loan", self.loan, ['total_payment', 'total_principal_paid',
+			'total_interest_payable', 'written_off_amount', 'disbursed_amount', 'status'], as_dict=1)
 
-		pending_principal_amount = flt(total_payment) - flt(interest_payable) - flt(principal_paid) - flt(written_off_amount)
+		if loan_details.status == 'Disbursed':
+			pending_principal_amount = flt(loan_details.total_payment) - flt(loan_details.total_interest_payable) \
+				- flt(loan_details.total_principal_paid) - flt(loan_details.written_off_amount)
+		else:
+			pending_principal_amount = flt(loan_details.disbursed_amount) - flt(loan_details.total_interest_payable) \
+				- flt(loan_details.total_principal_paid) - flt(loan_details.written_off_amount)
+
 		security_value = 0
 		unpledge_qty_map = {}
 		ltv_ratio = 0
diff --git a/erpnext/patches.txt b/erpnext/patches.txt
index f2e4f72..043fafd 100644
--- a/erpnext/patches.txt
+++ b/erpnext/patches.txt
@@ -742,3 +742,5 @@
 erpnext.patches.v13_0.create_leave_policy_assignment_based_on_employee_current_leave_policy
 erpnext.patches.v13_0.add_po_to_global_search
 erpnext.patches.v13_0.update_returned_qty_in_pr_dn
+erpnext.patches.v13_0.set_company_in_leave_ledger_entry
+erpnext.patches.v13_0.convert_qi_parameter_to_link_field
diff --git a/erpnext/patches/v13_0/convert_qi_parameter_to_link_field.py b/erpnext/patches/v13_0/convert_qi_parameter_to_link_field.py
new file mode 100644
index 0000000..289b6a7
--- /dev/null
+++ b/erpnext/patches/v13_0/convert_qi_parameter_to_link_field.py
@@ -0,0 +1,23 @@
+from __future__ import unicode_literals
+import frappe
+
+def execute():
+	frappe.reload_doc('stock', 'doctype', 'quality_inspection_parameter')
+
+	# get all distinct parameters from QI readigs table
+	reading_params = frappe.db.get_all("Quality Inspection Reading", fields=["distinct specification"])
+	reading_params = [d.specification for d in reading_params]
+
+	# get all distinct parameters from QI Template as some may be unused in QI
+	template_params = frappe.db.get_all("Item Quality Inspection Parameter", fields=["distinct specification"])
+	template_params = [d.specification for d in template_params]
+
+	params = list(set(reading_params + template_params))
+
+	for parameter in params:
+		if not frappe.db.exists("Quality Inspection Parameter", parameter):
+			frappe.get_doc({
+				"doctype": "Quality Inspection Parameter",
+				"parameter": parameter,
+				"description": parameter
+			}).insert(ignore_permissions=True)
\ No newline at end of file
diff --git a/erpnext/patches/v13_0/set_company_in_leave_ledger_entry.py b/erpnext/patches/v13_0/set_company_in_leave_ledger_entry.py
new file mode 100644
index 0000000..66857c4
--- /dev/null
+++ b/erpnext/patches/v13_0/set_company_in_leave_ledger_entry.py
@@ -0,0 +1,7 @@
+import frappe
+
+def execute():
+	frappe.reload_doc('HR', 'doctype', 'Leave Allocation')
+	frappe.reload_doc('HR', 'doctype', 'Leave Ledger Entry')
+	frappe.db.sql("""update `tabLeave Ledger Entry` as lle set company = (select company from `tabEmployee` where employee = lle.employee)""")
+	frappe.db.sql("""update `tabLeave Allocation` as la set company = (select company from `tabEmployee` where employee = la.employee)""")
\ No newline at end of file
diff --git a/erpnext/patches/v4_0/map_charge_to_taxes_and_charges.py b/erpnext/patches/v4_0/map_charge_to_taxes_and_charges.py
index ad043dd..97e217a 100644
--- a/erpnext/patches/v4_0/map_charge_to_taxes_and_charges.py
+++ b/erpnext/patches/v4_0/map_charge_to_taxes_and_charges.py
@@ -5,11 +5,11 @@
 import frappe
 
 def execute():
-	# udpate sales cycle
+	# update sales cycle
 	for d in ['Sales Invoice', 'Sales Order', 'Quotation', 'Delivery Note']:
 		frappe.db.sql("""update `tab%s` set taxes_and_charges=charge""" % d)
 
-	# udpate purchase cycle
+	# update purchase cycle
 	for d in ['Purchase Invoice', 'Purchase Order', 'Supplier Quotation', 'Purchase Receipt']:
 		frappe.db.sql("""update `tab%s` set taxes_and_charges=purchase_other_charges""" % d)
 	
diff --git a/erpnext/payroll/doctype/payroll_entry/payroll_entry.js b/erpnext/payroll/doctype/payroll_entry/payroll_entry.js
index 2288a27..61c593d 100644
--- a/erpnext/payroll/doctype/payroll_entry/payroll_entry.js
+++ b/erpnext/payroll/doctype/payroll_entry/payroll_entry.js
@@ -46,7 +46,7 @@
 					}
 				).toggleClass('btn-primary', !(frm.doc.employees || []).length);
 			}
-			if ((frm.doc.employees || []).length) {
+			if ((frm.doc.employees || []).length && !frappe.model.has_workflow(frm.doctype)) {
 				frm.page.clear_primary_action();
 				frm.page.set_primary_action(__('Create Salary Slips'), () => {
 					frm.save('Submit').then(() => {
diff --git a/erpnext/payroll/doctype/payroll_entry/payroll_entry.py b/erpnext/payroll/doctype/payroll_entry/payroll_entry.py
index a25a6e7..6bcd4e0 100644
--- a/erpnext/payroll/doctype/payroll_entry/payroll_entry.py
+++ b/erpnext/payroll/doctype/payroll_entry/payroll_entry.py
@@ -21,6 +21,9 @@
 		if cint(entries) == len(self.employees):
 				self.set_onload("submitted_ss", True)
 
+	def validate(self):
+		self.number_of_employees = len(self.employees)
+
 	def on_submit(self):
 		self.create_salary_slips()
 
@@ -113,7 +116,7 @@
 		for d in employees:
 			self.append('employees', d)
 
-		self.number_of_employees = len(employees)
+		self.number_of_employees = len(self.employees)
 		if self.validate_attendance:
 			return self.validate_employee_attendance()
 
@@ -145,8 +148,8 @@
 		"""
 		self.check_permission('write')
 		self.created = 1
-		emp_list = [d.employee for d in self.get_emp_list()]
-		if emp_list:
+		employees = [emp.employee for emp in self.employees]
+		if employees:
 			args = frappe._dict({
 				"salary_slip_based_on_timesheet": self.salary_slip_based_on_timesheet,
 				"payroll_frequency": self.payroll_frequency,
@@ -160,10 +163,10 @@
 				"exchange_rate": self.exchange_rate,
 				"currency": self.currency
 			})
-			if len(emp_list) > 30:
-				frappe.enqueue(create_salary_slips_for_employees, timeout=600, employees=emp_list, args=args)
+			if len(employees) > 30:
+				frappe.enqueue(create_salary_slips_for_employees, timeout=600, employees=employees, args=args)
 			else:
-				create_salary_slips_for_employees(emp_list, args, publish_progress=False)
+				create_salary_slips_for_employees(employees, args, publish_progress=False)
 				# since this method is called via frm.call this doc needs to be updated manually
 				self.reload()
 
diff --git a/erpnext/payroll/doctype/salary_slip/salary_slip.js b/erpnext/payroll/doctype/salary_slip/salary_slip.js
index 8e05bb2..51fb359 100644
--- a/erpnext/payroll/doctype/salary_slip/salary_slip.js
+++ b/erpnext/payroll/doctype/salary_slip/salary_slip.js
@@ -151,7 +151,6 @@
 		var salary_detail_fields = ["formula", "abbr", "statistical_component", "variable_based_on_taxable_salary"];
 		frm.fields_dict['earnings'].grid.set_column_disp(salary_detail_fields, false);
 		frm.fields_dict['deductions'].grid.set_column_disp(salary_detail_fields, false);
-		calculate_totals(frm);
 		frm.trigger("set_dynamic_labels");
 	},
 
diff --git a/erpnext/payroll/doctype/salary_slip/salary_slip.py b/erpnext/payroll/doctype/salary_slip/salary_slip.py
index 99d8a83..183ad13 100644
--- a/erpnext/payroll/doctype/salary_slip/salary_slip.py
+++ b/erpnext/payroll/doctype/salary_slip/salary_slip.py
@@ -143,8 +143,8 @@
 				self.salary_slip_based_on_timesheet = self._salary_structure_doc.salary_slip_based_on_timesheet or 0
 				self.set_time_sheet()
 				self.pull_sal_struct()
-				payroll_based_on, consider_unmarked_attendance_as = frappe.db.get_value("Payroll Settings", None, ["payroll_based_on","consider_unmarked_attendance_as"])
-				return [payroll_based_on, consider_unmarked_attendance_as]
+				ps = frappe.db.get_value("Payroll Settings", None, ["payroll_based_on","consider_unmarked_attendance_as"], as_dict=1)
+				return [ps.payroll_based_on, ps.consider_unmarked_attendance_as]
 
 	def set_time_sheet(self):
 		if self.salary_slip_based_on_timesheet:
@@ -424,16 +424,19 @@
 	def calculate_net_pay(self):
 		if self.salary_structure:
 			self.calculate_component_amounts("earnings")
-		self.gross_pay = self.get_component_totals("earnings")
+		self.gross_pay = self.get_component_totals("earnings", depends_on_payment_days=1)
 		self.base_gross_pay = flt(flt(self.gross_pay) * flt(self.exchange_rate), self.precision('base_gross_pay'))
 
 		if self.salary_structure:
 			self.calculate_component_amounts("deductions")
-		self.total_deduction = self.get_component_totals("deductions")
-		self.base_total_deduction = flt(flt(self.total_deduction) * flt(self.exchange_rate), self.precision('base_total_deduction'))
 
 		self.set_loan_repayment()
+		self.set_component_amounts_based_on_payment_days()
+		self.set_net_pay()
 
+	def set_net_pay(self):
+		self.total_deduction = self.get_component_totals("deductions")
+		self.base_total_deduction = flt(flt(self.total_deduction) * flt(self.exchange_rate), self.precision('base_total_deduction'))
 		self.net_pay = flt(self.gross_pay) - (flt(self.total_deduction) + flt(self.total_loan_repayment))
 		self.rounded_total = rounded(self.net_pay)
 		self.base_net_pay = flt(flt(self.net_pay) * flt(self.exchange_rate), self.precision('base_net_pay'))
@@ -455,8 +458,6 @@
 		else:
 			self.add_tax_components(payroll_period)
 
-		self.set_component_amounts_based_on_payment_days(component_type)
-
 	def add_structure_components(self, component_type):
 		data = self.get_data_for_eval()
 		for struct_row in self._salary_structure_doc.get(component_type):
@@ -576,7 +577,7 @@
 					'default_amount': amount if not struct_row.get("is_additional_component") else 0,
 					'depends_on_payment_days' : struct_row.depends_on_payment_days,
 					'salary_component' : struct_row.salary_component,
-					'abbr' : struct_row.abbr,
+					'abbr' : struct_row.abbr or struct_row.get("salary_component_abbr"),
 					'additional_salary': additional_salary,
 					'do_not_include_in_total' : struct_row.do_not_include_in_total,
 					'is_tax_applicable': struct_row.is_tax_applicable,
@@ -813,7 +814,7 @@
 			cint(row.depends_on_payment_days) and cint(self.total_working_days) and
 			(not self.salary_slip_based_on_timesheet or
 				getdate(self.start_date) < joining_date or
-				getdate(self.end_date) > relieving_date
+				(relieving_date and getdate(self.end_date) > relieving_date)
 			)):
 			additional_amount = flt((flt(row.additional_amount) * flt(self.payment_days)
 				/ cint(self.total_working_days)), row.precision("additional_amount"))
@@ -946,15 +947,21 @@
 		struct_row['variable_based_on_taxable_salary'] = component.variable_based_on_taxable_salary
 		return struct_row
 
-	def get_component_totals(self, component_type):
+	def get_component_totals(self, component_type, depends_on_payment_days=0):
+		joining_date, relieving_date = frappe.get_cached_value("Employee", self.employee,
+			["date_of_joining", "relieving_date"])
+
 		total = 0.0
 		for d in self.get(component_type):
 			if not d.do_not_include_in_total:
-				d.amount = flt(d.amount, d.precision("amount"))
-				total += d.amount
+				if depends_on_payment_days:
+					amount = self.get_amount_based_on_payment_days(d, joining_date, relieving_date)[0]
+				else:
+					amount = flt(d.amount, d.precision("amount"))
+				total += amount
 		return total
 
-	def set_component_amounts_based_on_payment_days(self, component_type):
+	def set_component_amounts_based_on_payment_days(self):
 		joining_date, relieving_date = frappe.get_cached_value("Employee", self.employee,
 			["date_of_joining", "relieving_date"])
 
@@ -964,8 +971,9 @@
 		if not joining_date:
 			frappe.throw(_("Please set the Date Of Joining for employee {0}").format(frappe.bold(self.employee_name)))
 
-		for d in self.get(component_type):
-			d.amount = self.get_amount_based_on_payment_days(d, joining_date, relieving_date)[0]
+		for component_type in ("earnings", "deductions"):
+			for d in self.get(component_type):
+				d.amount = flt(self.get_amount_based_on_payment_days(d, joining_date, relieving_date)[0], d.precision("amount"))
 
 	def set_loan_repayment(self):
 		self.total_loan_repayment = 0
@@ -1089,17 +1097,17 @@
 		self.calculate_net_pay()
 
 	def set_totals(self):
-		self.gross_pay = 0
+		self.gross_pay = 0.0
 		if self.salary_slip_based_on_timesheet == 1:
 			self.calculate_total_for_salary_slip_based_on_timesheet()
 		else:
-			self.total_deduction = 0
+			self.total_deduction = 0.0
 			if self.earnings:
 				for earning in self.earnings:
-					self.gross_pay += flt(earning.amount)
+					self.gross_pay += flt(earning.amount, earning.precision("amount"))
 			if self.deductions:
 				for deduction in self.deductions:
-					self.total_deduction += flt(deduction.amount)
+					self.total_deduction += flt(deduction.amount, deduction.precision("amount"))
 			self.net_pay = flt(self.gross_pay) - flt(self.total_deduction) - flt(self.total_loan_repayment)
 		self.set_base_totals()
 
@@ -1145,8 +1153,10 @@
 			fields = ['sum(net_pay) as sum'],
 			filters = {'employee_name' : self.employee_name,
 				'start_date' : ['>=', period_start_date],
-				'end_date' : ['<', period_end_date]})
-
+				'end_date' : ['<', period_end_date],
+				'name': ['!=', self.name],
+				'docstatus': 1
+			})
 
 		year_to_date = flt(salary_slip_sum[0].sum) if salary_slip_sum else 0.0
 
@@ -1160,7 +1170,9 @@
 			fields = ['sum(net_pay) as sum'],
 			filters = {'employee_name' : self.employee_name,
 				'start_date' : ['>=', first_day_of_the_month],
-				'end_date' : ['<', self.start_date]
+				'end_date' : ['<', self.start_date],
+				'name': ['!=', self.name],
+				'docstatus': 1
 			})
 
 		month_to_date = flt(salary_slip_sum[0].sum) if salary_slip_sum else 0.0
diff --git a/erpnext/payroll/doctype/salary_slip/test_salary_slip.py b/erpnext/payroll/doctype/salary_slip/test_salary_slip.py
index d6fb419..4368c03 100644
--- a/erpnext/payroll/doctype/salary_slip/test_salary_slip.py
+++ b/erpnext/payroll/doctype/salary_slip/test_salary_slip.py
@@ -318,7 +318,7 @@
 
 		year_to_date = 0
 		for slip in salary_slips:
-			year_to_date += slip.net_pay
+			year_to_date += flt(slip.net_pay)
 			self.assertEqual(slip.year_to_date, year_to_date)
 
 	def test_tax_for_payroll_period(self):
diff --git a/erpnext/public/js/controllers/transaction.js b/erpnext/public/js/controllers/transaction.js
index 3bc20f8..bed9c14 100644
--- a/erpnext/public/js/controllers/transaction.js
+++ b/erpnext/public/js/controllers/transaction.js
@@ -543,6 +543,7 @@
 							company: me.frm.doc.company,
 							order_type: me.frm.doc.order_type,
 							is_pos: cint(me.frm.doc.is_pos),
+							is_return: cint(me.frm.doc.is_return),
 							is_subcontracted: me.frm.doc.is_subcontracted,
 							transaction_date: me.frm.doc.transaction_date || me.frm.doc.posting_date,
 							ignore_pricing_rule: me.frm.doc.ignore_pricing_rule,
diff --git a/erpnext/regional/germany/accounts_controller.py b/erpnext/regional/germany/accounts_controller.py
index 5b2b31f..7f76493 100644
--- a/erpnext/regional/germany/accounts_controller.py
+++ b/erpnext/regional/germany/accounts_controller.py
@@ -48,9 +48,6 @@
 
 def missing(field_label, regulation):
 	"""Notify the user that a required field is missing."""
-	context = 'Specific for Germany. Example: Remember to set Company Tax ID. It is required by § 14 Abs. 4 Nr. 2 UStG.'
-	msgprint(_('Remember to set {field_label}. It is required by {regulation}.', context=context).format(
-			field_label=frappe.bold(_(field_label)),
-			regulation=regulation
-		)
-	)
+	translated_msg = _('Remember to set {field_label}. It is required by {regulation}.', context='Specific for Germany. Example: Remember to set Company Tax ID. It is required by § 14 Abs. 4 Nr. 2 UStG.') # noqa: E501
+	formatted_msg = translated_msg.format(field_label=frappe.bold(_(field_label)), regulation=regulation)
+	msgprint(formatted_msg)
diff --git a/erpnext/regional/germany/test_accounts_controller.py b/erpnext/regional/germany/test_accounts_controller.py
new file mode 100644
index 0000000..8bd378c
--- /dev/null
+++ b/erpnext/regional/germany/test_accounts_controller.py
@@ -0,0 +1,12 @@
+import frappe
+import unittest
+from erpnext.regional.germany.accounts_controller import validate_regional
+
+
+class TestAccountsController(unittest.TestCase):
+
+	def setUp(self):
+		self.sales_invoice = frappe.get_last_doc('Sales Invoice')
+
+	def test_validate_regional(self):
+		validate_regional(self.sales_invoice)
diff --git a/erpnext/regional/india/e_invoice/utils.py b/erpnext/regional/india/e_invoice/utils.py
index 02ce6c1..abe1504 100644
--- a/erpnext/regional/india/e_invoice/utils.py
+++ b/erpnext/regional/india/e_invoice/utils.py
@@ -15,7 +15,7 @@
 from pyqrcode import create as qrcreate
 from frappe.integrations.utils import make_post_request, make_get_request
 from erpnext.regional.india.utils import get_gst_accounts, get_place_of_supply
-from frappe.utils.data import cstr, cint, format_date, flt, time_diff_in_seconds, now_datetime, add_to_date
+from frappe.utils.data import cstr, cint, format_date, flt, time_diff_in_seconds, now_datetime, add_to_date, get_link_to_form
 
 def validate_einvoice_fields(doc):
 	einvoicing_enabled = cint(frappe.db.get_value('E Invoice Settings', 'E Invoice Settings', 'enable'))
@@ -84,29 +84,32 @@
 	))
 
 def get_party_details(address_name):
-	address = frappe.get_all('Address', filters={'name': address_name}, fields=['*'])[0]
-	gstin = address.get('gstin')
+	d = frappe.get_all('Address', filters={'name': address_name}, fields=['*'])[0]
 
-	gstin_details = get_gstin_details(gstin)
-	legal_name = gstin_details.get('LegalName') or gstin_details.get('TradeName')
-	location = gstin_details.get('AddrLoc') or address.get('city')
-	state_code = gstin_details.get('StateCode')
-	pincode = gstin_details.get('AddrPncd')
-	address_line1 = '{} {}'.format(gstin_details.get('AddrBno'), gstin_details.get('AddrFlno'))
-	address_line2 = '{} {}'.format(gstin_details.get('AddrBnm'), gstin_details.get('AddrSt'))
-	email_id = address.get('email_id')
-	phone = address.get('phone')
-	# get last 10 digit 
-	phone = phone.replace(" ", "")[-10:] if phone else ''
+	if (not d.gstin
+		or not d.city
+		or not d.pincode
+		or not d.address_title
+		or not d.address_line1
+		or not d.gst_state_number):
 
-	if state_code == 97:
+		frappe.throw(
+			msg=_('Address lines, city, pincode, gstin is mandatory for address {}. Please set them and try again.').format(
+				get_link_to_form('Address', address_name)
+			),
+			title=_('Missing Address Fields')
+		)
+
+	if d.gst_state_number == 97:
 		# according to einvoice standard
 		pincode = 999999
 
 	return frappe._dict(dict(
-		gstin=gstin, legal_name=legal_name, location=location,
-		pincode=pincode, state_code=state_code, address_line1=address_line1,
-		address_line2=address_line2, email=email_id, phone=phone
+		gstin=d.gstin, legal_name=d.address_title,
+		location=d.city, pincode=d.pincode,
+		state_code=d.gst_state_number,
+		address_line1=d.address_line1,
+		address_line2=d.address_line2
 	))
 
 def get_gstin_details(gstin):
@@ -127,14 +130,22 @@
 		return GSPConnector.get_gstin_details(gstin)
 
 def get_overseas_address_details(address_name):
-	address_title, address_line1, address_line2, city, phone, email_id = frappe.db.get_value(
-		'Address', address_name, ['address_title', 'address_line1', 'address_line2', 'city', 'phone', 'email_id']
+	address_title, address_line1, address_line2, city = frappe.db.get_value(
+		'Address', address_name, ['address_title', 'address_line1', 'address_line2', 'city']
 	)
 
+	if not address_title or not address_line1 or not city:
+		frappe.throw(
+			msg=_('Address lines and city is mandatory for address {}. Please set them and try again.').format(
+				get_link_to_form('Address', address_name)
+			),
+			title=_('Missing Address Fields')
+		)
+
 	return frappe._dict(dict(
-		gstin='URP', legal_name=address_title, address_line1=address_line1,
-		address_line2=address_line2, email=email_id, phone=phone,
-		pincode=999999, state_code=96, place_of_supply=96, location=city
+		gstin='URP', legal_name=address_title, location=city,
+		address_line1=address_line1, address_line2=address_line2,
+		pincode=999999, state_code=96, place_of_supply=96
 	))
 
 def get_item_list(invoice):
@@ -146,9 +157,10 @@
 		item.update(d.as_dict())
 
 		item.sr_no = d.idx
-		item.discount_amount = abs(item.discount_amount * item.qty)
-		item.description = d.item_name
+		item.description = d.item_name.replace('"', '\\"')
+
 		item.qty = abs(item.qty)
+		item.discount_amount = abs(item.discount_amount * item.qty)
 		item.unit_rate = abs(item.base_amount / item.qty)
 		item.gross_amount = abs(item.base_amount)
 		item.taxable_value = abs(item.base_amount)
@@ -156,6 +168,7 @@
 		item.batch_expiry_date = frappe.db.get_value('Batch', d.batch_no, 'expiry_date') if d.batch_no else None
 		item.batch_expiry_date = format_date(item.batch_expiry_date, 'dd/mm/yyyy') if item.batch_expiry_date else None
 		item.is_service_item = 'N' if frappe.db.get_value('Item', d.item_code, 'is_stock_item') else 'Y'
+		item.serial_no = ""
 
 		item = update_item_taxes(invoice, item)
 		
@@ -272,7 +285,25 @@
 		vehicle_type=vehicle_type[invoice.gst_vehicle_type]
 	))
 
+def validate_mandatory_fields(invoice):
+	if not invoice.company_address:
+		frappe.throw(_('Company Address is mandatory to fetch company GSTIN details.'), title=_('Missing Fields'))
+	if not invoice.customer_address:
+		frappe.throw(_('Customer Address is mandatory to fetch customer GSTIN details.'), title=_('Missing Fields'))
+	if not frappe.db.get_value('Address', invoice.company_address, 'gstin'):
+		frappe.throw(
+			_('GSTIN is mandatory to fetch company GSTIN details. Please enter GSTIN in selected company address.'),
+			title=_('Missing Fields')
+		)
+	if not frappe.db.get_value('Address', invoice.customer_address, 'gstin'):
+		frappe.throw(
+			_('GSTIN is mandatory to fetch customer GSTIN details. Please enter GSTIN in selected customer address.'),
+			title=_('Missing Fields')
+		)
+
 def make_einvoice(invoice):
+	validate_mandatory_fields(invoice)
+
 	schema = read_json('einv_template')
 
 	transaction_details = get_transaction_details(invoice)
@@ -351,7 +382,7 @@
 					# remove empty dicts
 					einvoice.pop(fieldname, None)
 			continue
-		
+
 		# convert to int or str
 		if value_type == 'string':
 			einvoice[fieldname] = str(value)
diff --git a/erpnext/selling/report/sales_analytics/sales_analytics.js b/erpnext/selling/report/sales_analytics/sales_analytics.js
index 0e565a3..9089b53 100644
--- a/erpnext/selling/report/sales_analytics/sales_analytics.js
+++ b/erpnext/selling/report/sales_analytics/sales_analytics.js
@@ -74,67 +74,71 @@
 		return Object.assign(options, {
 			checkboxColumn: true,
 			events: {
-				onCheckRow: function(data) {
+				onCheckRow: function (data) {
+					if (!data) return;
+					const data_doctype = $(
+						data[2].html
+					)[0].attributes.getNamedItem("data-doctype").value;
+					const tree_type = frappe.query_report.filters[0].value;
+					if (data_doctype != tree_type) return;
+
 					row_name = data[2].content;
 					length = data.length;
 
-					var tree_type = frappe.query_report.filters[0].value;
-
-					if(tree_type == "Customer") {
-						row_values = data.slice(4,length-1).map(function (column) {
-							return column.content;
-						})
+					if (tree_type == "Customer") {
+						row_values = data
+							.slice(4, length - 1)
+							.map(function (column) {
+								return column.content;
+							});
 					} else if (tree_type == "Item") {
-						row_values = data.slice(5,length-1).map(function (column) {
-							return column.content;
-						})
-					}
-					else {
-						row_values = data.slice(3,length-1).map(function (column) {
-							return column.content;
-						})
+						row_values = data
+							.slice(5, length - 1)
+							.map(function (column) {
+								return column.content;
+							});
+					} else {
+						row_values = data
+							.slice(3, length - 1)
+							.map(function (column) {
+								return column.content;
+							});
 					}
 
 					entry = {
-						'name':row_name,
-						'values':row_values
-					}
+						name: row_name,
+						values: row_values,
+					};
 
 					let raw_data = frappe.query_report.chart.data;
 					let new_datasets = raw_data.datasets;
 
-					var found = false;
-
-					for(var i=0; i < new_datasets.length;i++){
-						if(new_datasets[i].name == row_name){
-							found = true;
-							new_datasets.splice(i,1);
-							break;
+					let element_found = new_datasets.some((element, index, array)=>{
+						if(element.name == row_name){
+							array.splice(index, 1)
+							return true
 						}
-					}
+						return false
+					})
 
-					if(!found){
+					if (!element_found) {
 						new_datasets.push(entry);
 					}
 
 					let new_data = {
 						labels: raw_data.labels,
-						datasets: new_datasets
-					}
-
-					setTimeout(() => {
-						frappe.query_report.chart.update(new_data)
-					}, 500)
-
-
-					setTimeout(() => {
-						frappe.query_report.chart.draw(true);
-					}, 1000)
+						datasets: new_datasets,
+					};
+					chart_options = {
+						data: new_data,
+						type: "line",
+					};
+					frappe.query_report.render_chart(chart_options);
 
 					frappe.query_report.raw_chart_data = new_data;
 				},
-			}
-		})
+			},
+		});
 	},
 }
 
diff --git a/erpnext/stock/doctype/item_quality_inspection_parameter/item_quality_inspection_parameter.json b/erpnext/stock/doctype/item_quality_inspection_parameter/item_quality_inspection_parameter.json
index 888bc2d..3e81619 100644
--- a/erpnext/stock/doctype/item_quality_inspection_parameter/item_quality_inspection_parameter.json
+++ b/erpnext/stock/doctype/item_quality_inspection_parameter/item_quality_inspection_parameter.json
@@ -8,26 +8,32 @@
  "field_order": [
   "specification",
   "value",
+  "non_numeric",
   "column_break_3",
+  "min_value",
+  "max_value",
+  "formula_based_criteria",
   "acceptance_formula"
  ],
  "fields": [
   {
    "fieldname": "specification",
-   "fieldtype": "Data",
+   "fieldtype": "Link",
    "in_list_view": 1,
    "label": "Parameter",
    "oldfieldname": "specification",
    "oldfieldtype": "Data",
+   "options": "Quality Inspection Parameter",
    "print_width": "200px",
    "reqd": 1,
-   "width": "200px"
+   "width": "100px"
   },
   {
+   "depends_on": "eval:(!doc.formula_based_criteria && doc.non_numeric)",
    "fieldname": "value",
    "fieldtype": "Data",
    "in_list_view": 1,
-   "label": "Acceptance Criteria",
+   "label": "Acceptance Criteria Value",
    "oldfieldname": "value",
    "oldfieldtype": "Data"
   },
@@ -36,17 +42,45 @@
    "fieldtype": "Column Break"
   },
   {
-   "description": "Simple Python formula based on numeric Readings.<br> Example 1: <b>reading_1 &gt; 0.2 and reading_1 &lt; 0.5</b><br>\nExample 2: <b>(reading_1 + reading_2) / 2 &lt; 10</b>",
+   "depends_on": "formula_based_criteria",
+   "description": "Simple Python formula applied on Reading fields.<br> Numeric eg. 1: <b>reading_1 &gt; 0.2 and reading_1 &lt; 0.5</b><br>\nNumeric eg. 2: <b>mean &gt; 3.5</b> (mean of populated fields)<br>\nValue based eg.:  <b>reading_value in (\"A\", \"B\", \"C\")</b>",
    "fieldname": "acceptance_formula",
    "fieldtype": "Code",
-   "in_list_view": 1,
    "label": "Acceptance Criteria Formula"
+  },
+  {
+   "default": "0",
+   "fieldname": "formula_based_criteria",
+   "fieldtype": "Check",
+   "label": "Formula Based Criteria"
+  },
+  {
+   "depends_on": "eval:(!doc.formula_based_criteria && !doc.non_numeric)",
+   "fieldname": "min_value",
+   "fieldtype": "Float",
+   "in_list_view": 1,
+   "label": "Minimum Value"
+  },
+  {
+   "depends_on": "eval:(!doc.formula_based_criteria && !doc.non_numeric)",
+   "fieldname": "max_value",
+   "fieldtype": "Float",
+   "in_list_view": 1,
+   "label": "Maximum Value"
+  },
+  {
+   "default": "0",
+   "fieldname": "non_numeric",
+   "fieldtype": "Check",
+   "in_list_view": 1,
+   "label": "Non-Numeric",
+   "width": "80px"
   }
  ],
  "idx": 1,
  "istable": 1,
  "links": [],
- "modified": "2020-11-16 16:33:42.421842",
+ "modified": "2021-01-07 21:32:49.866439",
  "modified_by": "Administrator",
  "module": "Stock",
  "name": "Item Quality Inspection Parameter",
diff --git a/erpnext/stock/doctype/quality_inspection/quality_inspection.js b/erpnext/stock/doctype/quality_inspection/quality_inspection.js
index 03e3de1..f7565fd 100644
--- a/erpnext/stock/doctype/quality_inspection/quality_inspection.js
+++ b/erpnext/stock/doctype/quality_inspection/quality_inspection.js
@@ -4,6 +4,55 @@
 cur_frm.cscript.refresh = cur_frm.cscript.inspection_type;
 
 frappe.ui.form.on("Quality Inspection", {
+
+	setup: function(frm) {
+		frm.set_query("batch_no", function() {
+			return {
+				filters: {
+					"item": frm.doc.item_code
+				}
+			};
+		});
+
+		// Serial No based on item_code
+		frm.set_query("item_serial_no", function() {
+			let filters = {};
+			if (frm.doc.item_code) {
+				filters = {
+					'item_code': frm.doc.item_code
+				};
+			}
+			return { filters: filters };
+		});
+
+		// item code based on GRN/DN
+		frm.set_query("item_code", function(doc) {
+			let doctype = doc.reference_type;
+
+			if (doc.reference_type !== "Job Card") {
+				doctype = (doc.reference_type == "Stock Entry") ?
+					"Stock Entry Detail" : doc.reference_type + " Item";
+			}
+
+			if (doc.reference_type && doc.reference_name) {
+				let filters = {
+					"from": doctype,
+					"inspection_type": doc.inspection_type
+				};
+
+				if (doc.reference_type == doctype)
+					filters["reference_name"] = doc.reference_name;
+				else
+					filters["parent"] = doc.reference_name;
+
+				return {
+					query: "erpnext.stock.doctype.quality_inspection.quality_inspection.item_query",
+					filters: filters
+				};
+			}
+		});
+	},
+
 	refresh: function(frm) {
 		// Ignore cancellation of reference doctype on cancel all.
 		frm.ignore_doctypes_on_cancel_all = [frm.doc.reference_type];
@@ -31,55 +80,5 @@
 				}
 			});
 		}
-	}
-})
-
-// item code based on GRN/DN
-cur_frm.fields_dict['item_code'].get_query = function(doc, cdt, cdn) {
-	let doctype = doc.reference_type;
-
-	if (doc.reference_type !== "Job Card") {
-		doctype = (doc.reference_type == "Stock Entry") ?
-			"Stock Entry Detail" : doc.reference_type + " Item";
-	}
-
-	if (doc.reference_type && doc.reference_name) {
-		let filters = {
-			"from": doctype,
-			"inspection_type": doc.inspection_type
-		};
-
-		if (doc.reference_type == doctype)
-			filters["reference_name"] = doc.reference_name;
-		else
-			filters["parent"] = doc.reference_name;
-
-		return {
-			query: "erpnext.stock.doctype.quality_inspection.quality_inspection.item_query",
-			filters: filters
-		};
-	}
-},
-
-// Serial No based on item_code
-cur_frm.fields_dict['item_serial_no'].get_query = function(doc, cdt, cdn) {
-	var filters = {};
-	if (doc.item_code) {
-		filters = {
-			'item_code': doc.item_code
-		}
-	}
-	return { filters: filters }
-}
-
-cur_frm.set_query("batch_no", function(doc) {
-	return {
-		filters: {
-			"item": doc.item_code
-		}
-	}
-})
-
-cur_frm.add_fetch('item_code', 'item_name', 'item_name');
-cur_frm.add_fetch('item_code', 'description', 'description');
-
+	},
+});
\ No newline at end of file
diff --git a/erpnext/stock/doctype/quality_inspection/quality_inspection.json b/erpnext/stock/doctype/quality_inspection/quality_inspection.json
index f6d7619..edfe7e9 100644
--- a/erpnext/stock/doctype/quality_inspection/quality_inspection.json
+++ b/erpnext/stock/doctype/quality_inspection/quality_inspection.json
@@ -136,6 +136,7 @@
    "width": "50%"
   },
   {
+   "fetch_from": "item_code.item_name",
    "fieldname": "item_name",
    "fieldtype": "Data",
    "in_global_search": 1,
@@ -143,6 +144,7 @@
    "read_only": 1
   },
   {
+   "fetch_from": "item_code.description",
    "fieldname": "description",
    "fieldtype": "Small Text",
    "label": "Description",
@@ -236,7 +238,7 @@
  "index_web_pages_for_search": 1,
  "is_submittable": 1,
  "links": [],
- "modified": "2020-11-19 17:06:05.409963",
+ "modified": "2020-12-18 19:59:55.710300",
  "modified_by": "Administrator",
  "module": "Stock",
  "name": "Quality Inspection",
diff --git a/erpnext/stock/doctype/quality_inspection/quality_inspection.py b/erpnext/stock/doctype/quality_inspection/quality_inspection.py
index ae4eb9b..b3acbc5 100644
--- a/erpnext/stock/doctype/quality_inspection/quality_inspection.py
+++ b/erpnext/stock/doctype/quality_inspection/quality_inspection.py
@@ -6,7 +6,7 @@
 from frappe.model.document import Document
 from frappe.model.mapper import get_mapped_doc
 from frappe import _
-from frappe.utils import flt
+from frappe.utils import flt, cint
 from erpnext.stock.doctype.quality_inspection_template.quality_inspection_template \
 	import get_template_details
 
@@ -16,7 +16,7 @@
 			self.get_item_specification_details()
 
 		if self.readings:
-			self.set_status_based_on_acceptance_formula()
+			self.inspect_and_set_status()
 
 	def get_item_specification_details(self):
 		if not self.quality_inspection_template:
@@ -29,9 +29,7 @@
 		parameters = get_template_details(self.quality_inspection_template)
 		for d in parameters:
 			child = self.append('readings', {})
-			child.specification = d.specification
-			child.value = d.value
-			child.acceptance_formula = d.acceptance_formula
+			child.update(d)
 			child.status = "Accepted"
 
 	def get_quality_inspection_template(self):
@@ -69,35 +67,98 @@
 				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)
+
+				if self.docstatus == 2: # if cancel, then remove qi link wherever same name
+					conditions += " and t1.quality_inspection = '%s'"%(self.name)
+
 				frappe.db.sql("""
-					UPDATE `tab{child_doc}` t1, `tab{parent_doc}` t2
-					SET t1.quality_inspection = %s, t2.modified = %s
-					WHERE t1.parent = %s and t1.item_code = %s and t1.parent = t2.name
-				""".format(parent_doc=self.reference_type, child_doc=doctype),
+					UPDATE
+						`tab{child_doc}` t1, `tab{parent_doc}` t2
+					SET
+						t1.quality_inspection = %s, t2.modified = %s
+					WHERE
+						t1.parent = %s
+						and t1.item_code = %s
+						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))
 
-	def set_status_based_on_acceptance_formula(self):
+	def inspect_and_set_status(self):
 		for reading in self.readings:
-			if not reading.acceptance_formula: continue
+			if not reading.manual_inspection: # dont auto set status if manual
+				if reading.formula_based_criteria:
+					self.set_status_based_on_acceptance_formula(reading)
+				else:
+					# if not formula based check acceptance values set
+					self.set_status_based_on_acceptance_values(reading)
 
-			condition = reading.acceptance_formula
-			data = {}
+	def set_status_based_on_acceptance_values(self, reading):
+		if cint(reading.non_numeric):
+			result = reading.get("reading_value") == reading.get("value")
+		else:
+			# numeric readings
+			result = self.min_max_criteria_passed(reading)
+
+		reading.status = "Accepted" if result else "Rejected"
+
+	def min_max_criteria_passed(self, reading):
+		"""Determine whether all readings fall in the acceptable range."""
+		for i in range(1, 11):
+			reading_value = reading.get("reading_" + str(i))
+			if reading_value is not None and reading_value.strip():
+				result = flt(reading.get("min_value")) <= flt(reading_value) <= flt(reading.get("max_value"))
+				if not result: return False
+		return True
+
+	def set_status_based_on_acceptance_formula(self, reading):
+		if not reading.acceptance_formula:
+			frappe.throw(_("Row #{0}: Acceptance Criteria Formula is required.").format(reading.idx),
+				title=_("Missing Formula"))
+
+		condition = reading.acceptance_formula
+		data = self.get_formula_evaluation_data(reading)
+
+		try:
+			result = frappe.safe_eval(condition, None, data)
+			reading.status = "Accepted" if result else "Rejected"
+		except NameError as e:
+			field = frappe.bold(e.args[0].split()[1])
+			frappe.throw(_("Row #{0}: {1} is not a valid reading field. Please refer to the field description.")
+				.format(reading.idx, field),
+				title=_("Invalid Formula"))
+		except Exception:
+			frappe.throw(_("Row #{0}: Acceptance Criteria Formula is incorrect.").format(reading.idx),
+				title=_("Invalid Formula"))
+
+	def get_formula_evaluation_data(self, reading):
+		data = {}
+		if cint(reading.non_numeric):
+			data = {"reading_value": reading.get("reading_value")}
+		else:
+			# numeric readings
 			for i in range(1, 11):
 				field = "reading_" + str(i)
-				data[field] = flt(reading.get(field)) or 0
+				data[field] = flt(reading.get(field))
+			data["mean"] = self.calculate_mean(reading)
 
-			try:
-				result = frappe.safe_eval(condition, None, data)
-				reading.status = "Accepted" if result else "Rejected"
-			except SyntaxError:
-				frappe.throw(_("Row #{0}: Acceptance Criteria Formula is incorrect.").format(reading.idx),
-					title=_("Invalid Formula"))
-			except NameError as e:
-				field = frappe.bold(e.args[0].split()[1])
-				frappe.throw(_("Row #{0}: {1} is not a valid reading field. Please refer to the field description.")
-					.format(reading.idx, field),
-					title=_("Invalid Formula"))
+		return data
 
+	def calculate_mean(self, reading):
+		"""Calculate mean of all non-empty readings."""
+		from statistics import mean
+		readings_list = []
+
+		for i in range(1, 11):
+			reading_value = reading.get("reading_" + str(i))
+			if reading_value is not None and reading_value.strip():
+				readings_list.append(flt(reading_value))
+
+		actual_mean = mean(readings_list) if readings_list else 0
+		return actual_mean
 
 @frappe.whitelist()
 @frappe.validate_and_sanitize_search_inputs
diff --git a/erpnext/stock/doctype/quality_inspection/test_quality_inspection.py b/erpnext/stock/doctype/quality_inspection/test_quality_inspection.py
index 2c40009..8c5a04b 100644
--- a/erpnext/stock/doctype/quality_inspection/test_quality_inspection.py
+++ b/erpnext/stock/doctype/quality_inspection/test_quality_inspection.py
@@ -44,24 +44,61 @@
 		qa.delete()
 		dn.delete()
 
+	def test_value_based_qi_readings(self):
+		# Test QI based on acceptance values (Non formula)
+		dn = create_delivery_note(item_code="_Test Item with QA", do_not_submit=True)
+		readings = [{
+			"specification": "Iron Content", # numeric reading
+			"min_value": 0.1,
+			"max_value": 0.9,
+			"reading_1": "0.4"
+		},
+		{
+			"specification": "Particle Inspection Needed", # non-numeric reading
+			"non_numeric": 1,
+			"value": "Yes",
+			"reading_value": "Yes"
+		}]
+
+		qa = create_quality_inspection(reference_type="Delivery Note", reference_name=dn.name,
+			readings=readings, do_not_save=True)
+		qa.save()
+
+		# status must be auto set as per formula
+		self.assertEqual(qa.readings[0].status, "Accepted")
+		self.assertEqual(qa.readings[1].status, "Accepted")
+
+		qa.delete()
+		dn.delete()
+
 	def test_formula_based_qi_readings(self):
 		dn = create_delivery_note(item_code="_Test Item with QA", do_not_submit=True)
 		readings = [{
-			"specification": "Iron Content",
+			"specification": "Iron Content", # numeric reading
+			"formula_based_criteria": 1,
 			"acceptance_formula": "reading_1 > 0.35 and reading_1 < 0.50",
-			"reading_1": 0.4
+			"reading_1": "0.4"
 		},
 		{
-			"specification": "Calcium Content",
+			"specification": "Calcium Content", # numeric reading
+			"formula_based_criteria": 1,
 			"acceptance_formula": "reading_1 > 0.20 and reading_1 < 0.50",
-			"reading_1": 0.7
+			"reading_1": "0.7"
 		},
 		{
-			"specification": "Mg Content",
-			"acceptance_formula": "(reading_1 + reading_2 + reading_3) / 3 < 0.9",
-			"reading_1": 0.5,
-			"reading_2": 0.7,
+			"specification": "Mg Content", # numeric reading
+			"formula_based_criteria": 1,
+			"acceptance_formula": "mean < 0.9",
+			"reading_1": "0.5",
+			"reading_2": "0.7",
 			"reading_3": "random text" # check if random string input causes issues
+		},
+		{
+			"specification": "Calcium Content", # non-numeric reading
+			"formula_based_criteria": 1,
+			"non_numeric": 1,
+			"acceptance_formula": "reading_value in ('Grade A', 'Grade B', 'Grade C')",
+			"reading_value": "Grade B"
 		}]
 
 		qa = create_quality_inspection(reference_type="Delivery Note", reference_name=dn.name,
@@ -72,6 +109,7 @@
 		self.assertEqual(qa.readings[0].status, "Accepted")
 		self.assertEqual(qa.readings[1].status, "Rejected")
 		self.assertEqual(qa.readings[2].status, "Accepted")
+		self.assertEqual(qa.readings[3].status, "Accepted")
 
 		qa.delete()
 		dn.delete()
@@ -86,11 +124,20 @@
 	qa.item_code = args.item_code or "_Test Item with QA"
 	qa.sample_size = 1
 	qa.inspected_by = frappe.session.user
+	qa.status = args.status or "Accepted"
 
-	readings = args.readings or {"specification": "Size", "status": args.status}
+	if not args.readings:
+		create_quality_inspection_parameter("Size")
+		readings = {"specification": "Size", "min_value": 0, "max_value": 10}
+	else:
+		readings = args.readings
+
+	if args.status == "Rejected":
+		readings["reading_1"] = "12" # status is auto set in child on save
 
 	if isinstance(readings, list):
 		for entry in readings:
+			create_quality_inspection_parameter(entry["specification"])
 			qa.append("readings", entry)
 	else:
 		qa.append("readings", readings)
@@ -101,3 +148,11 @@
 			qa.submit()
 
 	return qa
+
+def create_quality_inspection_parameter(parameter):
+	if not frappe.db.exists("Quality Inspection Parameter", parameter):
+		frappe.get_doc({
+			"doctype": "Quality Inspection Parameter",
+			"parameter": parameter,
+			"description": parameter
+		}).insert()
\ No newline at end of file
diff --git a/erpnext/stock/doctype/quality_inspection_parameter/__init__.py b/erpnext/stock/doctype/quality_inspection_parameter/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/erpnext/stock/doctype/quality_inspection_parameter/__init__.py
diff --git a/erpnext/stock/doctype/quality_inspection_parameter/quality_inspection_parameter.js b/erpnext/stock/doctype/quality_inspection_parameter/quality_inspection_parameter.js
new file mode 100644
index 0000000..47c7e11
--- /dev/null
+++ b/erpnext/stock/doctype/quality_inspection_parameter/quality_inspection_parameter.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('Quality Inspection Parameter', {
+	// refresh: function(frm) {
+
+	// }
+});
diff --git a/erpnext/stock/doctype/quality_inspection_parameter/quality_inspection_parameter.json b/erpnext/stock/doctype/quality_inspection_parameter/quality_inspection_parameter.json
new file mode 100644
index 0000000..0b5a9b5
--- /dev/null
+++ b/erpnext/stock/doctype/quality_inspection_parameter/quality_inspection_parameter.json
@@ -0,0 +1,86 @@
+{
+ "actions": [],
+ "autoname": "field:parameter",
+ "creation": "2020-12-28 17:06:00.254129",
+ "doctype": "DocType",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+  "parameter",
+  "description"
+ ],
+ "fields": [
+  {
+   "fieldname": "parameter",
+   "fieldtype": "Data",
+   "label": "Parameter",
+   "unique": 1
+  },
+  {
+   "fieldname": "description",
+   "fieldtype": "Text Editor",
+   "label": "Description"
+  }
+ ],
+ "index_web_pages_for_search": 1,
+ "links": [],
+ "modified": "2020-12-28 18:06:54.897317",
+ "modified_by": "Administrator",
+ "module": "Stock",
+ "name": "Quality Inspection Parameter",
+ "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": "Stock User",
+   "share": 1,
+   "write": 1
+  },
+  {
+   "create": 1,
+   "delete": 1,
+   "email": 1,
+   "export": 1,
+   "print": 1,
+   "read": 1,
+   "report": 1,
+   "role": "Quality Manager",
+   "share": 1,
+   "write": 1
+  },
+  {
+   "create": 1,
+   "delete": 1,
+   "email": 1,
+   "export": 1,
+   "print": 1,
+   "read": 1,
+   "report": 1,
+   "role": "Manufacturing User",
+   "share": 1,
+   "write": 1
+  }
+ ],
+ "quick_entry": 1,
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "track_changes": 1
+}
\ No newline at end of file
diff --git a/erpnext/stock/doctype/quality_inspection_parameter/quality_inspection_parameter.py b/erpnext/stock/doctype/quality_inspection_parameter/quality_inspection_parameter.py
new file mode 100644
index 0000000..8678422
--- /dev/null
+++ b/erpnext/stock/doctype/quality_inspection_parameter/quality_inspection_parameter.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 QualityInspectionParameter(Document):
+	pass
diff --git a/erpnext/stock/doctype/quality_inspection_parameter/test_quality_inspection_parameter.py b/erpnext/stock/doctype/quality_inspection_parameter/test_quality_inspection_parameter.py
new file mode 100644
index 0000000..cefdc08
--- /dev/null
+++ b/erpnext/stock/doctype/quality_inspection_parameter/test_quality_inspection_parameter.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 TestQualityInspectionParameter(unittest.TestCase):
+	pass
diff --git a/erpnext/stock/doctype/quality_inspection_reading/quality_inspection_reading.json b/erpnext/stock/doctype/quality_inspection_reading/quality_inspection_reading.json
index c1976dd..dddb3d5 100644
--- a/erpnext/stock/doctype/quality_inspection_reading/quality_inspection_reading.json
+++ b/erpnext/stock/doctype/quality_inspection_reading/quality_inspection_reading.json
@@ -7,21 +7,28 @@
  "engine": "InnoDB",
  "field_order": [
   "specification",
-  "value",
   "status",
+  "value",
+  "non_numeric",
+  "manual_inspection",
   "column_break_4",
+  "min_value",
+  "max_value",
+  "formula_based_criteria",
   "acceptance_formula",
   "section_break_3",
+  "reading_value",
+  "section_break_14",
   "reading_1",
   "reading_2",
   "reading_3",
-  "column_break_10",
   "reading_4",
+  "column_break_10",
   "reading_5",
   "reading_6",
-  "column_break_14",
   "reading_7",
   "reading_8",
+  "column_break_14",
   "reading_9",
   "reading_10"
  ],
@@ -29,19 +36,20 @@
   {
    "columns": 3,
    "fieldname": "specification",
-   "fieldtype": "Data",
+   "fieldtype": "Link",
    "in_list_view": 1,
    "label": "Parameter",
    "oldfieldname": "specification",
    "oldfieldtype": "Data",
+   "options": "Quality Inspection Parameter",
    "reqd": 1
   },
   {
    "columns": 2,
+   "depends_on": "eval:(!doc.formula_based_criteria && doc.non_numeric)",
    "fieldname": "value",
    "fieldtype": "Data",
-   "in_list_view": 1,
-   "label": "Acceptance Criteria",
+   "label": "Acceptance Criteria Value",
    "oldfieldname": "value",
    "oldfieldtype": "Data"
   },
@@ -67,7 +75,6 @@
    "columns": 1,
    "fieldname": "reading_3",
    "fieldtype": "Data",
-   "in_list_view": 1,
    "label": "Reading 3",
    "oldfieldname": "reading_3",
    "oldfieldtype": "Data"
@@ -133,15 +140,18 @@
    "options": "Accepted\nRejected"
   },
   {
+   "depends_on": "non_numeric",
    "fieldname": "section_break_3",
-   "fieldtype": "Section Break"
+   "fieldtype": "Section Break",
+   "label": "Value Based Inspection"
   },
   {
    "fieldname": "column_break_4",
    "fieldtype": "Column Break"
   },
   {
-   "description": "Simple Python formula based on numeric Readings.<br> Example 1: <b>reading_1 &gt; 0.2 and reading_1 &lt; 0.5</b><br>\nExample 2: <b>(reading_1 + reading_2) / 2 &lt; 10</b>",
+   "depends_on": "formula_based_criteria",
+   "description": "Simple Python formula applied on Reading fields.<br> Numeric eg. 1: <b>reading_1 &gt; 0.2 and reading_1 &lt; 0.5</b><br>\nNumeric eg. 2: <b>mean &gt; 3.5</b> (mean of populated fields)<br>\nValue based eg.:  <b>reading_value in (\"A\", \"B\", \"C\")</b>",
    "fieldname": "acceptance_formula",
    "fieldtype": "Code",
    "label": "Acceptance Criteria Formula"
@@ -153,12 +163,59 @@
   {
    "fieldname": "column_break_14",
    "fieldtype": "Column Break"
+  },
+  {
+   "default": "0",
+   "fieldname": "formula_based_criteria",
+   "fieldtype": "Check",
+   "label": "Formula Based Criteria"
+  },
+  {
+   "depends_on": "eval:(!doc.formula_based_criteria && !doc.non_numeric)",
+   "description": "Applied on each reading.",
+   "fieldname": "min_value",
+   "fieldtype": "Float",
+   "label": "Minimum Value"
+  },
+  {
+   "depends_on": "eval:(!doc.formula_based_criteria && !doc.non_numeric)",
+   "description": "Applied on each reading.",
+   "fieldname": "max_value",
+   "fieldtype": "Float",
+   "label": "Maximum Value"
+  },
+  {
+   "depends_on": "non_numeric",
+   "fieldname": "reading_value",
+   "fieldtype": "Data",
+   "in_list_view": 1,
+   "label": "Reading Value"
+  },
+  {
+   "depends_on": "eval:!doc.non_numeric",
+   "fieldname": "section_break_14",
+   "fieldtype": "Section Break",
+   "label": "Numeric Inspection"
+  },
+  {
+   "default": "0",
+   "fieldname": "non_numeric",
+   "fieldtype": "Check",
+   "in_list_view": 1,
+   "label": "Non-Numeric"
+  },
+  {
+   "default": "0",
+   "description": "Set the status manually.",
+   "fieldname": "manual_inspection",
+   "fieldtype": "Check",
+   "label": "Manual Inspection"
   }
  ],
  "idx": 1,
  "istable": 1,
  "links": [],
- "modified": "2020-11-16 16:34:29.947856",
+ "modified": "2021-01-07 21:56:40.235579",
  "modified_by": "Administrator",
  "module": "Stock",
  "name": "Quality Inspection Reading",
diff --git a/erpnext/stock/doctype/quality_inspection_template/quality_inspection_template.py b/erpnext/stock/doctype/quality_inspection_template/quality_inspection_template.py
index e284846..c5a7974 100644
--- a/erpnext/stock/doctype/quality_inspection_template/quality_inspection_template.py
+++ b/erpnext/stock/doctype/quality_inspection_template/quality_inspection_template.py
@@ -13,6 +13,7 @@
 	if not template: return []
 
 	return frappe.get_all('Item Quality Inspection Parameter',
-		fields=["specification", "value", "acceptance_formula"],
+		fields=["specification", "value", "acceptance_formula",
+			"non_numeric", "formula_based_criteria", "min_value", "max_value"],
 		filters={'parenttype': 'Quality Inspection Template', 'parent': template},
 		order_by="idx")
\ No newline at end of file
diff --git a/erpnext/stock/get_item_details.py b/erpnext/stock/get_item_details.py
index 08f7a83..bf45251 100644
--- a/erpnext/stock/get_item_details.py
+++ b/erpnext/stock/get_item_details.py
@@ -74,7 +74,9 @@
 
 	update_party_blanket_order(args, out)
 
-	get_price_list_rate(args, item, out)
+	if not doc or cint(doc.get('is_return')) == 0:
+		# get price list rate only if the invoice is not a credit or debit note
+		get_price_list_rate(args, item, out)
 
 	if args.customer and cint(args.is_pos):
 		out.update(get_pos_profile_item_details(args.company, args))