Merge branch 'develop' into manufacturing-work-order-closed
diff --git a/erpnext/accounts/doctype/account/account_tree.js b/erpnext/accounts/doctype/account/account_tree.js
index b9ebb58..a3ef384 100644
--- a/erpnext/accounts/doctype/account/account_tree.js
+++ b/erpnext/accounts/doctype/account/account_tree.js
@@ -176,7 +176,7 @@
 					&& node.expandable && !node.hide_add;
 			},
 			click: function() {
-				var me = frappe.treeview_settings['Account'].treeview;
+				var me = frappe.views.trees['Account'];
 				me.new_node();
 			},
 			btnClass: "hidden-xs"
diff --git a/erpnext/accounts/doctype/accounts_settings/accounts_settings.py b/erpnext/accounts/doctype/accounts_settings/accounts_settings.py
index aa132a0..7451917 100644
--- a/erpnext/accounts/doctype/accounts_settings/accounts_settings.py
+++ b/erpnext/accounts/doctype/accounts_settings/accounts_settings.py
@@ -19,6 +19,9 @@
 		frappe.db.set_default("add_taxes_from_item_tax_template",
 			self.get("add_taxes_from_item_tax_template", 0))
 
+		frappe.db.set_default("enable_common_party_accounting",
+			self.get("enable_common_party_accounting", 0))
+
 		self.validate_stale_days()
 		self.enable_payment_schedule_in_print()
 		self.toggle_discount_accounting_fields()
diff --git a/erpnext/accounts/doctype/party_link/party_link.py b/erpnext/accounts/doctype/party_link/party_link.py
index daf667c..e9f813c 100644
--- a/erpnext/accounts/doctype/party_link/party_link.py
+++ b/erpnext/accounts/doctype/party_link/party_link.py
@@ -25,3 +25,17 @@
 		if existing_party_link:
 			frappe.throw(_('{} {} is already linked with another {}')
 				.format(self.primary_role, self.primary_party, existing_party_link[0]))
+
+
+@frappe.whitelist()
+def create_party_link(primary_role, primary_party, secondary_party):
+	party_link = frappe.new_doc('Party Link')
+	party_link.primary_role = primary_role
+	party_link.primary_party = primary_party
+	party_link.secondary_role = 'Customer' if primary_role == 'Supplier' else 'Supplier'
+	party_link.secondary_party = secondary_party
+
+	party_link.save(ignore_permissions=True)
+
+	return party_link
+
diff --git a/erpnext/accounts/doctype/period_closing_voucher/period_closing_voucher.py b/erpnext/accounts/doctype/period_closing_voucher/period_closing_voucher.py
index 34572fd..d0e555e 100644
--- a/erpnext/accounts/doctype/period_closing_voucher/period_closing_voucher.py
+++ b/erpnext/accounts/doctype/period_closing_voucher/period_closing_voucher.py
@@ -88,9 +88,10 @@
 
 		for acc in pl_accounts:
 			if flt(acc.bal_in_company_currency):
