Merge branch 'develop' into hr-separation
diff --git a/erpnext/accounts/doctype/gl_entry/gl_entry.py b/erpnext/accounts/doctype/gl_entry/gl_entry.py
index 9f71656..1987c83 100644
--- a/erpnext/accounts/doctype/gl_entry/gl_entry.py
+++ b/erpnext/accounts/doctype/gl_entry/gl_entry.py
@@ -42,7 +42,7 @@
 		self.validate_and_set_fiscal_year()
 		self.pl_must_have_cost_center()
 
-		if not self.flags.from_repost:
+		if not self.flags.from_repost and self.voucher_type != "Period Closing Voucher":
 			self.check_mandatory()
 			self.validate_cost_center()
 			self.check_pl_account()
@@ -51,7 +51,7 @@
 
 	def on_update(self):
 		adv_adj = self.flags.adv_adj
-		if not self.flags.from_repost:
+		if not self.flags.from_repost and self.voucher_type != "Period Closing Voucher":
 			self.validate_account_details(adv_adj)
 			self.validate_dimensions_for_pl_and_bs()
 			self.validate_allowed_dimensions()
diff --git a/erpnext/accounts/doctype/period_closing_voucher/period_closing_voucher.json b/erpnext/accounts/doctype/period_closing_voucher/period_closing_voucher.json
index 84c941e..54a76b3 100644
--- a/erpnext/accounts/doctype/period_closing_voucher/period_closing_voucher.json
+++ b/erpnext/accounts/doctype/period_closing_voucher/period_closing_voucher.json
@@ -10,10 +10,11 @@
   "fiscal_year",
   "amended_from",
   "company",
-  "cost_center_wise_pnl",
   "column_break1",
   "closing_account_head",
-  "remarks"
+  "remarks",
+  "gle_processing_status",
+  "error_message"
  ],
  "fields": [
   {
@@ -86,17 +87,26 @@
    "reqd": 1
   },
   {
-   "default": "0",
-   "fieldname": "cost_center_wise_pnl",
-   "fieldtype": "Check",
-   "label": "Book Cost Center Wise Profit/Loss"
+   "depends_on": "eval:doc.docstatus!=0",
+   "fieldname": "gle_processing_status",
+   "fieldtype": "Select",
+   "label": "GL Entry Processing Status",
+   "options": "In Progress\nCompleted\nFailed",
+   "read_only": 1
+  },
+  {
+   "depends_on": "eval:doc.gle_processing_status=='Failed'",
+   "fieldname": "error_message",
+   "fieldtype": "Text",
+   "label": "Error Message",
+   "read_only": 1
   }
  ],
  "icon": "fa fa-file-text",
  "idx": 1,
  "is_submittable": 1,
  "links": [],
- "modified": "2021-05-20 15:27:37.210458",
+ "modified": "2022-07-20 14:51:04.714154",
  "modified_by": "Administrator",
  "module": "Accounts",
  "name": "Period Closing Voucher",
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 5a86376..866a94d 100644
--- a/erpnext/accounts/doctype/period_closing_voucher/period_closing_voucher.py
+++ b/erpnext/accounts/doctype/period_closing_voucher/period_closing_voucher.py
@@ -8,7 +8,6 @@
 
 from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import (
 	get_accounting_dimensions,
-	get_dimensions,
 )
 from erpnext.accounts.utils import get_account_currency
 from erpnext.controllers.accounts_controller import AccountsController
@@ -20,13 +19,28 @@
 		self.validate_posting_date()
 
 	def on_submit(self):
+		self.db_set("gle_processing_status", "In Progress")
 		self.make_gl_entries()
 
 	def on_cancel(self):
+		self.db_set("gle_processing_status", "In Progress")
 		self.ignore_linked_doctypes = ("GL Entry", "Stock Ledger Entry")
-		from erpnext.accounts.general_ledger import make_reverse_gl_entries
-
-		make_reverse_gl_entries(voucher_type="Period Closing Voucher", voucher_no=self.name)
+		gle_count = frappe.db.count(
+			"GL Entry",
+			{"voucher_type": "Period Closing Voucher", "voucher_no": self.name, "is_cancelled": 0},
+		)
+		if gle_count > 5000:
+			frappe.enqueue(
+				make_reverse_gl_entries,
+				voucher_type="Period Closing Voucher",
+				voucher_no=self.name,
+				queue="long",
+			)
+			frappe.msgprint(
+				_("The GL Entries will be cancelled in the background, it can take a few minutes."), alert=True
+			)
+		else:
+			make_reverse_gl_entries(voucher_type="Period Closing Voucher", voucher_no=self.name)
 
 	def validate_account_head(self):
 		closing_account_type = frappe.db.get_value("Account", self.closing_account_head, "root_type")
