Merge pull request #27039 from nextchamp-saqib/common-party-acc

feat: common party accounting
diff --git a/erpnext/accounts/custom/address.py b/erpnext/accounts/custom/address.py
index c417a49..834227b 100644
--- a/erpnext/accounts/custom/address.py
+++ b/erpnext/accounts/custom/address.py
@@ -1,7 +1,7 @@
 import frappe
 from frappe import _
 from frappe.contacts.doctype.address.address import Address
-from frappe.contacts.doctype.address.address import get_address_templates
+from frappe.contacts.doctype.address.address import get_address_templates, get_address_display
 
 class ERPNextAddress(Address):
 	def validate(self):
@@ -22,6 +22,16 @@
 			frappe.throw(_("Address needs to be linked to a Company. Please add a row for Company in the Links table."),
 				title=_("Company Not Linked"))
 
+	def on_update(self):
+		"""
+		After Address is updated, update the related 'Primary Address' on Customer.
+		"""
+		address_display = get_address_display(self.as_dict())
+		filters = { "customer_primary_address": self.name }
+		customers = frappe.db.get_all("Customer", filters=filters, as_list=True)
+		for customer_name in customers:
+			frappe.db.set_value("Customer", customer_name[0], "primary_address", address_display)
+			
 @frappe.whitelist()
 def get_shipping_address(company, address = None):
 	filters = [
diff --git a/erpnext/accounts/doctype/bank_transaction/bank_transaction.py b/erpnext/accounts/doctype/bank_transaction/bank_transaction.py
index 0544a46..7ea71fc 100644
--- a/erpnext/accounts/doctype/bank_transaction/bank_transaction.py
+++ b/erpnext/accounts/doctype/bank_transaction/bank_transaction.py
@@ -55,6 +55,11 @@
 				self.clear_sales_invoice(payment_entry, for_cancel=for_cancel)
 
 	def clear_simple_entry(self, payment_entry, for_cancel=False):
+		if payment_entry.payment_document == "Payment Entry":
+			if frappe.db.get_value("Payment Entry", payment_entry.payment_entry, "payment_type") == "Internal Transfer":
+				if len(get_reconciled_bank_transactions(payment_entry)) < 2:
+					return
+
 		clearance_date = self.date if not for_cancel else None
 		frappe.db.set_value(
 			payment_entry.payment_document, payment_entry.payment_entry,
@@ -70,6 +75,17 @@
 			),
 			"clearance_date", clearance_date)
 