+				cost_center = acc.cost_center if self.cost_center_wise_pnl else company_cost_center
 				gl_entry = self.get_gl_dict({
 					"account": self.closing_account_head,
-					"cost_center": acc.cost_center or company_cost_center,
+					"cost_center": cost_center,
 					"finance_book": acc.finance_book,
 					"account_currency": acc.account_currency,
 					"debit_in_account_currency": abs(flt(acc.bal_in_account_currency)) if flt(acc.bal_in_account_currency) > 0 else 0,
diff --git a/erpnext/accounts/doctype/period_closing_voucher/test_period_closing_voucher.py b/erpnext/accounts/doctype/period_closing_voucher/test_period_closing_voucher.py
index 0e29755..030b4ca 100644
--- a/erpnext/accounts/doctype/period_closing_voucher/test_period_closing_voucher.py
+++ b/erpnext/accounts/doctype/period_closing_voucher/test_period_closing_voucher.py
@@ -66,8 +66,8 @@
 		company = create_company()
 		surplus_account = create_account()
 
-		cost_center1 = create_cost_center("Test Cost Center 1")
-		cost_center2 = create_cost_center("Test Cost Center 2")
+		cost_center1 = create_cost_center("Main")
+		cost_center2 = create_cost_center("Western Branch")
 
 		create_sales_invoice(
 			company=company,
@@ -86,7 +86,10 @@
 			debit_to="Debtors - TPC"
 		)
 
-		pcv = self.make_period_closing_voucher()
+		pcv = self.make_period_closing_voucher(submit=False)
+		pcv.cost_center_wise_pnl = 1
+		pcv.save()
+		pcv.submit()
 		surplus_account = pcv.closing_account_head
 
 		expected_gle = (
@@ -149,7 +152,7 @@
 
 		self.assertEqual(pcv_gle, expected_gle)
 
-	def make_period_closing_voucher(self):
+	def make_period_closing_voucher(self, submit=True):
 		surplus_account = create_account()
 		cost_center = create_cost_center("Test Cost Center 1")
 		pcv = frappe.get_doc({
@@ -163,7 +166,8 @@
 			"remarks": "test"
 		})
 		pcv.insert()
-		pcv.submit()
+		if submit:
+			pcv.submit()
 
 		return pcv
 
diff --git a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py
index 02e2416..b5453ac 100644
--- a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py
+++ b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py
@@ -2300,6 +2300,7 @@
 		from erpnext.accounts.doctype.opening_invoice_creation_tool.test_opening_invoice_creation_tool import (
 			make_customer,
 		)
+		from erpnext.accounts.doctype.party_link.party_link import create_party_link
 		from erpnext.buying.doctype.supplier.test_supplier import create_supplier
 
 		# create a customer
@@ -2308,13 +2309,7 @@
 		supplier = create_supplier(supplier_name="_Test Common Supplier").name
 
 		# create a party link between customer & supplier
-		# set primary role as supplier
-		party_link = frappe.new_doc("Party Link")
-		party_link.primary_role = "Supplier"
-		party_link.primary_party = supplier
-		party_link.secondary_role = "Customer"
-		party_link.secondary_party = customer
-		party_link.save()
+		party_link = create_party_link("Supplier", supplier, customer)
 
 		# enable common party accounting
 		frappe.db.set_value('Accounts Settings', None, 'enable_common_party_accounting', 1)
diff --git a/erpnext/accounts/report/gross_profit/gross_profit.js b/erpnext/accounts/report/gross_profit/gross_profit.js
index 856b97d..685f2d6 100644
--- a/erpnext/accounts/report/gross_profit/gross_profit.js
+++ b/erpnext/accounts/report/gross_profit/gross_profit.js
@@ -44,7 +44,7 @@
 	"formatter": function(value, row, column, data, default_formatter) {
 		value = default_formatter(value, row, column, data);
 
-		if (data && data.indent == 0.0) {
+		if (data && (data.indent == 0.0 || row[1].content == "Total")) {
 			value = $(`<span>${value}</span>`);
 			var $value = $(value).css("font-weight", "bold");
 			value = $value.wrap("<p></p>").parent().html();
diff --git a/erpnext/accounts/report/gross_profit/gross_profit.json b/erpnext/accounts/report/gross_profit/gross_profit.json
index 5fff3fd..76c560a 100644
--- a/erpnext/accounts/report/gross_profit/gross_profit.json
+++ b/erpnext/accounts/report/gross_profit/gross_profit.json
@@ -9,7 +9,7 @@
  "filters": [],
  "idx": 3,
  "is_standard": "Yes",
- "modified": "2021-08-19 18:57:07.468202",
+ "modified": "2021-11-13 19:14:23.730198",
  "modified_by": "Administrator",
  "module": "Accounts",
  "name": "Gross Profit",
diff --git a/erpnext/accounts/report/gross_profit/gross_profit.py b/erpnext/accounts/report/gross_profit/gross_profit.py
index 9d5a242..20bc3ec 100644
--- a/erpnext/accounts/report/gross_profit/gross_profit.py
+++ b/erpnext/accounts/report/gross_profit/gross_profit.py
@@ -19,7 +19,7 @@
 	data = []
 
 	group_wise_columns = frappe._dict({
-		"invoice": ["parent", "customer", "customer_group", "posting_date","item_code", "item_name","item_group", "brand", "description", \
+		"invoice": ["invoice_or_item", "customer", "customer_group", "posting_date","item_code", "item_name","item_group", "brand", "description",
 			"warehouse", "qty", "base_rate", "buying_rate", "base_amount",
 			"buying_amount", "gross_profit", "gross_profit_percent", "project"],
 		"item_code": ["item_code", "item_name", "brand", "description", "qty", "base_rate",
@@ -77,13 +77,15 @@
 
 		row.append(filters.currency)
 		if idx == len(gross_profit_data.grouped_data)-1:
-			row[0] = frappe.bold("Total")
+			row[0] = "Total"
+
 		data.append(row)
 
 def get_columns(group_wise_columns, filters):
 	columns = []
 	column_map = frappe._dict({
 		"parent": _("Sales Invoice") + ":Link/Sales Invoice:120",
+		"invoice_or_item": _("Sales Invoice") + ":Link/Sales Invoice:120",
 		"posting_date": _("Posting Date") + ":Date:100",
 		"posting_time": _("Posting Time") + ":Data:100",
 		"item_code": _("Item Code") + ":Link/Item:100",
@@ -122,7 +124,7 @@
 
 def get_column_names():
 	return frappe._dict({
-		'parent': 'sales_invoice',
+		'invoice_or_item': 'sales_invoice',
 		'customer': 'customer',
 		'customer_group': 'customer_group',
 		'posting_date': 'posting_date',
@@ -245,19 +247,28 @@
 				self.add_to_totals(new_row)
 			else:
 				for i, row in enumerate(self.grouped[key]):
-					if row.parent in self.returned_invoices \
-							and row.item_code in self.returned_invoices[row.parent]:
-						returned_item_rows = self.returned_invoices[row.parent][row.item_code]
-						for returned_item_row in returned_item_rows:
-							row.qty += flt(returned_item_row.qty)
-							row.base_amount += flt(returned_item_row.base_amount, self.currency_precision)
-						row.buying_amount = flt(flt(row.qty) * flt(row.buying_rate), self.currency_precision)
-					if (flt(row.qty) or row.base_amount) and self.is_not_invoice_row(row):
-						row = self.set_average_rate(row)
-						self.grouped_data.append(row)
-					self.add_to_totals(row)
+					if row.indent == 1.0:
+						if row.parent in self.returned_invoices \
+								and row.item_code in self.returned_invoices[row.parent]:
+							returned_item_rows = self.returned_invoices[row.parent][row.item_code]
+							for returned_item_row in returned_item_rows:
+								row.qty += flt(returned_item_row.qty)
+								row.base_amount += flt(returned_item_row.base_amount, self.currency_precision)
+							row.buying_amount = flt(flt(row.qty) * flt(row.buying_rate), self.currency_precision)
+						if (flt(row.qty) or row.base_amount):
+							row = self.set_average_rate(row)
+							self.grouped_data.append(row)
+						self.add_to_totals(row)
+
 		self.set_average_gross_profit(self.totals)
-		self.grouped_data.append(self.totals)
+
+		if self.filters.get("group_by") == "Invoice":
+			self.totals.indent = 0.0
+			self.totals.parent_invoice = ""
+			self.totals.parent = "Total"
+			self.si_list.append(self.totals)
+		else:
+			self.grouped_data.append(self.totals)
 
 	def is_not_invoice_row(self, row):
 		return (self.filters.get("group_by") == "Invoice" and row.indent != 0.0) or self.filters.get("group_by") != "Invoice"
@@ -446,7 +457,7 @@
 				if not row.indent:
 					row.indent = 1.0
 					row.parent_invoice = row.parent
-					row.parent = row.item_code
+					row.invoice_or_item = row.item_code
 
 					if frappe.db.exists('Product Bundle', row.item_code):
 						self.add_bundle_items(row, index)
@@ -455,7 +466,8 @@
 		return frappe._dict({
 			'parent_invoice': "",
 			'indent': 0.0,
-			'parent': row.parent,
+			'invoice_or_item': row.parent,
+			'parent': None,
 			'posting_date': row.posting_date,
 			'posting_time': row.posting_time,
 			'project': row.project,
@@ -499,7 +511,8 @@
 		return frappe._dict({
 			'parent_invoice': product_bundle.item_code,
 			'indent': product_bundle.indent + 1,
-			'parent': item.item_code,
+			'parent': None,
+			'invoice_or_item': item.item_code,
 			'posting_date': product_bundle.posting_date,
 			'posting_time': product_bundle.posting_time,
 			'project': product_bundle.project,
diff --git a/erpnext/assets/doctype/asset_value_adjustment/asset_value_adjustment.js b/erpnext/assets/doctype/asset_value_adjustment/asset_value_adjustment.js
index 79c8861..36f510b 100644
--- a/erpnext/assets/doctype/asset_value_adjustment/asset_value_adjustment.js
+++ b/erpnext/assets/doctype/asset_value_adjustment/asset_value_adjustment.js
@@ -14,6 +14,14 @@
 				}
 			}
 		});
+		frm.set_query('asset', function() {
+			return {
+				filters: {
+					calculate_depreciation: 1,
+					docstatus: 1
+				}
+			};
+		});
 	},
 
 	onload: function(frm) {
diff --git a/erpnext/assets/doctype/asset_value_adjustment/asset_value_adjustment.py b/erpnext/assets/doctype/asset_value_adjustment/asset_value_adjustment.py
index b93f474..0b646ed 100644
--- a/erpnext/assets/doctype/asset_value_adjustment/asset_value_adjustment.py
+++ b/erpnext/assets/doctype/asset_value_adjustment/asset_value_adjustment.py
@@ -10,7 +10,11 @@
 from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import (
 	get_checks_for_pl_and_bs_accounts,
 )
+from erpnext.assets.doctype.asset.asset import get_depreciation_amount
 from erpnext.assets.doctype.asset.depreciation import get_depreciation_accounts
+from erpnext.regional.india.utils import (
+	get_depreciation_amount as get_depreciation_amount_for_india,
+)
 
 
 class AssetValueAdjustment(Document):
@@ -90,6 +94,7 @@
 
 	def reschedule_depreciations(self, asset_value):
 		asset = frappe.get_doc('Asset', self.asset)
+		country = frappe.get_value('Company', self.company, 'country')
 
 		for d in asset.finance_books:
 			d.value_after_depreciation = asset_value
@@ -111,8 +116,10 @@
 						depreciation_amount = days * rate_per_day
 						from_date = data.schedule_date
 					else:
-						depreciation_amount = asset.get_depreciation_amount(value_after_depreciation,
-							no_of_depreciations, d)
+						if country == "India":
+							depreciation_amount = get_depreciation_amount_for_india(asset, value_after_depreciation, d)
+						else:
+							depreciation_amount = get_depreciation_amount(asset, value_after_depreciation, d)
 
 					if depreciation_amount:
 						value_after_depreciation -= flt(depreciation_amount)
diff --git a/erpnext/buying/doctype/supplier/supplier.js b/erpnext/buying/doctype/supplier/supplier.js
index 7ee9196..f0899b0 100644
--- a/erpnext/buying/doctype/supplier/supplier.js
+++ b/erpnext/buying/doctype/supplier/supplier.js
@@ -83,6 +83,12 @@
 				frm.trigger("get_supplier_group_details");
 			}, __('Actions'));
 
+			if (cint(frappe.defaults.get_default("enable_common_party_accounting"))) {
+				frm.add_custom_button(__('Link with Customer'), function () {
+					frm.trigger('show_party_link_dialog');
+				}, __('Actions'));
+			}
+
 			// indicators
 			erpnext.utils.set_party_dashboard_indicators(frm);
 		}
@@ -128,5 +134,42 @@
 		else {
 			frm.toggle_reqd("represents_company", false);
 		}
+	},
+	show_party_link_dialog: function(frm) {
+		const dialog = new frappe.ui.Dialog({
+			title: __('Select a Customer'),
+			fields: [{
+				fieldtype: 'Link', label: __('Customer'),
+				options: 'Customer', fieldname: 'customer', reqd: 1
+			}],
+			primary_action: function({ customer }) {
+				frappe.call({
+					method: 'erpnext.accounts.doctype.party_link.party_link.create_party_link',
+					args: {
+						primary_role: 'Supplier',
+						primary_party: frm.doc.name,
+						secondary_party: customer
+					},
+					freeze: true,
+					callback: function() {
+						dialog.hide();
+						frappe.msgprint({
+							message: __('Successfully linked to Customer'),
+							alert: true
+						});
+					},
+					error: function() {
+						dialog.hide();
+						frappe.msgprint({
+							message: __('Linking to Customer Failed. Please try again.'),
+							title: __('Linking Failed'),
+							indicator: 'red'
+						});
+					}
+				});
+			},
+			primary_action_label: __('Create Link')
+		});
+		dialog.show();
 	}
 });
diff --git a/erpnext/controllers/stock_controller.py b/erpnext/controllers/stock_controller.py
index 08d422d..aba15b4 100644
--- a/erpnext/controllers/stock_controller.py
+++ b/erpnext/controllers/stock_controller.py
@@ -676,5 +676,6 @@
 	repost_entry.company = args.company
 	repost_entry.allow_zero_rate = args.allow_zero_rate
 	repost_entry.flags.ignore_links = True
+	repost_entry.flags.ignore_permissions = True
 	repost_entry.save()
 	repost_entry.submit()
diff --git a/erpnext/regional/saudi_arabia/utils.py b/erpnext/regional/saudi_arabia/utils.py
index cc6c0af..0c036f9 100644
--- a/erpnext/regional/saudi_arabia/utils.py
+++ b/erpnext/regional/saudi_arabia/utils.py
@@ -28,14 +28,22 @@
 
 	for field in meta.get_image_fields():
 		if field.fieldname == 'qr_code':
+			from urllib.parse import urlencode
+
 			# Creating public url to print format
 			default_print_format = frappe.db.get_value('Property Setter', dict(property='default_print_format', doc_type=doc.doctype), "value")
 
 			# System Language
 			language = frappe.get_system_settings('language')
 
+			params = urlencode({
+				'format': default_print_format or 'Standard',
+				'_lang': language,
+				'key': doc.get_signature()
+			})
+
 			# creating qr code for the url
-			url = f"{ frappe.utils.get_url() }/{ doc.doctype }/{ doc.name }?format={ default_print_format or 'Standard' }&_lang={ language }&key={ doc.get_signature() }"
+			url = f"{ frappe.utils.get_url() }/{ doc.doctype }/{ doc.name }?{ params }"
 			qr_image = io.BytesIO()
 			url = qr_create(url, error='L')
 			url.png(qr_image, scale=2, quiet_zone=1)
diff --git a/erpnext/selling/doctype/customer/customer.js b/erpnext/selling/doctype/customer/customer.js
index 4b0bbd5..107e4a4 100644
--- a/erpnext/selling/doctype/customer/customer.js
+++ b/erpnext/selling/doctype/customer/customer.js
@@ -134,6 +134,12 @@
 				frm.trigger("get_customer_group_details");
 			}, __('Actions'));
 
+			if (cint(frappe.defaults.get_default("enable_common_party_accounting"))) {
+				frm.add_custom_button(__('Link with Supplier'), function () {
+					frm.trigger('show_party_link_dialog');
+				}, __('Actions'));
+			}
+
 			// indicator
 			erpnext.utils.set_party_dashboard_indicators(frm);
 
@@ -158,5 +164,42 @@
 			}
 		});
 
+	},
+	show_party_link_dialog: function(frm) {
+		const dialog = new frappe.ui.Dialog({
+			title: __('Select a Supplier'),
+			fields: [{
+				fieldtype: 'Link', label: __('Supplier'),
+				options: 'Supplier', fieldname: 'supplier', reqd: 1
+			}],
+			primary_action: function({ supplier }) {
+				frappe.call({
+					method: 'erpnext.accounts.doctype.party_link.party_link.create_party_link',
+					args: {
+						primary_role: 'Customer',
+						primary_party: frm.doc.name,
+						secondary_party: supplier
+					},
+					freeze: true,
+					callback: function() {
+						dialog.hide();
+						frappe.msgprint({
+							message: __('Successfully linked to Supplier'),
+							alert: true
+						});
+					},
+					error: function() {
+						dialog.hide();
+						frappe.msgprint({
+							message: __('Linking to Supplier Failed. Please try again.'),
+							title: __('Linking Failed'),
+							indicator: 'red'
+						});
+					}
+				});
+			},
+			primary_action_label: __('Create Link')
+		});
+		dialog.show();
 	}
 });