@@ -67,90 +81,80 @@
 	def make_gl_entries(self):
 		gl_entries = self.get_gl_entries()
 		if gl_entries:
-			from erpnext.accounts.general_ledger import make_gl_entries
-
-			make_gl_entries(gl_entries)
+			if len(gl_entries) > 5000:
+				frappe.enqueue(process_gl_entries, gl_entries=gl_entries, queue="long")
+				frappe.msgprint(
+					_("The GL Entries will be processed in the background, it can take a few minutes."),
+					alert=True,
+				)
+			else:
+				process_gl_entries(gl_entries)
 
 	def get_gl_entries(self):
 		gl_entries = []
-		pl_accounts = self.get_pl_balances()
 
-		for acc in pl_accounts:
+		# pl account
+		for acc in self.get_pl_balances_based_on_dimensions(group_by_account=True):
 			if flt(acc.bal_in_company_currency):
-				gl_entries.append(
-					self.get_gl_dict(
-						{
-							"account": acc.account,
-							"cost_center": acc.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,
-							"debit": abs(flt(acc.bal_in_company_currency))
-							if flt(acc.bal_in_company_currency) < 0
-							else 0,
-							"credit_in_account_currency": abs(flt(acc.bal_in_account_currency))
-							if flt(acc.bal_in_account_currency) > 0
-							else 0,
-							"credit": abs(flt(acc.bal_in_company_currency))
-							if flt(acc.bal_in_company_currency) > 0
-							else 0,
-						},
-						item=acc,
-					)
-				)
+				gl_entries.append(self.get_gle_for_pl_account(acc))
 
-		if gl_entries:
-			gle_for_net_pl_bal = self.get_pnl_gl_entry(pl_accounts)
-			gl_entries += gle_for_net_pl_bal
+		# closing liability account
+		for acc in self.get_pl_balances_based_on_dimensions(group_by_account=False):
+			if flt(acc.bal_in_company_currency):
+				gl_entries.append(self.get_gle_for_closing_account(acc))
 
 		return gl_entries
 
-	def get_pnl_gl_entry(self, pl_accounts):
-		company_cost_center = frappe.db.get_value("Company", self.company, "cost_center")
-		gl_entries = []
+	def get_gle_for_pl_account(self, acc):
+		gl_entry = self.get_gl_dict(
+			{
+				"account": acc.account,
+				"cost_center": acc.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,
+				"debit": abs(flt(acc.bal_in_company_currency)) if flt(acc.bal_in_company_currency) < 0 else 0,
+				"credit_in_account_currency": abs(flt(acc.bal_in_account_currency))
+				if flt(acc.bal_in_account_currency) > 0
+				else 0,
+				"credit": abs(flt(acc.bal_in_company_currency)) if flt(acc.bal_in_company_currency) > 0 else 0,
+			},
+			item=acc,
+		)
+		self.update_default_dimensions(gl_entry, acc)
+		return gl_entry
 
-		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": 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,
-						"debit": abs(flt(acc.bal_in_company_currency))
-						if flt(acc.bal_in_company_currency) > 0
-						else 0,
-						"credit_in_account_currency": abs(flt(acc.bal_in_account_currency))
-						if flt(acc.bal_in_account_currency) < 0
-						else 0,
-						"credit": abs(flt(acc.bal_in_company_currency))
-						if flt(acc.bal_in_company_currency) < 0
-						else 0,
-					},
-					item=acc,
-				)
+	def get_gle_for_closing_account(self, acc):
+		gl_entry = self.get_gl_dict(
+			{
+				"account": self.closing_account_head,
+				"cost_center": acc.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,
+				"debit": abs(flt(acc.bal_in_company_currency)) if flt(acc.bal_in_company_currency) > 0 else 0,
+				"credit_in_account_currency": abs(flt(acc.bal_in_account_currency))
+				if flt(acc.bal_in_account_currency) < 0
+				else 0,
+				"credit": abs(flt(acc.bal_in_company_currency)) if flt(acc.bal_in_company_currency) < 0 else 0,
+			},
+			item=acc,
+		)
+		self.update_default_dimensions(gl_entry, acc)
+		return gl_entry
 
-				self.update_default_dimensions(gl_entry)
-
-				gl_entries.append(gl_entry)
-
-		return gl_entries
-
-	def update_default_dimensions(self, gl_entry):
+	def update_default_dimensions(self, gl_entry, acc):
 		if not self.accounting_dimensions:
 			self.accounting_dimensions = get_accounting_dimensions()
 