+def get_reconciled_bank_transactions(payment_entry):
+	reconciled_bank_transactions = frappe.get_all(
+		'Bank Transaction Payments', 
+		filters = {
+			'payment_entry': payment_entry.payment_entry
+		},
+		fields = ['parent']
+	)
+
+	return reconciled_bank_transactions
+
 def get_total_allocated_amount(payment_entry):
 	return frappe.db.sql("""
 		SELECT
diff --git a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py
index 83f0222..5a19426 100644
--- a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py
+++ b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py
@@ -1140,6 +1140,18 @@
 		self.assertEqual(loss_for_si['credit'], loss_for_return_si['debit'])
 		self.assertEqual(loss_for_si['debit'], loss_for_return_si['credit'])
 
+	def test_incoming_rate_for_stand_alone_credit_note(self):
+		return_si = create_sales_invoice(is_return=1, update_stock=1, qty=-1, rate=90000, incoming_rate=10,
+			company='_Test Company with perpetual inventory', warehouse='Stores - TCP1', debit_to='Debtors - TCP1',
+			income_account='Sales - TCP1', expense_account='Cost of Goods Sold - TCP1', cost_center='Main - TCP1')
+
+		incoming_rate = frappe.db.get_value('Stock Ledger Entry', {'voucher_no': return_si.name}, 'incoming_rate')
+		debit_amount = frappe.db.get_value('GL Entry',
+			{'voucher_no': return_si.name, 'account': 'Stock In Hand - TCP1'}, 'debit')
+
+		self.assertEqual(debit_amount, 10.0)
+		self.assertEqual(incoming_rate, 10.0)
+
 	def test_discount_on_net_total(self):
 		si = frappe.copy_doc(test_records[2])
 		si.apply_discount_on = "Net Total"
@@ -2419,7 +2431,8 @@
 		"asset": args.asset or None,
 		"cost_center": args.cost_center or "_Test Cost Center - _TC",
 		"serial_no": args.serial_no,
-		"conversion_factor": 1
+		"conversion_factor": 1,
+		"incoming_rate": args.incoming_rate or 0
 	})
 
 	if not args.do_not_save:
diff --git a/erpnext/accounts/doctype/sales_invoice_item/sales_invoice_item.json b/erpnext/accounts/doctype/sales_invoice_item/sales_invoice_item.json
index c77076c..b90f3f0 100644
--- a/erpnext/accounts/doctype/sales_invoice_item/sales_invoice_item.json
+++ b/erpnext/accounts/doctype/sales_invoice_item/sales_invoice_item.json
@@ -53,7 +53,6 @@
   "column_break_24",
   "base_net_rate",
   "base_net_amount",
-  "incoming_rate",
   "drop_ship",
   "delivered_by_supplier",
   "accounting",
@@ -81,6 +80,7 @@
   "target_warehouse",
   "quality_inspection",
   "batch_no",
+  "incoming_rate",
   "col_break5",
   "allow_zero_valuation_rate",
   "serial_no",
@@ -807,12 +807,12 @@
    "read_only": 1
   },
   {
+   "depends_on": "eval:parent.is_return && parent.update_stock && !parent.return_against",
    "fieldname": "incoming_rate",
    "fieldtype": "Currency",
-   "label": "Incoming Rate",
+   "label": "Incoming Rate (Costing)",
    "no_copy": 1,
-   "print_hide": 1,
-   "read_only": 1
+   "print_hide": 1
   },
   {
    "depends_on": "eval: doc.uom != doc.stock_uom",
@@ -833,7 +833,7 @@
  "idx": 1,
  "istable": 1,
  "links": [],
- "modified": "2021-08-12 20:15:47.668399",
+ "modified": "2021-08-19 13:41:53.435827",
  "modified_by": "Administrator",
  "module": "Accounts",
  "name": "Sales Invoice Item",
diff --git a/erpnext/accounts/doctype/subscription/subscription.py b/erpnext/accounts/doctype/subscription/subscription.py
index 7c4ff73..8bf7b78f 100644
--- a/erpnext/accounts/doctype/subscription/subscription.py
+++ b/erpnext/accounts/doctype/subscription/subscription.py
@@ -367,21 +367,25 @@
 			)
 
 		# Discounts
-		if self.additional_discount_percentage:
-			invoice.additional_discount_percentage = self.additional_discount_percentage
+		if self.is_trialling():
+			invoice.additional_discount_percentage = 100
+		else:
+			if self.additional_discount_percentage:
+				invoice.additional_discount_percentage = self.additional_discount_percentage
 
-		if self.additional_discount_amount:
-			invoice.discount_amount = self.additional_discount_amount
+			if self.additional_discount_amount:
+				invoice.discount_amount = self.additional_discount_amount
 
-		if self.additional_discount_percentage or self.additional_discount_amount:
-			discount_on = self.apply_additional_discount
-			invoice.apply_discount_on = discount_on if discount_on else 'Grand Total'
+			if self.additional_discount_percentage or self.additional_discount_amount:
+				discount_on = self.apply_additional_discount
+				invoice.apply_discount_on = discount_on if discount_on else 'Grand Total'
 
 		# Subscription period
 		invoice.from_date = self.current_invoice_start
 		invoice.to_date = self.current_invoice_end
 
 		invoice.flags.ignore_mandatory = True
+
 		invoice.save()
 
 		if self.submit_invoice:
diff --git a/erpnext/assets/doctype/asset/depreciation.py b/erpnext/assets/doctype/asset/depreciation.py
index 8fdbbf9..9a61b79 100644
--- a/erpnext/assets/doctype/asset/depreciation.py
+++ b/erpnext/assets/doctype/asset/depreciation.py
@@ -59,7 +59,7 @@
 				"credit_in_account_currency": d.depreciation_amount,
 				"reference_type": "Asset",
 				"reference_name": asset.name,
-				"cost_center": ""
+				"cost_center": depreciation_cost_center
 			}
 
 			debit_entry = {
diff --git a/erpnext/controllers/sales_and_purchase_return.py b/erpnext/controllers/sales_and_purchase_return.py
index 5ee1f2f..01486fc 100644
--- a/erpnext/controllers/sales_and_purchase_return.py
+++ b/erpnext/controllers/sales_and_purchase_return.py
@@ -394,19 +394,6 @@
 	if not return_against:
 		return_against = frappe.get_cached_value(voucher_type, voucher_no, "return_against")
 
-	if not return_against and voucher_type == 'Sales Invoice' and sle:
-		return get_incoming_rate({
-			"item_code": sle.item_code,
-			"warehouse": sle.warehouse,
-			"posting_date": sle.get('posting_date'),
-			"posting_time": sle.get('posting_time'),
-			"qty": sle.actual_qty,
-			"serial_no": sle.get('serial_no'),
-			"company": sle.company,
-			"voucher_type": sle.voucher_type,
-			"voucher_no": sle.voucher_no
-		}, raise_error_if_no_rate=False)
-
 	return_against_item_field = get_return_against_item_fields(voucher_type)
 
 	filters = get_filters(voucher_type, voucher_no, voucher_detail_no,
@@ -417,7 +404,24 @@
 	else:
 		select_field = "abs(stock_value_difference / actual_qty)"
 
-	return flt(frappe.db.get_value("Stock Ledger Entry", filters, select_field))
+	rate = flt(frappe.db.get_value("Stock Ledger Entry", filters, select_field))
+	if not (rate and return_against) and voucher_type in ['Sales Invoice', 'Delivery Note']:
+		rate = frappe.db.get_value(f'{voucher_type} Item', voucher_detail_no, 'incoming_rate')
+
+		if not rate and sle:
+			rate = get_incoming_rate({
+				"item_code": sle.item_code,
+				"warehouse": sle.warehouse,
+				"posting_date": sle.get('posting_date'),
+				"posting_time": sle.get('posting_time'),
+				"qty": sle.actual_qty,
+				"serial_no": sle.get('serial_no'),
+				"company": sle.company,
+				"voucher_type": sle.voucher_type,
+				"voucher_no": sle.voucher_no
+			}, raise_error_if_no_rate=False)
+
+	return rate
 
 def get_return_against_item_fields(voucher_type):
 	return_against_item_fields = {
diff --git a/erpnext/controllers/selling_controller.py b/erpnext/controllers/selling_controller.py
index fc2cc97..4ea0e11 100644
--- a/erpnext/controllers/selling_controller.py
+++ b/erpnext/controllers/selling_controller.py
@@ -362,7 +362,7 @@
 				sales_order.update_reserved_qty(so_item_rows)
 
 	def set_incoming_rate(self):
-		if self.doctype not in ("Delivery Note", "Sales Invoice", "Sales Order"):
+		if self.doctype not in ("Delivery Note", "Sales Invoice"):
 			return
 
 		items = self.get("items") + (self.get("packed_items") or [])
@@ -371,18 +371,19 @@
 				# Get incoming rate based on original item cost based on valuation method
 				qty = flt(d.get('stock_qty') or d.get('actual_qty'))
 
-				d.incoming_rate = get_incoming_rate({
-					"item_code": d.item_code,
-					"warehouse": d.warehouse,
-					"posting_date": self.get('posting_date') or self.get('transaction_date'),
-					"posting_time": self.get('posting_time') or nowtime(),
-					"qty": qty if cint(self.get("is_return")) else (-1 * qty),
-					"serial_no": d.get('serial_no'),
-					"company": self.company,
-					"voucher_type": self.doctype,
-					"voucher_no": self.name,
-					"allow_zero_valuation": d.get("allow_zero_valuation")
-				}, raise_error_if_no_rate=False)
+				if not d.incoming_rate:
+					d.incoming_rate = get_incoming_rate({
+						"item_code": d.item_code,
+						"warehouse": d.warehouse,
+						"posting_date": self.get('posting_date') or self.get('transaction_date'),
+						"posting_time": self.get('posting_time') or nowtime(),
+						"qty": qty if cint(self.get("is_return")) else (-1 * qty),
+						"serial_no": d.get('serial_no'),
+						"company": self.company,
+						"voucher_type": self.doctype,
+						"voucher_no": self.name,
+						"allow_zero_valuation": d.get("allow_zero_valuation")
+					}, raise_error_if_no_rate=False)
 
 				# For internal transfers use incoming rate as the valuation rate
 				if self.is_internal_transfer():
diff --git a/erpnext/healthcare/doctype/fee_validity/fee_validity.json b/erpnext/healthcare/doctype/fee_validity/fee_validity.json
index b001bf0..d76b42e 100644
--- a/erpnext/healthcare/doctype/fee_validity/fee_validity.json
+++ b/erpnext/healthcare/doctype/fee_validity/fee_validity.json
@@ -46,13 +46,13 @@
   {
    "fieldname": "visited",
    "fieldtype": "Int",
-   "label": "Visited yet",
+   "label": "Visits Completed",
    "read_only": 1
   },
   {
    "fieldname": "valid_till",
    "fieldtype": "Date",
-   "label": "Valid till",
+   "label": "Valid Till",
    "read_only": 1
   },
   {
@@ -106,7 +106,7 @@
  ],
  "in_create": 1,
  "links": [],
- "modified": "2020-03-17 20:25:06.487418",
+ "modified": "2021-08-26 10:51:05.609349",
  "modified_by": "Administrator",
  "module": "Healthcare",
  "name": "Fee Validity",
diff --git a/erpnext/healthcare/doctype/fee_validity/fee_validity.py b/erpnext/healthcare/doctype/fee_validity/fee_validity.py
index 5b9c179..59586e0 100644
--- a/erpnext/healthcare/doctype/fee_validity/fee_validity.py
+++ b/erpnext/healthcare/doctype/fee_validity/fee_validity.py
@@ -11,7 +11,6 @@
 class FeeValidity(Document):
 	def validate(self):
 		self.update_status()
-		self.set_start_date()
 
 	def update_status(self):
 		if self.visited >= self.max_visits:
@@ -19,13 +18,6 @@
 		else:
 			self.status = 'Pending'
 
-	def set_start_date(self):
-		self.start_date = getdate()
-		for appointment in self.ref_appointments:
-			appointment_date = frappe.db.get_value('Patient Appointment', appointment.appointment, 'appointment_date')
-			if getdate(appointment_date) < self.start_date:
-				self.start_date = getdate(appointment_date)
-
 
 def create_fee_validity(appointment):
 	if not check_is_new_patient(appointment):
@@ -36,11 +28,9 @@
 	fee_validity.patient = appointment.patient
 	fee_validity.max_visits = frappe.db.get_single_value('Healthcare Settings', 'max_visits') or 1
 	valid_days = frappe.db.get_single_value('Healthcare Settings', 'valid_days') or 1
-	fee_validity.visited = 1
+	fee_validity.visited = 0
+	fee_validity.start_date = getdate(appointment.appointment_date)
 	fee_validity.valid_till = getdate(appointment.appointment_date) + datetime.timedelta(days=int(valid_days))
-	fee_validity.append('ref_appointments', {
-		'appointment': appointment.name
-	})
 	fee_validity.save(ignore_permissions=True)
 	return fee_validity
 
diff --git a/erpnext/healthcare/doctype/fee_validity/test_fee_validity.py b/erpnext/healthcare/doctype/fee_validity/test_fee_validity.py
index 82e7136..4a17872 100644
--- a/erpnext/healthcare/doctype/fee_validity/test_fee_validity.py
+++ b/erpnext/healthcare/doctype/fee_validity/test_fee_validity.py
@@ -22,14 +22,14 @@
 		item = create_healthcare_service_items()
 		healthcare_settings = frappe.get_single("Healthcare Settings")
 		healthcare_settings.enable_free_follow_ups = 1
-		healthcare_settings.max_visits = 2
+		healthcare_settings.max_visits = 1
 		healthcare_settings.valid_days = 7
 		healthcare_settings.automate_appointment_invoicing = 1
 		healthcare_settings.op_consulting_charge_item = item
 		healthcare_settings.save(ignore_permissions=True)
 		patient, medical_department, practitioner = create_healthcare_docs()
 
-		# For first appointment, invoice is generated
+		# For first appointment, invoice is generated. First appointment not considered in fee validity
 		appointment = create_appointment(patient, practitioner, nowdate())
 		invoiced = frappe.db.get_value("Patient Appointment", appointment.name, "invoiced")
 		self.assertEqual(invoiced, 1)
diff --git a/erpnext/healthcare/doctype/healthcare_practitioner/healthcare_practitioner.json b/erpnext/healthcare/doctype/healthcare_practitioner/healthcare_practitioner.json
index 8162f03..cb455eb 100644
--- a/erpnext/healthcare/doctype/healthcare_practitioner/healthcare_practitioner.json
+++ b/erpnext/healthcare/doctype/healthcare_practitioner/healthcare_practitioner.json
@@ -282,7 +282,7 @@
  ],
  "image_field": "image",
  "links": [],
- "modified": "2021-01-22 10:14:43.187675",
+ "modified": "2021-08-24 10:42:08.513054",
  "modified_by": "Administrator",
  "module": "Healthcare",
  "name": "Healthcare Practitioner",
@@ -295,6 +295,7 @@
    "read": 1,
    "report": 1,
    "role": "Laboratory User",
+   "select": 1,
    "share": 1,
    "write": 1
   },
@@ -307,6 +308,7 @@
    "read": 1,
    "report": 1,
    "role": "Physician",
+   "select": 1,
    "share": 1,
    "write": 1
   },
@@ -319,6 +321,7 @@
    "read": 1,
    "report": 1,
    "role": "Nursing User",
+   "select": 1,
    "share": 1,
    "write": 1
   }
diff --git a/erpnext/healthcare/doctype/patient_appointment/patient_appointment.json b/erpnext/healthcare/doctype/patient_appointment/patient_appointment.json
index 6e996bd..73ec3bc 100644
--- a/erpnext/healthcare/doctype/patient_appointment/patient_appointment.json
+++ b/erpnext/healthcare/doctype/patient_appointment/patient_appointment.json
@@ -142,7 +142,6 @@
   {
    "fieldname": "practitioner",
    "fieldtype": "Link",
-   "ignore_user_permissions": 1,
    "in_standard_filter": 1,
    "label": "Healthcare Practitioner",
    "options": "Healthcare Practitioner",
diff --git a/erpnext/healthcare/doctype/patient_appointment/patient_appointment.py b/erpnext/healthcare/doctype/patient_appointment/patient_appointment.py
index 05e2cd3..7db4fa6 100755
--- a/erpnext/healthcare/doctype/patient_appointment/patient_appointment.py
+++ b/erpnext/healthcare/doctype/patient_appointment/patient_appointment.py
@@ -109,9 +109,13 @@
 					frappe.db.set_value('Patient Appointment', self.name, 'notes', comments)
 
 	def update_fee_validity(self):
+		if not frappe.db.get_single_value('Healthcare Settings', 'enable_free_follow_ups'):
+			return
+
 		fee_validity = manage_fee_validity(self)
 		if fee_validity:
-			frappe.msgprint(_('{0} has fee validity till {1}').format(self.patient, fee_validity.valid_till))
+			frappe.msgprint(_('{0}: {1} has fee validity till {2}').format(self.patient,
+				frappe.bold(self.patient_name), fee_validity.valid_till))
 
 	@frappe.whitelist()
 	def get_therapy_types(self):
diff --git a/erpnext/healthcare/doctype/patient_appointment/test_patient_appointment.py b/erpnext/healthcare/doctype/patient_appointment/test_patient_appointment.py
index 2df6921..157b3e1 100644
--- a/erpnext/healthcare/doctype/patient_appointment/test_patient_appointment.py
+++ b/erpnext/healthcare/doctype/patient_appointment/test_patient_appointment.py
@@ -107,14 +107,17 @@
 		patient, medical_department, practitioner = create_healthcare_docs()
 		frappe.db.set_value('Healthcare Settings', None, 'enable_free_follow_ups', 1)
 		appointment = create_appointment(patient, practitioner, nowdate())
-		fee_validity = frappe.db.get_value('Fee Validity Reference', {'appointment': appointment.name}, 'parent')
+		fee_validity = frappe.db.get_value('Fee Validity', {'patient': patient, 'practitioner': practitioner})
 		# fee validity created
 		self.assertTrue(fee_validity)
 
-		visited = frappe.db.get_value('Fee Validity', fee_validity, 'visited')
+		# first follow up appointment
+		appointment = create_appointment(patient, practitioner, nowdate())
+		self.assertEqual(frappe.db.get_value('Fee Validity', fee_validity, 'visited'), 1)
+
 		update_status(appointment.name, 'Cancelled')
 		# check fee validity updated
-		self.assertEqual(frappe.db.get_value('Fee Validity', fee_validity, 'visited'), visited - 1)
+		self.assertEqual(frappe.db.get_value('Fee Validity', fee_validity, 'visited'), 0)
 
 		frappe.db.set_value('Healthcare Settings', None, 'enable_free_follow_ups', 0)
 		frappe.db.set_value('Healthcare Settings', None, 'automate_appointment_invoicing', 1)
@@ -199,10 +202,33 @@
 
 		assert new_invoice_count == invoice_count + 1
 
+	def test_patient_appointment_should_consider_permissions_while_fetching_appointments(self):
+		patient, medical_department, practitioner = create_healthcare_docs()
+		create_appointment(patient, practitioner, nowdate())
 
-def create_healthcare_docs():
+		patient, medical_department, new_practitioner = create_healthcare_docs(practitioner_name='Dr. John')
+		create_appointment(patient, new_practitioner, nowdate())
+
+		roles = [{"doctype": "Has Role", "role": "Physician"}]
+		user = create_user(roles=roles)
+		new_practitioner = frappe.get_doc('Healthcare Practitioner', new_practitioner)
+		new_practitioner.user_id = user.email
+		new_practitioner.save()
+
+		frappe.set_user(user.name)
+		appointments = frappe.get_list('Patient Appointment')
+		assert len(appointments) == 1
+
+		frappe.set_user("Administrator")
+		appointments = frappe.get_list('Patient Appointment')
+		assert len(appointments) == 2
+
+def create_healthcare_docs(practitioner_name=None):
+	if not practitioner_name:
+		practitioner_name = '_Test Healthcare Practitioner'
+
 	patient = create_patient()
-	practitioner = frappe.db.exists('Healthcare Practitioner', '_Test Healthcare Practitioner')
+	practitioner = frappe.db.exists('Healthcare Practitioner', practitioner_name)
 	medical_department = frappe.db.exists('Medical Department', '_Test Medical Department')
 
 	if not medical_department:
@@ -213,7 +239,7 @@
 
 	if not practitioner:
 		practitioner = frappe.new_doc('Healthcare Practitioner')
-		practitioner.first_name = '_Test Healthcare Practitioner'
+		practitioner.first_name = practitioner_name
 		practitioner.gender = 'Female'
 		practitioner.department = medical_department
 		practitioner.op_consulting_charge = 500
@@ -319,3 +345,17 @@
 			'price_list': args.get('price_list') or frappe.db.get_value("Price List", {"selling": 1}),
 			'items': args.get('items') or items
 		}).insert()
+
+def create_user(email=None, roles=None):
+	if not email:
+		email = '{}@frappe.com'.format(frappe.utils.random_string(10))
+	user = frappe.db.exists('User', email)
+	if not user:
+		user = frappe.get_doc({
+			"doctype": "User",
+			"email": email,
+			"first_name": "test_user",
+			"password": "password",
+			"roles": roles,
+		}).insert()
+	return user
diff --git a/erpnext/manufacturing/doctype/job_card/job_card.py b/erpnext/manufacturing/doctype/job_card/job_card.py
index 66e2394..3efbe88 100644
--- a/erpnext/manufacturing/doctype/job_card/job_card.py
+++ b/erpnext/manufacturing/doctype/job_card/job_card.py
@@ -26,17 +26,17 @@
 		self.set_status()
 		self.validate_operation_id()
 		self.validate_sequence_id()
-		self.get_sub_operations()
+		self.set_sub_operations()
 		self.update_sub_operation_status()
 
-	def get_sub_operations(self):
+	def set_sub_operations(self):
 		if self.operation:
 			self.sub_operations = []
-			for row in frappe.get_all("Sub Operation",
-				filters = {"parent": self.operation}, fields=["operation", "idx"]):
-				row.status = "Pending"
+			for row in frappe.get_all('Sub Operation',
+				filters = {'parent': self.operation}, fields=['operation', 'idx'], order_by='idx'):
+				row.status = 'Pending'
 				row.sub_operation = row.operation
-				self.append("sub_operations", row)
+				self.append('sub_operations', row)
 
 	def validate_time_logs(self):
 		self.total_time_in_mins = 0.0
@@ -690,7 +690,7 @@
 		target.set('time_logs', [])
 		target.set('employee', [])
 		target.set('items', [])
-		target.get_sub_operations()
+		target.set_sub_operations()
 		target.get_required_items()
 		target.validate_time_logs()
 
diff --git a/erpnext/patches.txt b/erpnext/patches.txt
index bf0446b..0a6a8bd 100644
--- a/erpnext/patches.txt
+++ b/erpnext/patches.txt
@@ -295,5 +295,6 @@
 erpnext.patches.v13_0.add_custom_field_for_south_africa #2
 erpnext.patches.v13_0.update_recipient_email_digest
 erpnext.patches.v13_0.shopify_deprecation_warning
+erpnext.patches.v13_0.reset_clearance_date_for_intracompany_payment_entries
 erpnext.patches.v13_0.einvoicing_deprecation_warning
 erpnext.patches.v14_0.delete_einvoicing_doctypes
diff --git a/erpnext/patches/v13_0/reset_clearance_date_for_intracompany_payment_entries.py b/erpnext/patches/v13_0/reset_clearance_date_for_intracompany_payment_entries.py
new file mode 100644
index 0000000..1da5275
--- /dev/null
+++ b/erpnext/patches/v13_0/reset_clearance_date_for_intracompany_payment_entries.py
@@ -0,0 +1,45 @@
+# Copyright (c) 2019, Frappe and Contributors
+# License: GNU General Public License v3. See license.txt
+
+from __future__ import unicode_literals
+
+import frappe
+
+def execute():
+    """
+        Reset Clearance Date for Payment Entries of type Internal Transfer that have only been reconciled with one Bank Transaction.
+        This will allow the Payment Entries to be reconciled with the second Bank Transaction using the Bank Reconciliation Tool.
+    """
+
+    intra_company_pe = get_intra_company_payment_entries_with_clearance_dates()
+    reconciled_bank_transactions = get_reconciled_bank_transactions(intra_company_pe)
+
+    for payment_entry in reconciled_bank_transactions:
+        if len(reconciled_bank_transactions[payment_entry]) == 1:
+            frappe.db.set_value('Payment Entry', payment_entry, 'clearance_date', None)
+
+def get_intra_company_payment_entries_with_clearance_dates():
+    return frappe.get_all(
+        'Payment Entry',
+        filters = {
+            'payment_type': 'Internal Transfer',
+            'clearance_date': ["not in", None]
+        },
+        pluck = 'name'
+    )
+
+def get_reconciled_bank_transactions(intra_company_pe):
+    """Returns dictionary where each key:value pair is Payment Entry : List of Bank Transactions reconciled with Payment Entry"""
+
+    reconciled_bank_transactions = {}
+
+    for payment_entry in intra_company_pe:
+        reconciled_bank_transactions[payment_entry] = frappe.get_all(
+            'Bank Transaction Payments', 
+            filters = {
+                'payment_entry': payment_entry
+            }, 
+            pluck='parent'
+        )
+
+    return reconciled_bank_transactions
\ No newline at end of file
diff --git a/erpnext/regional/report/vat_audit_report/test_vat_audit_report.py b/erpnext/regional/report/vat_audit_report/test_vat_audit_report.py
new file mode 100644
index 0000000..dea17a6
--- /dev/null
+++ b/erpnext/regional/report/vat_audit_report/test_vat_audit_report.py
@@ -0,0 +1,193 @@
+# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors
+# For license information, please see license.txt
+
+from __future__ import unicode_literals
+import frappe
+from unittest import TestCase
+from frappe.utils import today
+
+from erpnext.accounts.doctype.account.test_account import create_account
+from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice
+from erpnext.accounts.doctype.purchase_invoice.test_purchase_invoice import make_purchase_invoice
+
+from erpnext.regional.report.vat_audit_report.vat_audit_report import execute
+
+class TestVATAuditReport(TestCase):
+	def setUp(self):
+		frappe.set_user("Administrator")
+		make_company("_Test Company SA VAT", "_TCSV")
+
+		create_account(account_name="VAT - 0%", account_type="Tax",
+			parent_account="Duties and Taxes - _TCSV", company="_Test Company SA VAT")
+		create_account(account_name="VAT - 15%", account_type="Tax",
+			parent_account="Duties and Taxes - _TCSV", company="_Test Company SA VAT")
+		set_sa_vat_accounts()
+
+		make_item("_Test SA VAT Item")
+		make_item("_Test SA VAT Zero Rated Item", properties = {"is_zero_rated": 1})
+
+		make_customer()
+		make_supplier()
+
+		make_sales_invoices()
+		create_purchase_invoices()
+
+	def tearDown(self):
+		frappe.db.sql("delete from `tabSales Invoice` where company='_Test Company SA VAT'")
+		frappe.db.sql("delete from `tabPurchase Invoice` where company='_Test Company SA VAT'")
+
+	def test_vat_audit_report(self):
+		filters = {
+			"company": "_Test Company SA VAT",
+			"from_date": today(),
+			"to_date": today()
+		}
+		columns, data = execute(filters)
+		total_tax_amount = 0
+		total_row_tax = 0
+		for row in data:
+			keys = row.keys()
+			# skips total row tax_amount in if.. and skips section header in elif..
+			if 'voucher_no' in keys:
+				total_tax_amount = total_tax_amount + row['tax_amount']
+			elif 'tax_amount' in keys:
+				total_row_tax = total_row_tax + row['tax_amount']
+
+		self.assertEqual(total_tax_amount, total_row_tax)
+
+def make_company(company_name, abbr):
+	if not frappe.db.exists("Company", company_name):
+		company = frappe.get_doc({
+			"doctype": "Company",
+			"company_name": company_name,
+			"abbr": abbr,
+			"default_currency": "ZAR",
+			"country": "South Africa",
+			"create_chart_of_accounts_based_on": "Standard Template"
+		})
+		company.insert()
+	else:
+		company = frappe.get_doc("Company", company_name)
+
+	company.create_default_warehouses()
+
+	if not frappe.db.get_value("Cost Center", {"is_group": 0, "company": company.name}):
+		company.create_default_cost_center()
+
+	company.save()
+
+	return company
+
+def set_sa_vat_accounts():
+	if not frappe.db.exists("South Africa VAT Settings", "_Test Company SA VAT"):
+		vat_accounts = frappe.get_all(
+			"Account",
+			fields=["name"],
+			filters = {
+				"company": "_Test Company SA VAT",
+				"is_group": 0,
+				"account_type": "Tax"
+			}
+		)
+
+		sa_vat_accounts = []
+		for account in vat_accounts:
+			sa_vat_accounts.append({
+				"doctype": "South Africa VAT Account",
+				"account": account.name
+			})
+
+		frappe.get_doc({
+			"company": "_Test Company SA VAT",
+			"vat_accounts": sa_vat_accounts,
+			"doctype": "South Africa VAT Settings",
+		}).insert()
+
+def make_customer():
+	if not frappe.db.exists("Customer", "_Test SA Customer"):
+		frappe.get_doc({
+			"doctype": "Customer",
+			"customer_name": "_Test SA Customer",
+			"customer_type": "Company",
+		}).insert()
+
+def make_supplier():
+	if not frappe.db.exists("Supplier", "_Test SA Supplier"):
+		frappe.get_doc({
+			"doctype": "Supplier",
+			"supplier_name": "_Test SA Supplier",
+			"supplier_type": "Company",
+			"supplier_group":"All Supplier Groups"
+		}).insert()
+
+def make_item(item_code, properties=None):
+	if not frappe.db.exists("Item", item_code):
+		item = frappe.get_doc({
+			"doctype": "Item",
+			"item_code": item_code,
+			"item_name": item_code,
+			"description": item_code,
+			"item_group": "Products"
+		})
+
+		if properties:
+			item.update(properties)
+
+		item.insert()
+
+def make_sales_invoices():
+	def make_sales_invoices_wrapper(item, rate, tax_account, tax_rate, tax=True):
+		si = create_sales_invoice(
+			company="_Test Company SA VAT",
+			customer = "_Test SA Customer",
+			currency = "ZAR",
+			item=item,
+			rate=rate,
+			warehouse = "Finished Goods - _TCSV",
+			debit_to = "Debtors - _TCSV",
+			income_account = "Sales - _TCSV",
+			expense_account = "Cost of Goods Sold - _TCSV",
+			cost_center = "Main - _TCSV",
+			do_not_save=1
+		)
+		if tax:
+			si.append("taxes", {
+					"charge_type": "On Net Total",
+					"account_head": tax_account,
+					"cost_center": "Main - _TCSV",
+					"description": "VAT 15% @ 15.0",
+					"rate": tax_rate
+				})
+
+		si.submit()
+
+	test_item = "_Test SA VAT Item"
+	test_zero_rated_item = "_Test SA VAT Zero Rated Item"
+
+	make_sales_invoices_wrapper(test_item, 100.0, "VAT - 15% - _TCSV", 15.0)
+	make_sales_invoices_wrapper(test_zero_rated_item, 100.0, "VAT - 0% - _TCSV", 0.0)
+
+def create_purchase_invoices():
+	pi = make_purchase_invoice(
+		company = "_Test Company SA VAT",
+		supplier = "_Test SA Supplier",
+		supplier_warehouse = "Finished Goods - _TCSV",
+		warehouse = "Finished Goods - _TCSV",
+		currency = "ZAR",
+		cost_center = "Main - _TCSV",
+		expense_account = "Cost of Goods Sold - _TCSV",
+		item = "_Test SA VAT Item",
+		qty = 1,
+		rate = 100,
+		uom = "Nos",
+		do_not_save = 1
+	)
+	pi.append("taxes", {
+		"charge_type": "On Net Total",
+		"account_head": "VAT - 15% - _TCSV",
+		"cost_center": "Main - _TCSV",
+		"description": "VAT 15% @ 15.0",
+		"rate": 15.0
+	})
+
+	pi.submit()
diff --git a/erpnext/regional/report/vat_audit_report/vat_audit_report.py b/erpnext/regional/report/vat_audit_report/vat_audit_report.py
index 17aca17..88f6b92 100644
--- a/erpnext/regional/report/vat_audit_report/vat_audit_report.py
+++ b/erpnext/regional/report/vat_audit_report/vat_audit_report.py
@@ -1,11 +1,11 @@
-# Copyright (c) 2013, Frappe Technologies Pvt. Ltd. and contributors
+# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors
 # For license information, please see license.txt
 
 from __future__ import unicode_literals
 import frappe
 import json
 from frappe import _
-from frappe.utils import formatdate
+from frappe.utils import formatdate, get_link_to_form
 
 def execute(filters=None):
 	return VATAuditReport(filters).run()
@@ -42,7 +42,8 @@
 		self.sa_vat_accounts = frappe.get_list("South Africa VAT Account",
 			filters = {"parent": self.filters.company}, pluck="account")
 		if not self.sa_vat_accounts and not frappe.flags.in_test and not frappe.flags.in_migrate:
-			frappe.throw(_("Please set VAT Accounts in South Africa VAT Settings"))
+			link_to_settings = get_link_to_form("South Africa VAT Settings", "", label="South Africa VAT Settings")
+			frappe.throw(_("Please set VAT Accounts in {0}").format(link_to_settings))
 
 	def get_invoice_data(self, doctype):
 		conditions = self.get_conditions()
@@ -69,7 +70,7 @@
 
 		items = frappe.db.sql("""
 			SELECT
-				item_code, parent, taxable_value, base_net_amount, is_zero_rated
+				item_code, parent, base_net_amount, is_zero_rated
 			FROM
 				`tab%s Item`
 			WHERE
@@ -79,7 +80,7 @@
 			if d.item_code not in self.invoice_items.get(d.parent, {}):
 				self.invoice_items.setdefault(d.parent, {}).setdefault(d.item_code, {
 					'net_amount': 0.0})
-				self.invoice_items[d.parent][d.item_code]['net_amount'] += d.get('taxable_value', 0) or d.get('base_net_amount', 0)
+				self.invoice_items[d.parent][d.item_code]['net_amount'] += d.get('base_net_amount', 0)
 				self.invoice_items[d.parent][d.item_code]['is_zero_rated'] = d.is_zero_rated
 
 	def get_items_based_on_tax_rate(self, doctype):
diff --git a/erpnext/selling/doctype/customer/customer.js b/erpnext/selling/doctype/customer/customer.js
index 2f06e98..cb00019 100644
--- a/erpnext/selling/doctype/customer/customer.js
+++ b/erpnext/selling/doctype/customer/customer.js
@@ -111,7 +111,6 @@
 		}
 
 		frappe.dynamic_link = {doc: frm.doc, fieldname: 'name', doctype: 'Customer'}