diff --git a/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.json b/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.json
index a800bf8..3ff0f60 100644
--- a/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.json
+++ b/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.json
@@ -177,10 +177,11 @@
  "index_web_pages_for_search": 1,
  "is_submittable": 1,
  "links": [],
- "modified": "2021-07-22 18:59:43.057878",
+ "modified": "2021-11-18 02:18:10.524560",
  "modified_by": "Administrator",
  "module": "Stock",
  "name": "Repost Item Valuation",
+ "naming_rule": "Expression (old style)",
  "owner": "Administrator",
  "permissions": [
   {
@@ -206,27 +207,12 @@
    "print": 1,
    "read": 1,
    "report": 1,
-   "role": "Stock User",
-   "share": 1,
-   "submit": 1,
-   "write": 1
-  },
-  {
-   "cancel": 1,
-   "create": 1,
-   "delete": 1,
-   "email": 1,
-   "export": 1,
-   "print": 1,
-   "read": 1,
-   "report": 1,
    "role": "Stock Manager",
    "share": 1,
    "submit": 1,
    "write": 1
   },
   {
-   "cancel": 1,
    "create": 1,
    "delete": 1,
    "email": 1,
@@ -234,7 +220,7 @@
    "print": 1,
    "read": 1,
    "report": 1,
-   "role": "Accounts User",
+   "role": "Accounts Manager",
    "share": 1,
    "submit": 1,
    "write": 1
diff --git a/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py b/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py
index 170aa7f..59d191f 100644
--- a/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py
+++ b/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py
@@ -133,7 +133,7 @@
 	riv_entries = get_repost_item_valuation_entries()
 
 	for row in riv_entries:
-		doc = frappe.get_cached_doc('Repost Item Valuation', row.name)
+		doc = frappe.get_doc('Repost Item Valuation', row.name)
 		repost(doc)
 
 	riv_entries = get_repost_item_valuation_entries()
diff --git a/erpnext/stock/stock_ledger.py b/erpnext/stock/stock_ledger.py
index 9c4c676..9d40982 100644
--- a/erpnext/stock/stock_ledger.py
+++ b/erpnext/stock/stock_ledger.py
@@ -111,6 +111,7 @@
 				frappe.throw(_("Cannot cancel the transaction. Reposting of item valuation on submission is not completed yet."))
 			if repost_entry.status == 'Queued':
 				doc = frappe.get_doc("Repost Item Valuation", repost_entry.name)
+				doc.flags.ignore_permissions = True
 				doc.cancel()
 				doc.delete()