-		_, default_dimensions = get_dimensions()
 		for dimension in self.accounting_dimensions:
-			gl_entry.update({dimension: default_dimensions.get(self.company, {}).get(dimension)})
+			gl_entry.update({dimension: acc.get(dimension)})
 
-	def get_pl_balances(self):
+	def get_pl_balances_based_on_dimensions(self, group_by_account=False):
 		"""Get balance for dimension-wise pl accounts"""
 
 		dimension_fields = ["t1.cost_center", "t1.finance_book"]
@@ -159,20 +163,56 @@
 		for dimension in self.accounting_dimensions:
 			dimension_fields.append("t1.{0}".format(dimension))
 
+		if group_by_account:
+			dimension_fields.append("t1.account")
+
 		return frappe.db.sql(
 			"""
 			select
-				t1.account, t2.account_currency, {dimension_fields},
+				t2.account_currency,
+				{dimension_fields},
 				sum(t1.debit_in_account_currency) - sum(t1.credit_in_account_currency) as bal_in_account_currency,
 				sum(t1.debit) - sum(t1.credit) as bal_in_company_currency
 			from `tabGL Entry` t1, `tabAccount` t2
-			where t1.is_cancelled = 0 and t1.account = t2.name and t2.report_type = 'Profit and Loss'
-			and t2.docstatus < 2 and t2.company = %s
-			and t1.posting_date between %s and %s
-			group by t1.account, {dimension_fields}
+			where
+				t1.is_cancelled = 0
+				and t1.account = t2.name
+				and t2.report_type = 'Profit and Loss'
+				and t2.docstatus < 2
+				and t2.company = %s
+				and t1.posting_date between %s and %s
+			group by {dimension_fields}
 		""".format(
 				dimension_fields=", ".join(dimension_fields)
 			),
 			(self.company, self.get("year_start_date"), self.posting_date),
 			as_dict=1,
 		)
+
+
+def process_gl_entries(gl_entries):
+	from erpnext.accounts.general_ledger import make_gl_entries
+
+	try:
+		make_gl_entries(gl_entries, merge_entries=False)
+		frappe.db.set_value(
+			"Period Closing Voucher", gl_entries[0].get("voucher_no"), "gle_processing_status", "Completed"
+		)
+	except Exception as e:
+		frappe.db.rollback()
+		frappe.log_error(e)
+		frappe.db.set_value(
+			"Period Closing Voucher", gl_entries[0].get("voucher_no"), "gle_processing_status", "Failed"
+		)
+
+
+def make_reverse_gl_entries(voucher_type, voucher_no):
+	from erpnext.accounts.general_ledger import make_reverse_gl_entries
+
+	try:
+		make_reverse_gl_entries(voucher_type=voucher_type, voucher_no=voucher_no)
+		frappe.db.set_value("Period Closing Voucher", voucher_no, "gle_processing_status", "Completed")
+	except Exception as e:
+		frappe.db.rollback()
+		frappe.log_error(e)
+		frappe.db.set_value("Period Closing Voucher", voucher_no, "gle_processing_status", "Failed")
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 3b938ea..3dca588 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
@@ -49,7 +49,7 @@
 
 		expected_gle = (
 			("Cost of Goods Sold - TPC", 0.0, 600.0),
-			(surplus_account, 600.0, 400.0),
+			(surplus_account, 200.0, 0.0),
 			("Sales - TPC", 400.0, 0.0),
 		)
 