-		frm.toggle_display(['address_html','contact_html'], !frm.doc.__islocal);
 
 		if(!frm.doc.__islocal) {
 			frappe.contacts.render_address_and_contact(frm);
diff --git a/erpnext/selling/doctype/customer/customer.json b/erpnext/selling/doctype/customer/customer.json
index 0d839fc..2acc64c 100644
--- a/erpnext/selling/doctype/customer/customer.json
+++ b/erpnext/selling/doctype/customer/customer.json
@@ -268,6 +268,7 @@
    "options": "fa fa-map-marker"
   },
   {
+   "depends_on": "eval: !doc.__islocal",
    "fieldname": "address_html",
    "fieldtype": "HTML",
    "label": "Address HTML",
@@ -284,6 +285,7 @@
    "width": "50%"
   },
   {
+   "depends_on": "eval: !doc.__islocal",
    "fieldname": "contact_html",
    "fieldtype": "HTML",
    "label": "Contact HTML",
diff --git a/erpnext/stock/stock_ledger.py b/erpnext/stock/stock_ledger.py
index eddd048..27feec1 100644
--- a/erpnext/stock/stock_ledger.py
+++ b/erpnext/stock/stock_ledger.py
@@ -332,6 +332,7 @@
 			where
 				item_code = %(item_code)s
 				and warehouse = %(warehouse)s
+				and is_cancelled = 0
 				and timestamp(posting_date, time_format(posting_time, %(time_format)s)) = timestamp(%(posting_date)s, time_format(%(posting_time)s, %(time_format)s))
 
 			order by