@@ -59,7 +59,8 @@
 		""",
 			(pcv.name),
 		)
-
+		pcv.reload()
+		self.assertEqual(pcv.gle_processing_status, "Completed")
 		self.assertEqual(pcv_gle, expected_gle)
 
 	def test_cost_center_wise_posting(self):
@@ -93,7 +94,6 @@
 		)
 
 		pcv = self.make_period_closing_voucher(submit=False)
-		pcv.cost_center_wise_pnl = 1
 		pcv.save()
 		pcv.submit()
 		surplus_account = pcv.closing_account_head
@@ -116,6 +116,16 @@
 
 		self.assertEqual(pcv_gle, expected_gle)
 
+		pcv.reload()
+		pcv.cancel()
+
+		self.assertFalse(
+			frappe.db.get_value(
+				"GL Entry",
+				{"voucher_type": "Period Closing Voucher", "voucher_no": pcv.name, "is_cancelled": 0},
+			)
+		)
+
 	def test_period_closing_with_finance_book_entries(self):
 		frappe.db.sql("delete from `tabGL Entry` where company='Test PCV Company'")
 
diff --git a/erpnext/accounts/general_ledger.py b/erpnext/accounts/general_ledger.py
index 76ef3ab..16072f3 100644
--- a/erpnext/accounts/general_ledger.py
+++ b/erpnext/accounts/general_ledger.py
@@ -168,6 +168,7 @@
 def merge_similar_entries(gl_map, precision=None):
 	merged_gl_map = []
 	accounting_dimensions = get_accounting_dimensions()
+
 	for entry in gl_map:
 		# if there is already an entry in this account then just add it
 		# to that entry
@@ -298,9 +299,10 @@
 	gle.flags.from_repost = from_repost
 	gle.flags.adv_adj = adv_adj
 	gle.flags.update_outstanding = update_outstanding or "Yes"
+	gle.flags.notify_update = False
 	gle.submit()
 
-	if not from_repost:
+	if not from_repost and gle.voucher_type != "Period Closing Voucher":
 		validate_expense_against_budget(args)
 
 
diff --git a/erpnext/assets/doctype/asset/asset.json b/erpnext/assets/doctype/asset/asset.json
index 6e6bbf1..991df4e 100644
--- a/erpnext/assets/doctype/asset/asset.json
+++ b/erpnext/assets/doctype/asset/asset.json
@@ -40,7 +40,6 @@
   "purchase_date",
   "section_break_23",
   "calculate_depreciation",
-  "allow_monthly_depreciation",
   "column_break_33",
   "opening_accumulated_depreciation",
   "number_of_depreciations_booked",
@@ -457,13 +456,6 @@
    "fieldtype": "Column Break"
   },
   {
-   "default": "0",
-   "depends_on": "calculate_depreciation",
-   "fieldname": "allow_monthly_depreciation",
-   "fieldtype": "Check",
-   "label": "Allow Monthly Depreciation"
-  },
-  {
    "collapsible": 1,
    "collapsible_depends_on": "is_existing_asset",
    "fieldname": "purchase_details_section",
@@ -518,7 +510,7 @@
    "link_fieldname": "asset"
   }
  ],
- "modified": "2022-01-30 20:19:24.680027",
+ "modified": "2022-07-20 10:15:12.887372",
  "modified_by": "Administrator",
  "module": "Assets",
  "name": "Asset",
diff --git a/erpnext/assets/doctype/asset/asset.py b/erpnext/assets/doctype/asset/asset.py
index a880c2f..a22d70d 100644
--- a/erpnext/assets/doctype/asset/asset.py
+++ b/erpnext/assets/doctype/asset/asset.py
@@ -343,51 +343,13 @@
 				skip_row = True
 
 			if depreciation_amount > 0:
-				# With monthly depreciation, each depreciation is divided by months remaining until next date
-				if self.allow_monthly_depreciation:
-					# month range is 1 to 12
-					# In pro rata case, for first and last depreciation, month range would be different
-					month_range = (
-						months
-						if (has_pro_rata and n == 0)
-						or (has_pro_rata and n == cint(number_of_pending_depreciations) - 1)
-						else finance_book.frequency_of_depreciation
-					)
-
-					for r in range(month_range):
-						if has_pro_rata and n == 0:
-							# For first entry of monthly depr
-							if r == 0:
-								days_until_first_depr = date_diff(monthly_schedule_date, self.available_for_use_date)
-								per_day_amt = depreciation_amount / days
-								depreciation_amount_for_current_month = per_day_amt * days_until_first_depr
-								depreciation_amount -= depreciation_amount_for_current_month
-								date = monthly_schedule_date
-								amount = depreciation_amount_for_current_month
-							else:
-								date = add_months(monthly_schedule_date, r)
-								amount = depreciation_amount / (month_range - 1)
-						elif (has_pro_rata and n == cint(number_of_pending_depreciations) - 1) and r == cint(
-							month_range
-						) - 1:
-							# For last entry of monthly depr
-							date = last_schedule_date
-							amount = depreciation_amount / month_range
-						else:
-							date = add_months(monthly_schedule_date, r)
-							amount = depreciation_amount / month_range
-
-						self._add_depreciation_row(
-							date, amount, finance_book.depreciation_method, finance_book.finance_book, finance_book.idx
-						)
-				else:
-					self._add_depreciation_row(
-						schedule_date,
-						depreciation_amount,
-						finance_book.depreciation_method,
-						finance_book.finance_book,
-						finance_book.idx,
-					)
+				self._add_depreciation_row(
+					schedule_date,
+					depreciation_amount,
+					finance_book.depreciation_method,
+					finance_book.finance_book,
+					finance_book.idx,
+				)
 
 	def _add_depreciation_row(
 		self, schedule_date, depreciation_amount, depreciation_method, finance_book, finance_book_id
@@ -854,10 +816,8 @@
 				return args.get("rate_of_depreciation")
 
 			value = flt(args.get("expected_value_after_useful_life")) / flt(self.gross_purchase_amount)
-
 			depreciation_rate = math.pow(value, 1.0 / flt(args.get("total_number_of_depreciations"), 2))
-
-			return 100 * (1 - flt(depreciation_rate, float_precision))
+			return flt((100 * (1 - depreciation_rate)), float_precision)
 
 	def get_pro_rata_amt(self, row, depreciation_amount, from_date, to_date):
 		days = date_diff(to_date, from_date)
diff --git a/erpnext/assets/doctype/asset/test_asset.py b/erpnext/assets/doctype/asset/test_asset.py
index 95abaa1..34d374c 100644
--- a/erpnext/assets/doctype/asset/test_asset.py
+++ b/erpnext/assets/doctype/asset/test_asset.py
@@ -721,12 +721,12 @@
 		)
 
 		expected_schedules = [
-			["2022-02-28", 645.0, 645.0],
-			["2022-03-31", 1206.8, 1851.8],
-			["2022-04-30", 1051.12, 2902.92],
-			["2022-05-31", 915.52, 3818.44],
-			["2022-06-30", 797.42, 4615.86],
-			["2022-07-15", 384.14, 5000.0],
+			["2022-02-28", 647.25, 647.25],
+			["2022-03-31", 1210.71, 1857.96],
+			["2022-04-30", 1053.99, 2911.95],
+			["2022-05-31", 917.55, 3829.5],
+			["2022-06-30", 798.77, 4628.27],
+			["2022-07-15", 371.73, 5000.0],
 		]
 
 		schedules = [
@@ -737,7 +737,6 @@
 			]
 			for d in asset.get("schedules")
 		]
-
 		self.assertEqual(schedules, expected_schedules)
 
 
diff --git a/erpnext/patches.txt b/erpnext/patches.txt
index aa85ea7..35b4c3c 100644
--- a/erpnext/patches.txt
+++ b/erpnext/patches.txt
@@ -306,4 +306,5 @@
 erpnext.patches.v14_0.migrate_gl_to_payment_ledger
 erpnext.patches.v14_0.crm_ux_cleanup
 erpnext.patches.v14_0.remove_india_localisation # 14-07-2022
+erpnext.patches.v13_0.fix_number_and_frequency_for_monthly_depreciation
 erpnext.patches.v14_0.remove_hr_and_payroll_modules
\ No newline at end of file
diff --git a/erpnext/patches/v13_0/fix_number_and_frequency_for_monthly_depreciation.py b/erpnext/patches/v13_0/fix_number_and_frequency_for_monthly_depreciation.py
new file mode 100644
index 0000000..e827058
--- /dev/null
+++ b/erpnext/patches/v13_0/fix_number_and_frequency_for_monthly_depreciation.py
@@ -0,0 +1,14 @@
+import frappe
+
+
+def execute():
+	assets = frappe.get_all("Asset", filters={"allow_monthly_depreciation": 1})
+
+	for d in assets:
+		print(d.name)
+		asset_doc = frappe.get_doc("Asset", d.name)
+		for i in asset_doc.get("finance_books"):
+			if i.frequency_of_depreciation != 1:
+				i.total_number_of_depreciations *= i.frequency_of_depreciation
+				i.frequency_of_depreciation = 1
+				i.db_update()
diff --git a/erpnext/stock/stock_ledger.py b/erpnext/stock/stock_ledger.py
index 80256c3..fd1aece 100644
--- a/erpnext/stock/stock_ledger.py
+++ b/erpnext/stock/stock_ledger.py
@@ -208,6 +208,8 @@
 	via_landed_cost_voucher=False,
 	doc=None,
 ):
+	if not args:
+		args = []  # set args to empty list if None to avoid enumerate error
 
 	items_to_be_repost = get_items_to_be_repost(
 		voucher_type=voucher_type, voucher_no=voucher_no, doc=doc
@@ -303,7 +305,7 @@
 			group_by="item_code, warehouse",
 		)
 
-	return items_to_be_repost
+	return items_to_be_repost or []
 
 
 def get_distinct_item_warehouse(args=None, doc=None):