Merge pull request #36492 from RitvikSardana/develop-ritvik-POS-runtime-effect

fix: POS runtime effect
diff --git a/.mergify.yml b/.mergify.yml
index c5f3d83..804b27d 100644
--- a/.mergify.yml
+++ b/.mergify.yml
@@ -15,6 +15,8 @@
         - or:
           - base=version-13
           - base=version-12
+          - base=version-14
+          - base=version-15
     actions:
       close:
       comment:
diff --git a/erpnext/accounts/doctype/journal_entry/journal_entry.js b/erpnext/accounts/doctype/journal_entry/journal_entry.js
index 8d8cbef..35a3788 100644
--- a/erpnext/accounts/doctype/journal_entry/journal_entry.js
+++ b/erpnext/accounts/doctype/journal_entry/journal_entry.js
@@ -8,7 +8,7 @@
 frappe.ui.form.on("Journal Entry", {
 	setup: function(frm) {
 		frm.add_fetch("bank_account", "account", "account");
-		frm.ignore_doctypes_on_cancel_all = ['Sales Invoice', 'Purchase Invoice', 'Journal Entry', "Repost Payment Ledger", 'Asset', 'Asset Movement', 'Asset Depreciation Schedule'];
+		frm.ignore_doctypes_on_cancel_all = ['Sales Invoice', 'Purchase Invoice', 'Journal Entry', "Repost Payment Ledger", 'Asset', 'Asset Movement', 'Asset Depreciation Schedule', "Repost Accounting Ledger"];
 	},
 
 	refresh: function(frm) {
diff --git a/erpnext/accounts/doctype/journal_entry/journal_entry.json b/erpnext/accounts/doctype/journal_entry/journal_entry.json
index 80e7222..2eb54a5 100644
--- a/erpnext/accounts/doctype/journal_entry/journal_entry.json
+++ b/erpnext/accounts/doctype/journal_entry/journal_entry.json
@@ -9,6 +9,7 @@
  "engine": "InnoDB",
  "field_order": [
   "entry_type_and_date",
+  "is_system_generated",
   "title",
   "voucher_type",
   "naming_series",
@@ -533,13 +534,22 @@
    "label": "Process Deferred Accounting",
    "options": "Process Deferred Accounting",
    "read_only": 1
+  },
+  {
+   "default": "0",
+   "depends_on": "eval:doc.is_system_generated == 1;",
+   "fieldname": "is_system_generated",
+   "fieldtype": "Check",
+   "label": "Is System Generated",
+   "no_copy": 1,
+   "read_only": 1
   }
  ],
  "icon": "fa fa-file-text",
  "idx": 176,
  "is_submittable": 1,
  "links": [],
- "modified": "2023-03-01 14:58:59.286591",
+ "modified": "2023-08-10 14:32:22.366895",
  "modified_by": "Administrator",
  "module": "Accounts",
  "name": "Journal Entry",
diff --git a/erpnext/accounts/doctype/journal_entry/journal_entry.py b/erpnext/accounts/doctype/journal_entry/journal_entry.py
index 1e1b3ba..85ef6f7 100644
--- a/erpnext/accounts/doctype/journal_entry/journal_entry.py
+++ b/erpnext/accounts/doctype/journal_entry/journal_entry.py
@@ -96,6 +96,8 @@
 			"Payment Ledger Entry",
 			"Repost Payment Ledger",
 			"Repost Payment Ledger Items",
+			"Repost Accounting Ledger",
+			"Repost Accounting Ledger Items",
 		)
 		self.make_gl_entries(1)
 		self.update_advance_paid()
@@ -795,6 +797,9 @@
 	def create_remarks(self):
 		r = []
 
+		if self.flags.skip_remarks_creation:
+			return
+
 		if self.user_remark:
 			r.append(_("Note: {0}").format(self.user_remark))
 
diff --git a/erpnext/accounts/doctype/loyalty_program/loyalty_program.py b/erpnext/accounts/doctype/loyalty_program/loyalty_program.py
index a134f74..4f58579 100644
--- a/erpnext/accounts/doctype/loyalty_program/loyalty_program.py
+++ b/erpnext/accounts/doctype/loyalty_program/loyalty_program.py
@@ -145,8 +145,8 @@
 
 		loyalty_amount = flt(points_to_redeem * loyalty_program_details.conversion_factor)
 
-		if loyalty_amount > ref_doc.grand_total:
-			frappe.throw(_("You can't redeem Loyalty Points having more value than the Grand Total."))
+		if loyalty_amount > ref_doc.rounded_total:
+			frappe.throw(_("You can't redeem Loyalty Points having more value than the Rounded Total."))
 
 		if not ref_doc.loyalty_amount and ref_doc.loyalty_amount != loyalty_amount:
 			ref_doc.loyalty_amount = loyalty_amount
diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.js b/erpnext/accounts/doctype/payment_entry/payment_entry.js
index 33f2634..f131be2 100644
--- a/erpnext/accounts/doctype/payment_entry/payment_entry.js
+++ b/erpnext/accounts/doctype/payment_entry/payment_entry.js
@@ -9,7 +9,7 @@
 
 frappe.ui.form.on('Payment Entry', {
 	onload: function(frm) {
-		frm.ignore_doctypes_on_cancel_all = ['Sales Invoice', 'Purchase Invoice', "Journal Entry", "Repost Payment Ledger"];
+		frm.ignore_doctypes_on_cancel_all = ['Sales Invoice', 'Purchase Invoice', 'Journal Entry', 'Repost Payment Ledger','Repost Accounting Ledger'];
 
 		if(frm.doc.__islocal) {
 			if (!frm.doc.paid_from) frm.set_value("paid_from_account_currency", null);
diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py
index 69ce19c..ac31e8a 100644
--- a/erpnext/accounts/doctype/payment_entry/payment_entry.py
+++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py
@@ -71,7 +71,7 @@
 		self.setup_party_account_field()
 		self.set_missing_values()
 		self.set_liability_account()
-		self.set_missing_ref_details()
+		self.set_missing_ref_details(force=True)
 		self.validate_payment_type()
 		self.validate_party_details()
 		self.set_exchange_rate()
@@ -147,6 +147,8 @@
 			"Payment Ledger Entry",
 			"Repost Payment Ledger",
 			"Repost Payment Ledger Items",
+			"Repost Accounting Ledger",
+			"Repost Accounting Ledger Items",
 		)
 		super(PaymentEntry, self).on_cancel()
 		self.make_gl_entries(cancel=1)
@@ -228,84 +230,88 @@
 		return False
 
 	def validate_allocated_amount_with_latest_data(self):
-		latest_references = get_outstanding_reference_documents(
-			{
-				"posting_date": self.posting_date,
-				"company": self.company,
-				"party_type": self.party_type,
-				"payment_type": self.payment_type,
-				"party": self.party,
-				"party_account": self.paid_from if self.payment_type == "Receive" else self.paid_to,
-				"get_outstanding_invoices": True,
-				"get_orders_to_be_billed": True,
-			},
-			validate=True,
-		)
+		if self.references:
+			uniq_vouchers = set([(x.reference_doctype, x.reference_name) for x in self.references])
+			vouchers = [frappe._dict({"voucher_type": x[0], "voucher_no": x[1]}) for x in uniq_vouchers]
+			latest_references = get_outstanding_reference_documents(
+				{
+					"posting_date": self.posting_date,
+					"company": self.company,
+					"party_type": self.party_type,
+					"payment_type": self.payment_type,
+					"party": self.party,
+					"party_account": self.paid_from if self.payment_type == "Receive" else self.paid_to,
+					"get_outstanding_invoices": True,
+					"get_orders_to_be_billed": True,
+					"vouchers": vouchers,
+				},
+				validate=True,
+			)
 
-		# Group latest_references by (voucher_type, voucher_no)
-		latest_lookup = {}
-		for d in latest_references:
-			d = frappe._dict(d)
-			latest_lookup.setdefault((d.voucher_type, d.voucher_no), frappe._dict())[d.payment_term] = d
+			# Group latest_references by (voucher_type, voucher_no)
+			latest_lookup = {}
+			for d in latest_references:
+				d = frappe._dict(d)
+				latest_lookup.setdefault((d.voucher_type, d.voucher_no), frappe._dict())[d.payment_term] = d
 
-		for idx, d in enumerate(self.get("references"), start=1):
-			latest = latest_lookup.get((d.reference_doctype, d.reference_name)) or frappe._dict()
+			for idx, d in enumerate(self.get("references"), start=1):
+				latest = latest_lookup.get((d.reference_doctype, d.reference_name)) or frappe._dict()
 
-			# If term based allocation is enabled, throw
-			if (
-				d.payment_term is None or d.payment_term == ""
-			) and self.term_based_allocation_enabled_for_reference(
-				d.reference_doctype, d.reference_name
-			):
-				frappe.throw(
-					_(
-						"{0} has Payment Term based allocation enabled. Select a Payment Term for Row #{1} in Payment References section"
-					).format(frappe.bold(d.reference_name), frappe.bold(idx))
-				)
-
-			# if no payment template is used by invoice and has a custom term(no `payment_term`), then invoice outstanding will be in 'None' key
-			latest = latest.get(d.payment_term) or latest.get(None)
-
-			# The reference has already been fully paid
-			if not latest:
-				frappe.throw(
-					_("{0} {1} has already been fully paid.").format(_(d.reference_doctype), d.reference_name)
-				)
-			# The reference has already been partly paid
-			elif latest.outstanding_amount < latest.invoice_amount and flt(
-				d.outstanding_amount, d.precision("outstanding_amount")
-			) != flt(latest.outstanding_amount, d.precision("outstanding_amount")):
-				frappe.throw(
-					_(
-						"{0} {1} has already been partly paid. Please use the 'Get Outstanding Invoice' or the 'Get Outstanding Orders' button to get the latest outstanding amounts."
-					).format(_(d.reference_doctype), d.reference_name)
-				)
-
-			fail_message = _("Row #{0}: Allocated Amount cannot be greater than outstanding amount.")
-
-			if (
-				d.payment_term
-				and (
-					(flt(d.allocated_amount)) > 0
-					and latest.payment_term_outstanding
-					and (flt(d.allocated_amount) > flt(latest.payment_term_outstanding))
-				)
-				and self.term_based_allocation_enabled_for_reference(d.reference_doctype, d.reference_name)
-			):
-				frappe.throw(
-					_(
-						"Row #{0}: Allocated amount:{1} is greater than outstanding amount:{2} for Payment Term {3}"
-					).format(
-						d.idx, d.allocated_amount, latest.payment_term_outstanding, d.payment_term
+				# If term based allocation is enabled, throw
+				if (
+					d.payment_term is None or d.payment_term == ""
+				) and self.term_based_allocation_enabled_for_reference(
+					d.reference_doctype, d.reference_name
+				):
+					frappe.throw(
+						_(
+							"{0} has Payment Term based allocation enabled. Select a Payment Term for Row #{1} in Payment References section"
+						).format(frappe.bold(d.reference_name), frappe.bold(idx))
 					)
-				)
 
-			if (flt(d.allocated_amount)) > 0 and flt(d.allocated_amount) > flt(latest.outstanding_amount):
-				frappe.throw(fail_message.format(d.idx))
+				# if no payment template is used by invoice and has a custom term(no `payment_term`), then invoice outstanding will be in 'None' key
+				latest = latest.get(d.payment_term) or latest.get(None)
 
-			# Check for negative outstanding invoices as well
-			if flt(d.allocated_amount) < 0 and flt(d.allocated_amount) < flt(latest.outstanding_amount):
-				frappe.throw(fail_message.format(d.idx))
+				# The reference has already been fully paid
+				if not latest:
+					frappe.throw(
+						_("{0} {1} has already been fully paid.").format(_(d.reference_doctype), d.reference_name)
+					)
+				# The reference has already been partly paid
+				elif latest.outstanding_amount < latest.invoice_amount and flt(
+					d.outstanding_amount, d.precision("outstanding_amount")
+				) != flt(latest.outstanding_amount, d.precision("outstanding_amount")):
+					frappe.throw(
+						_(
+							"{0} {1} has already been partly paid. Please use the 'Get Outstanding Invoice' or the 'Get Outstanding Orders' button to get the latest outstanding amounts."
+						).format(_(d.reference_doctype), d.reference_name)
+					)
+
+				fail_message = _("Row #{0}: Allocated Amount cannot be greater than outstanding amount.")
+
+				if (
+					d.payment_term
+					and (
+						(flt(d.allocated_amount)) > 0
+						and latest.payment_term_outstanding
+						and (flt(d.allocated_amount) > flt(latest.payment_term_outstanding))
+					)
+					and self.term_based_allocation_enabled_for_reference(d.reference_doctype, d.reference_name)
+				):
+					frappe.throw(
+						_(
+							"Row #{0}: Allocated amount:{1} is greater than outstanding amount:{2} for Payment Term {3}"
+						).format(
+							d.idx, d.allocated_amount, latest.payment_term_outstanding, d.payment_term
+						)
+					)
+
+				if (flt(d.allocated_amount)) > 0 and flt(d.allocated_amount) > flt(latest.outstanding_amount):
+					frappe.throw(fail_message.format(d.idx))
+
+				# Check for negative outstanding invoices as well
+				if flt(d.allocated_amount) < 0 and flt(d.allocated_amount) < flt(latest.outstanding_amount):
+					frappe.throw(fail_message.format(d.idx))
 
 	def delink_advance_entry_references(self):
 		for reference in self.references:
@@ -688,7 +694,9 @@
 		if not self.apply_tax_withholding_amount:
 			return
 
-		net_total = self.paid_amount
+		order_amount = self.get_order_net_total()
+
+		net_total = flt(order_amount) + flt(self.unallocated_amount)
 
 		# Adding args as purchase invoice to get TDS amount
 		args = frappe._dict(
@@ -733,6 +741,20 @@
 		for d in to_remove:
 			self.remove(d)
 
+	def get_order_net_total(self):
+		if self.party_type == "Supplier":
+			doctype = "Purchase Order"
+		else:
+			doctype = "Sales Order"
+
+		docnames = [d.reference_name for d in self.references if d.reference_doctype == doctype]
+
+		tax_withholding_net_total = frappe.db.get_value(
+			doctype, {"name": ["in", docnames]}, ["sum(base_tax_withholding_net_total)"]
+		)
+
+		return tax_withholding_net_total
+
 	def apply_taxes(self):
 		self.initialize_taxes()
 		self.determine_exclusive_rate()
@@ -1569,6 +1591,7 @@
 			min_outstanding=args.get("outstanding_amt_greater_than"),
 			max_outstanding=args.get("outstanding_amt_less_than"),
 			accounting_dimensions=accounting_dimensions_filter,
+			vouchers=args.get("vouchers") or None,
 		)
 
 		outstanding_invoices = split_invoices_based_on_payment_terms(
diff --git a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py
index ea06e0e..3a9e80a 100644
--- a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py
+++ b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py
@@ -6,7 +6,7 @@
 from frappe import _, msgprint, qb
 from frappe.model.document import Document
 from frappe.query_builder.custom import ConstantColumn
-from frappe.utils import flt, get_link_to_form, getdate, nowdate, today
+from frappe.utils import flt, fmt_money, get_link_to_form, getdate, nowdate, today
 
 import erpnext
 from erpnext.accounts.doctype.process_payment_reconciliation.process_payment_reconciliation import (
@@ -657,6 +657,7 @@
 						"reference_name": inv.against_voucher,
 						"cost_center": erpnext.get_default_cost_center(company),
 						"exchange_rate": inv.exchange_rate,
+						"user_remark": f"{fmt_money(flt(inv.allocated_amount), currency=company_currency)} against {inv.against_voucher}",
 					},
 					{
 						"account": inv.account,
@@ -671,6 +672,7 @@
 						"reference_name": inv.voucher_no,
 						"cost_center": erpnext.get_default_cost_center(company),
 						"exchange_rate": inv.exchange_rate,
+						"user_remark": f"{fmt_money(flt(inv.allocated_amount), currency=company_currency)} from {inv.voucher_no}",
 					},
 				],
 			}
@@ -678,6 +680,9 @@
 
 		jv.flags.ignore_mandatory = True
 		jv.flags.ignore_exchange_rate = True
+		jv.remark = None
+		jv.flags.skip_remarks_creation = True
+		jv.is_system_generated = True
 		jv.submit()
 
 		if inv.difference_amount != 0:
diff --git a/erpnext/accounts/doctype/pos_closing_entry/pos_closing_entry.js b/erpnext/accounts/doctype/pos_closing_entry/pos_closing_entry.js
index 8eed573..faceaf3 100644
--- a/erpnext/accounts/doctype/pos_closing_entry/pos_closing_entry.js
+++ b/erpnext/accounts/doctype/pos_closing_entry/pos_closing_entry.js
@@ -153,7 +153,7 @@
 frappe.ui.form.on('POS Closing Entry Detail', {
 	closing_amount: (frm, cdt, cdn) => {
 		const row = locals[cdt][cdn];
-		frappe.model.set_value(cdt, cdn, "difference", flt(row.expected_amount - row.closing_amount));
+		frappe.model.set_value(cdt, cdn, "difference", flt(row.closing_amount - row.expected_amount));
 	}
 })
 
diff --git a/erpnext/accounts/doctype/process_payment_reconciliation/process_payment_reconciliation.json b/erpnext/accounts/doctype/process_payment_reconciliation/process_payment_reconciliation.json
index 8bb7092..1a1ab4d 100644
--- a/erpnext/accounts/doctype/process_payment_reconciliation/process_payment_reconciliation.json
+++ b/erpnext/accounts/doctype/process_payment_reconciliation/process_payment_reconciliation.json
@@ -146,7 +146,7 @@
  "index_web_pages_for_search": 1,
  "is_submittable": 1,
  "links": [],
- "modified": "2023-04-21 17:19:30.912953",
+ "modified": "2023-08-11 10:56:51.699137",
  "modified_by": "Administrator",
  "module": "Accounts",
  "name": "Process Payment Reconciliation",
@@ -154,15 +154,25 @@
  "owner": "Administrator",
  "permissions": [
   {
+   "amend": 1,
+   "cancel": 1,
    "create": 1,
    "delete": 1,
-   "email": 1,
-   "export": 1,
-   "print": 1,
    "read": 1,
-   "report": 1,
-   "role": "System Manager",
+   "role": "Accounts Manager",
    "share": 1,
+   "submit": 1,
+   "write": 1
+  },
+  {
+   "amend": 1,
+   "cancel": 1,
+   "create": 1,
+   "delete": 1,
+   "read": 1,
+   "role": "Accounts User",
+   "share": 1,
+   "submit": 1,
    "write": 1
   }
  ],
diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js
index 89d6207..66438a7 100644
--- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js
+++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js
@@ -35,7 +35,7 @@
 		super.onload();
 
 		// Ignore linked advances
-		this.frm.ignore_doctypes_on_cancel_all = ['Journal Entry', 'Payment Entry', 'Purchase Invoice', "Repost Payment Ledger"];
+		this.frm.ignore_doctypes_on_cancel_all = ['Journal Entry', 'Payment Entry', 'Purchase Invoice', "Repost Payment Ledger", "Repost Accounting Ledger"];
 
 		if(!this.frm.doc.__islocal) {
 			// show credit_to in print format
diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json
index d8759e9..0599e19 100644
--- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json
+++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json
@@ -167,6 +167,7 @@
   "column_break_63",
   "unrealized_profit_loss_account",
   "subscription_section",
+  "subscription",
   "auto_repeat",
   "update_auto_repeat_reference",
   "column_break_114",
@@ -1424,6 +1425,12 @@
    "read_only": 1
   },
   {
+   "fieldname": "subscription",
+   "fieldtype": "Link",
+   "label": "Subscription",
+   "options": "Subscription"
+  },
+  {
    "default": "0",
    "fieldname": "is_old_subcontracting_flow",
    "fieldtype": "Check",
@@ -1577,7 +1584,7 @@
  "idx": 204,
  "is_submittable": 1,
  "links": [],
- "modified": "2023-07-04 17:22:59.145031",
+ "modified": "2023-07-25 17:22:59.145031",
  "modified_by": "Administrator",
  "module": "Accounts",
  "name": "Purchase Invoice",
diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py
index d175df5..f334399 100644
--- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py
+++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py
@@ -969,30 +969,6 @@
 							item.item_tax_amount, item.precision("item_tax_amount")
 						)
 
-	def make_precision_loss_gl_entry(self, gl_entries):
-		round_off_account, round_off_cost_center = get_round_off_account_and_cost_center(
-			self.company, "Purchase Invoice", self.name, self.use_company_roundoff_cost_center
-		)
-
-		precision_loss = self.get("base_net_total") - flt(
-			self.get("net_total") * self.conversion_rate, self.precision("net_total")
-		)
-
-		if precision_loss:
-			gl_entries.append(
-				self.get_gl_dict(
-					{
-						"account": round_off_account,
-						"against": self.supplier,
-						"credit": precision_loss,
-						"cost_center": round_off_cost_center
-						if self.use_company_roundoff_cost_center
-						else self.cost_center or round_off_cost_center,
-						"remarks": _("Net total calculation precision loss"),
-					}
-				)
-			)
-
 	def get_asset_gl_entry(self, gl_entries):
 		arbnb_account = self.get_company_default("asset_received_but_not_billed")
 		eiiav_account = self.get_company_default("expenses_included_in_asset_valuation")
@@ -1439,6 +1415,8 @@
 			"Repost Item Valuation",
 			"Repost Payment Ledger",
 			"Repost Payment Ledger Items",
+			"Repost Accounting Ledger",
+			"Repost Accounting Ledger Items",
 			"Payment Ledger Entry",
 			"Tax Withheld Vouchers",
 			"Serial and Batch Bundle",
diff --git a/erpnext/accounts/doctype/repost_accounting_ledger/__init__.py b/erpnext/accounts/doctype/repost_accounting_ledger/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/erpnext/accounts/doctype/repost_accounting_ledger/__init__.py
diff --git a/erpnext/accounts/doctype/repost_accounting_ledger/repost_accounting_ledger.html b/erpnext/accounts/doctype/repost_accounting_ledger/repost_accounting_ledger.html
new file mode 100644
index 0000000..2dec8f7
--- /dev/null
+++ b/erpnext/accounts/doctype/repost_accounting_ledger/repost_accounting_ledger.html
@@ -0,0 +1,44 @@
+<style>
+	.print-format {
+		padding: 4mm;
+		font-size: 8.0pt !important;
+	}
+	.print-format td {
+		vertical-align:middle !important;
+	}
+	.old {
+	    background-color: #FFB3C0;
+	}
+	.new {
+	    background-color: #B3FFCC;
+	}
+</style>
+
+
+<table class="table table-bordered table-condensed">
+  <colgroup>
+  {% for col in gl_columns%}
+  <col style="width: 18mm;">
+  {% endfor %}
+  </colgroup>
+  <thead>
+    <tr>
+    {% for col in gl_columns%}
+    <td>{{ col.label }}</td>
+    {% endfor %}
+    </tr>
+  </thead>
+{% for gl in gl_data%}
+{% if gl["old"]%}
+<tr class="old">
+{% else %}
+<tr class="new">
+{% endif %}
+  {% for col in gl_columns %}
+  <td class="text-right">
+    {{ gl[col.fieldname] }}
+  </td>
+  {% endfor %}
+</tr>
+{% endfor %}
+</table>
diff --git a/erpnext/accounts/doctype/repost_accounting_ledger/repost_accounting_ledger.js b/erpnext/accounts/doctype/repost_accounting_ledger/repost_accounting_ledger.js
new file mode 100644
index 0000000..3a87a38
--- /dev/null
+++ b/erpnext/accounts/doctype/repost_accounting_ledger/repost_accounting_ledger.js
@@ -0,0 +1,50 @@
+// Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and contributors
+// For license information, please see license.txt
+
+frappe.ui.form.on("Repost Accounting Ledger", {
+	setup: function(frm) {
+		frm.fields_dict['vouchers'].grid.get_field('voucher_type').get_query = function(doc) {
+			return {
+				filters: {
+					name: ['in', ['Purchase Invoice', 'Sales Invoice', 'Payment Entry', 'Journal Entry']],
+				}
+			}
+		}
+
+		frm.fields_dict['vouchers'].grid.get_field('voucher_no').get_query = function(doc) {
+			if (doc.company) {
+				return {
+					filters: {
+						company: doc.company,
+						docstatus: 1
+					}
+				}
+			}
+		}
+	},
+
+	refresh: function(frm) {
+		frm.add_custom_button(__('Show Preview'), () => {
+			frm.call({
+				method: 'generate_preview',
+				doc: frm.doc,
+				freeze: true,
+				freeze_message: __('Generating Preview'),
+				callback: function(r) {
+					if (r && r.message) {
+						let content = r.message;
+						let opts = {
+							title: "Preview",
+							subtitle: "preview",
+							content: content,
+							print_settings: {orientation: "landscape"},
+							columns: [],
+							data: [],
+						}
+						frappe.render_grid(opts);
+					}
+				}
+			});
+		});
+	}
+});
diff --git a/erpnext/accounts/doctype/repost_accounting_ledger/repost_accounting_ledger.json b/erpnext/accounts/doctype/repost_accounting_ledger/repost_accounting_ledger.json
new file mode 100644
index 0000000..8d56c9b
--- /dev/null
+++ b/erpnext/accounts/doctype/repost_accounting_ledger/repost_accounting_ledger.json
@@ -0,0 +1,81 @@
+{
+ "actions": [],
+ "allow_rename": 1,
+ "autoname": "format:ACC-REPOST-{#####}",
+ "creation": "2023-07-04 13:07:32.923675",
+ "default_view": "List",
+ "doctype": "DocType",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+  "company",
+  "column_break_vpup",
+  "delete_cancelled_entries",
+  "section_break_metl",
+  "vouchers",
+  "amended_from"
+ ],
+ "fields": [
+  {
+   "fieldname": "company",
+   "fieldtype": "Link",
+   "label": "Company",
+   "options": "Company"
+  },
+  {
+   "fieldname": "amended_from",
+   "fieldtype": "Link",
+   "label": "Amended From",
+   "no_copy": 1,
+   "options": "Repost Accounting Ledger",
+   "print_hide": 1,
+   "read_only": 1
+  },
+  {
+   "fieldname": "vouchers",
+   "fieldtype": "Table",
+   "label": "Vouchers",
+   "options": "Repost Accounting Ledger Items"
+  },
+  {
+   "fieldname": "column_break_vpup",
+   "fieldtype": "Column Break"
+  },
+  {
+   "fieldname": "section_break_metl",
+   "fieldtype": "Section Break"
+  },
+  {
+   "default": "0",
+   "fieldname": "delete_cancelled_entries",
+   "fieldtype": "Check",
+   "label": "Delete Cancelled Ledger Entries"
+  }
+ ],
+ "index_web_pages_for_search": 1,
+ "is_submittable": 1,
+ "links": [],
+ "modified": "2023-07-27 15:47:58.975034",
+ "modified_by": "Administrator",
+ "module": "Accounts",
+ "name": "Repost Accounting Ledger",
+ "naming_rule": "Expression",
+ "owner": "Administrator",
+ "permissions": [
+  {
+   "create": 1,
+   "delete": 1,
+   "email": 1,
+   "export": 1,
+   "print": 1,
+   "read": 1,
+   "report": 1,
+   "role": "System Manager",
+   "share": 1,
+   "write": 1
+  }
+ ],
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "states": []
+}
\ No newline at end of file
diff --git a/erpnext/accounts/doctype/repost_accounting_ledger/repost_accounting_ledger.py b/erpnext/accounts/doctype/repost_accounting_ledger/repost_accounting_ledger.py
new file mode 100644
index 0000000..4cf2ed2
--- /dev/null
+++ b/erpnext/accounts/doctype/repost_accounting_ledger/repost_accounting_ledger.py
@@ -0,0 +1,183 @@
+# Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and contributors
+# For license information, please see license.txt
+
+import frappe
+from frappe import _, qb
+from frappe.model.document import Document
+from frappe.utils.data import comma_and
+
+
+class RepostAccountingLedger(Document):
+	def __init__(self, *args, **kwargs):
+		super(RepostAccountingLedger, self).__init__(*args, **kwargs)
+		self._allowed_types = set(
+			["Purchase Invoice", "Sales Invoice", "Payment Entry", "Journal Entry"]
+		)
+
+	def validate(self):
+		self.validate_vouchers()
+		self.validate_for_closed_fiscal_year()
+		self.validate_for_deferred_accounting()
+
+	def validate_for_deferred_accounting(self):
+		sales_docs = [x.voucher_no for x in self.vouchers if x.voucher_type == "Sales Invoice"]
+		docs_with_deferred_revenue = frappe.db.get_all(
+			"Sales Invoice Item",
+			filters={"parent": ["in", sales_docs], "docstatus": 1, "enable_deferred_revenue": True},
+			fields=["parent"],
+			as_list=1,
+		)
+
+		purchase_docs = [x.voucher_no for x in self.vouchers if x.voucher_type == "Purchase Invoice"]
+		docs_with_deferred_expense = frappe.db.get_all(
+			"Purchase Invoice Item",
+			filters={"parent": ["in", purchase_docs], "docstatus": 1, "enable_deferred_expense": 1},
+			fields=["parent"],
+			as_list=1,
+		)
+
+		if docs_with_deferred_revenue or docs_with_deferred_expense:
+			frappe.throw(
+				_("Documents: {0} have deferred revenue/expense enabled for them. Cannot repost.").format(
+					frappe.bold(
+						comma_and([x[0] for x in docs_with_deferred_expense + docs_with_deferred_revenue])
+					)
+				)
+			)
+
+	def validate_for_closed_fiscal_year(self):
+		if self.vouchers:
+			latest_pcv = (
+				frappe.db.get_all(
+					"Period Closing Voucher",
+					filters={"company": self.company},
+					order_by="posting_date desc",
+					pluck="posting_date",
+					limit=1,
+				)
+				or None
+			)
+			if not latest_pcv:
+				return
+
+			for vtype in self._allowed_types:
+				if names := [x.voucher_no for x in self.vouchers if x.voucher_type == vtype]:
+					latest_voucher = frappe.db.get_all(
+						vtype,
+						filters={"name": ["in", names]},
+						pluck="posting_date",
+						order_by="posting_date desc",
+						limit=1,
+					)[0]
+					if latest_voucher and latest_pcv[0] >= latest_voucher:
+						frappe.throw(_("Cannot Resubmit Ledger entries for vouchers in Closed fiscal year."))
+
+	def validate_vouchers(self):
+		if self.vouchers:
+			# Validate voucher types
+			voucher_types = set([x.voucher_type for x in self.vouchers])
+			if disallowed_types := voucher_types.difference(self._allowed_types):
+				frappe.throw(
+					_("{0} types are not allowed. Only {1} are.").format(
+						frappe.bold(comma_and(list(disallowed_types))),
+						frappe.bold(comma_and(list(self._allowed_types))),
+					)
+				)
+
+	def get_existing_ledger_entries(self):
+		vouchers = [x.voucher_no for x in self.vouchers]
+		gl = qb.DocType("GL Entry")
+		existing_gles = (
+			qb.from_(gl)
+			.select(gl.star)
+			.where((gl.voucher_no.isin(vouchers)) & (gl.is_cancelled == 0))
+			.run(as_dict=True)
+		)
+		self.gles = frappe._dict({})
+
+		for gle in existing_gles:
+			self.gles.setdefault((gle.voucher_type, gle.voucher_no), frappe._dict({})).setdefault(
+				"existing", []
+			).append(gle.update({"old": True}))
+
+	def generate_preview_data(self):
+		self.gl_entries = []
+		self.get_existing_ledger_entries()
+		for x in self.vouchers:
+			doc = frappe.get_doc(x.voucher_type, x.voucher_no)
+			if doc.doctype in ["Payment Entry", "Journal Entry"]:
+				gle_map = doc.build_gl_map()
+			else:
+				gle_map = doc.get_gl_entries()
+
+			old_entries = self.gles.get((x.voucher_type, x.voucher_no))
+			if old_entries:
+				self.gl_entries.extend(old_entries.existing)
+			self.gl_entries.extend(gle_map)
+
+	@frappe.whitelist()
+	def generate_preview(self):
+		from erpnext.accounts.report.general_ledger.general_ledger import get_columns as get_gl_columns
+
+		gl_columns = []
+		gl_data = []
+
+		self.generate_preview_data()
+		if self.gl_entries:
+			filters = {"company": self.company, "include_dimensions": 1}
+			for x in get_gl_columns(filters):
+				if x["fieldname"] == "gl_entry":
+					x["fieldname"] = "name"
+				gl_columns.append(x)
+
+			gl_data = self.gl_entries
+		rendered_page = frappe.render_template(
+			"erpnext/accounts/doctype/repost_accounting_ledger/repost_accounting_ledger.html",
+			{"gl_columns": gl_columns, "gl_data": gl_data},
+		)
+
+		return rendered_page
+
+	def on_submit(self):
+		job_name = "repost_accounting_ledger_" + self.name
+		frappe.enqueue(
+			method="erpnext.accounts.doctype.repost_accounting_ledger.repost_accounting_ledger.start_repost",
+			account_repost_doc=self.name,
+			is_async=True,
+			job_name=job_name,
+		)
+		frappe.msgprint(_("Repost has started in the background"))
+
+
+@frappe.whitelist()
+def start_repost(account_repost_doc=str) -> None:
+	if account_repost_doc:
+		repost_doc = frappe.get_doc("Repost Accounting Ledger", account_repost_doc)
+
+		if repost_doc.docstatus == 1:
+			# Prevent repost on invoices with deferred accounting
+			repost_doc.validate_for_deferred_accounting()
+
+			for x in repost_doc.vouchers:
+				doc = frappe.get_doc(x.voucher_type, x.voucher_no)
+
+				if repost_doc.delete_cancelled_entries:
+					frappe.db.delete("GL Entry", filters={"voucher_type": doc.doctype, "voucher_no": doc.name})
+					frappe.db.delete(
+						"Payment Ledger Entry", filters={"voucher_type": doc.doctype, "voucher_no": doc.name}
+					)
+
+				if doc.doctype in ["Sales Invoice", "Purchase Invoice"]:
+					if not repost_doc.delete_cancelled_entries:
+						doc.docstatus = 2
+						doc.make_gl_entries_on_cancel()
+
+					doc.docstatus = 1
+					doc.make_gl_entries()
+
+				elif doc.doctype in ["Payment Entry", "Journal Entry"]:
+					if not repost_doc.delete_cancelled_entries:
+						doc.make_gl_entries(1)
+					doc.make_gl_entries()
+
+				frappe.db.commit()
diff --git a/erpnext/accounts/doctype/repost_accounting_ledger/test_repost_accounting_ledger.py b/erpnext/accounts/doctype/repost_accounting_ledger/test_repost_accounting_ledger.py
new file mode 100644
index 0000000..0e75dd2
--- /dev/null
+++ b/erpnext/accounts/doctype/repost_accounting_ledger/test_repost_accounting_ledger.py
@@ -0,0 +1,202 @@
+# Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and Contributors
+# See license.txt
+
+import frappe
+from frappe import qb
+from frappe.query_builder.functions import Sum
+from frappe.tests.utils import FrappeTestCase, change_settings
+from frappe.utils import add_days, nowdate, today
+
+from erpnext.accounts.doctype.payment_entry.payment_entry import get_payment_entry
+from erpnext.accounts.doctype.payment_request.payment_request import make_payment_request
+from erpnext.accounts.doctype.repost_accounting_ledger.repost_accounting_ledger import start_repost
+from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice
+from erpnext.accounts.test.accounts_mixin import AccountsTestMixin
+from erpnext.accounts.utils import get_fiscal_year
+
+
+class TestRepostAccountingLedger(AccountsTestMixin, FrappeTestCase):
+	def setUp(self):
+		self.create_company()
+		self.create_customer()
+		self.create_item()
+
+	def teadDown(self):
+		frappe.db.rollback()
+
+	def test_01_basic_functions(self):
+		si = create_sales_invoice(
+			item=self.item,
+			company=self.company,
+			customer=self.customer,
+			debit_to=self.debit_to,
+			parent_cost_center=self.cost_center,
+			cost_center=self.cost_center,
+			rate=100,
+		)
+
+		preq = frappe.get_doc(
+			make_payment_request(
+				dt=si.doctype,
+				dn=si.name,
+				payment_request_type="Inward",
+				party_type="Customer",
+				party=si.customer,
+			)
+		)
+		preq.save().submit()
+
+		# Test Validation Error
+		ral = frappe.new_doc("Repost Accounting Ledger")
+		ral.company = self.company
+		ral.delete_cancelled_entries = True
+		ral.append("vouchers", {"voucher_type": si.doctype, "voucher_no": si.name})
+		ral.append(
+			"vouchers", {"voucher_type": preq.doctype, "voucher_no": preq.name}
+		)  # this should throw validation error
+		self.assertRaises(frappe.ValidationError, ral.save)
+		ral.vouchers.pop()
+		preq.cancel()
+		preq.delete()
+
+		pe = get_payment_entry(si.doctype, si.name)
+		pe.save().submit()
+		ral.append("vouchers", {"voucher_type": pe.doctype, "voucher_no": pe.name})
+		ral.save()
+
+		# manually set an incorrect debit amount in DB
+		gle = frappe.db.get_all("GL Entry", filters={"voucher_no": si.name, "account": self.debit_to})
+		frappe.db.set_value("GL Entry", gle[0], "debit", 90)
+
+		gl = qb.DocType("GL Entry")
+		res = (
+			qb.from_(gl)
+			.select(gl.voucher_no, Sum(gl.debit).as_("debit"), Sum(gl.credit).as_("credit"))
+			.where((gl.voucher_no == si.name) & (gl.is_cancelled == 0))
+			.run()
+		)
+
+		# Assert incorrect ledger balance
+		self.assertNotEqual(res[0], (si.name, 100, 100))
+
+		# Submit repost document
+		ral.save().submit()
+
+		# background jobs don't run on test cases. Manually triggering repost function.
+		start_repost(ral.name)
+
+		res = (
+			qb.from_(gl)
+			.select(gl.voucher_no, Sum(gl.debit).as_("debit"), Sum(gl.credit).as_("credit"))
+			.where((gl.voucher_no == si.name) & (gl.is_cancelled == 0))
+			.run()
+		)
+
+		# Ledger should reflect correct amount post repost
+		self.assertEqual(res[0], (si.name, 100, 100))
+
+	def test_02_deferred_accounting_valiations(self):
+		si = create_sales_invoice(
+			item=self.item,
+			company=self.company,
+			customer=self.customer,
+			debit_to=self.debit_to,
+			parent_cost_center=self.cost_center,
+			cost_center=self.cost_center,
+			rate=100,
+			do_not_submit=True,
+		)
+		si.items[0].enable_deferred_revenue = True
+		si.items[0].deferred_revenue_account = self.deferred_revenue
+		si.items[0].service_start_date = nowdate()
+		si.items[0].service_end_date = add_days(nowdate(), 90)
+		si.save().submit()
+
+		ral = frappe.new_doc("Repost Accounting Ledger")
+		ral.company = self.company
+		ral.append("vouchers", {"voucher_type": si.doctype, "voucher_no": si.name})
+		self.assertRaises(frappe.ValidationError, ral.save)
+
+	@change_settings("Accounts Settings", {"delete_linked_ledger_entries": 1})
+	def test_04_pcv_validation(self):
+		# Clear old GL entries so PCV can be submitted.
+		gl = frappe.qb.DocType("GL Entry")
+		qb.from_(gl).delete().where(gl.company == self.company).run()
+
+		si = create_sales_invoice(
+			item=self.item,
+			company=self.company,
+			customer=self.customer,
+			debit_to=self.debit_to,
+			parent_cost_center=self.cost_center,
+			cost_center=self.cost_center,
+			rate=100,
+		)
+		pcv = frappe.get_doc(
+			{
+				"doctype": "Period Closing Voucher",
+				"transaction_date": today(),
+				"posting_date": today(),
+				"company": self.company,
+				"fiscal_year": get_fiscal_year(today(), company=self.company)[0],
+				"cost_center": self.cost_center,
+				"closing_account_head": self.retained_earnings,
+				"remarks": "test",
+			}
+		)
+		pcv.save().submit()
+
+		ral = frappe.new_doc("Repost Accounting Ledger")
+		ral.company = self.company
+		ral.append("vouchers", {"voucher_type": si.doctype, "voucher_no": si.name})
+		self.assertRaises(frappe.ValidationError, ral.save)
+
+		pcv.reload()
+		pcv.cancel()
+		pcv.delete()
+
+	def test_03_deletion_flag_and_preview_function(self):
+		si = create_sales_invoice(
+			item=self.item,
+			company=self.company,
+			customer=self.customer,
+			debit_to=self.debit_to,
+			parent_cost_center=self.cost_center,
+			cost_center=self.cost_center,
+			rate=100,
+		)
+
+		pe = get_payment_entry(si.doctype, si.name)
+		pe.save().submit()
+
+		# without deletion flag set
+		ral = frappe.new_doc("Repost Accounting Ledger")
+		ral.company = self.company
+		ral.delete_cancelled_entries = False
+		ral.append("vouchers", {"voucher_type": si.doctype, "voucher_no": si.name})
+		ral.append("vouchers", {"voucher_type": pe.doctype, "voucher_no": pe.name})
+		ral.save()
+
+		# assert preview data is generated
+		preview = ral.generate_preview()
+		self.assertIsNotNone(preview)
+
+		ral.save().submit()
+
+		# background jobs don't run on test cases. Manually triggering repost function.
+		start_repost(ral.name)
+
+		self.assertIsNotNone(frappe.db.exists("GL Entry", {"voucher_no": si.name, "is_cancelled": 1}))
+		self.assertIsNotNone(frappe.db.exists("GL Entry", {"voucher_no": pe.name, "is_cancelled": 1}))
+
+		# with deletion flag set
+		ral = frappe.new_doc("Repost Accounting Ledger")
+		ral.company = self.company
+		ral.delete_cancelled_entries = True
+		ral.append("vouchers", {"voucher_type": si.doctype, "voucher_no": si.name})
+		ral.append("vouchers", {"voucher_type": pe.doctype, "voucher_no": pe.name})
+		ral.save().submit()
+
+		start_repost(ral.name)
+		self.assertIsNone(frappe.db.exists("GL Entry", {"voucher_no": si.name, "is_cancelled": 1}))
+		self.assertIsNone(frappe.db.exists("GL Entry", {"voucher_no": pe.name, "is_cancelled": 1}))
diff --git a/erpnext/accounts/doctype/repost_accounting_ledger_items/__init__.py b/erpnext/accounts/doctype/repost_accounting_ledger_items/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/erpnext/accounts/doctype/repost_accounting_ledger_items/__init__.py
diff --git a/erpnext/accounts/doctype/repost_accounting_ledger_items/repost_accounting_ledger_items.json b/erpnext/accounts/doctype/repost_accounting_ledger_items/repost_accounting_ledger_items.json
new file mode 100644
index 0000000..4a2041f
--- /dev/null
+++ b/erpnext/accounts/doctype/repost_accounting_ledger_items/repost_accounting_ledger_items.json
@@ -0,0 +1,40 @@
+{
+ "actions": [],
+ "allow_rename": 1,
+ "creation": "2023-07-04 14:14:01.243848",
+ "doctype": "DocType",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+  "voucher_type",
+  "voucher_no"
+ ],
+ "fields": [
+  {
+   "fieldname": "voucher_type",
+   "fieldtype": "Link",
+   "in_list_view": 1,
+   "label": "Voucher Type",
+   "options": "DocType"
+  },
+  {
+   "fieldname": "voucher_no",
+   "fieldtype": "Dynamic Link",
+   "in_list_view": 1,
+   "label": "Voucher No",
+   "options": "voucher_type"
+  }
+ ],
+ "index_web_pages_for_search": 1,
+ "istable": 1,
+ "links": [],
+ "modified": "2023-07-04 14:15:51.165584",
+ "modified_by": "Administrator",
+ "module": "Accounts",
+ "name": "Repost Accounting Ledger Items",
+ "owner": "Administrator",
+ "permissions": [],
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "states": []
+}
\ No newline at end of file
diff --git a/erpnext/accounts/doctype/repost_accounting_ledger_items/repost_accounting_ledger_items.py b/erpnext/accounts/doctype/repost_accounting_ledger_items/repost_accounting_ledger_items.py
new file mode 100644
index 0000000..9221f44
--- /dev/null
+++ b/erpnext/accounts/doctype/repost_accounting_ledger_items/repost_accounting_ledger_items.py
@@ -0,0 +1,9 @@
+# Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and contributors
+# For license information, please see license.txt
+
+# import frappe
+from frappe.model.document import Document
+
+
+class RepostAccountingLedgerItems(Document):
+	pass
diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.js b/erpnext/accounts/doctype/sales_invoice/sales_invoice.js
index b45bc41..a4bcdb4 100644
--- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.js
+++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.js
@@ -37,7 +37,7 @@
 		super.onload();
 
 		this.frm.ignore_doctypes_on_cancel_all = ['POS Invoice', 'Timesheet', 'POS Invoice Merge Log',
-							  'POS Closing Entry', 'Journal Entry', 'Payment Entry', "Repost Payment Ledger"];
+							  'POS Closing Entry', 'Journal Entry', 'Payment Entry', "Repost Payment Ledger", "Repost Accounting Ledger"];
 
 		if(!this.frm.doc.__islocal && !this.frm.doc.customer && this.frm.doc.debit_to) {
 			// show debit_to in print format
diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.json b/erpnext/accounts/doctype/sales_invoice/sales_invoice.json
index f0d3f72..7581366 100644
--- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.json
+++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.json
@@ -194,6 +194,7 @@
   "select_print_heading",
   "language",
   "subscription_section",
+  "subscription",
   "from_date",
   "auto_repeat",
   "column_break_140",
@@ -2018,6 +2019,12 @@
    "read_only": 1
   },
   {
+   "fieldname": "subscription",
+   "fieldtype": "Link",
+   "label": "Subscription",
+   "options": "Subscription"
+  },
+  {
    "default": "0",
    "depends_on": "eval: doc.apply_discount_on == \"Grand Total\"",
    "fieldname": "is_cash_or_non_trade_discount",
@@ -2157,7 +2164,7 @@
    "link_fieldname": "consolidated_invoice"
   }
  ],
- "modified": "2023-06-21 16:02:18.988799",
+ "modified": "2023-07-25 16:02:18.988799",
  "modified_by": "Administrator",
  "module": "Accounts",
  "name": "Sales Invoice",
diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py
index f08bf18..0bc5aa2 100644
--- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py
+++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py
@@ -386,6 +386,8 @@
 			"Repost Item Valuation",
 			"Repost Payment Ledger",
 			"Repost Payment Ledger Items",
+			"Repost Accounting Ledger",
+			"Repost Accounting Ledger Items",
 			"Payment Ledger Entry",
 			"Serial and Batch Bundle",
 		)
@@ -1061,6 +1063,7 @@
 		self.make_internal_transfer_gl_entries(gl_entries)
 
 		self.make_item_gl_entries(gl_entries)
+		self.make_precision_loss_gl_entry(gl_entries)
 		self.make_discount_gl_entries(gl_entries)
 
 		# merge gl entries before adding pos entries
@@ -1651,15 +1654,13 @@
 		frappe.db.set_value("Customer", self.customer, "loyalty_program_tier", lp_details.tier_name)
 
 	def get_returned_amount(self):
-		from frappe.query_builder.functions import Coalesce, Sum
+		from frappe.query_builder.functions import Sum
 
 		doc = frappe.qb.DocType(self.doctype)
 		returned_amount = (
 			frappe.qb.from_(doc)
 			.select(Sum(doc.grand_total))
-			.where(
-				(doc.docstatus == 1) & (doc.is_return == 1) & (Coalesce(doc.return_against, "") == self.name)
-			)
+			.where((doc.docstatus == 1) & (doc.is_return == 1) & (doc.return_against == self.name))
 		).run()
 
 		return abs(returned_amount[0][0]) if returned_amount[0][0] else 0
diff --git a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py
index 8816a8c..f9cfe5a 100644
--- a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py
+++ b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py
@@ -2049,28 +2049,27 @@
 		self.assertEqual(si.total_taxes_and_charges, 228.82)
 		self.assertEqual(si.rounding_adjustment, -0.01)
 
-		expected_values = dict(
-			(d[0], d)
-			for d in [
-				[si.debit_to, 1500, 0.0],
-				["_Test Account Service Tax - _TC", 0.0, 114.41],
-				["_Test Account VAT - _TC", 0.0, 114.41],
-				["Sales - _TC", 0.0, 1271.18],
-			]
-		)
+		expected_values = [
+			["_Test Account Service Tax - _TC", 0.0, 114.41],
+			["_Test Account VAT - _TC", 0.0, 114.41],
+			[si.debit_to, 1500, 0.0],
+			["Round Off - _TC", 0.01, 0.01],
+			["Sales - _TC", 0.0, 1271.18],
+		]
 
 		gl_entries = frappe.db.sql(
-			"""select account, debit, credit
+			"""select account, sum(debit) as debit, sum(credit) as credit
 			from `tabGL Entry` where voucher_type='Sales Invoice' and voucher_no=%s
+			group by account
 			order by account asc""",
 			si.name,
 			as_dict=1,
 		)
 
-		for gle in gl_entries:
-			self.assertEqual(expected_values[gle.account][0], gle.account)
-			self.assertEqual(expected_values[gle.account][1], gle.debit)
-			self.assertEqual(expected_values[gle.account][2], gle.credit)
+		for i, gle in enumerate(gl_entries):
+			self.assertEqual(expected_values[i][0], gle.account)
+			self.assertEqual(expected_values[i][1], gle.debit)
+			self.assertEqual(expected_values[i][2], gle.credit)
 
 	def test_rounding_adjustment_3(self):
 		from erpnext.accounts.doctype.accounting_dimension.test_accounting_dimension import (
@@ -2125,13 +2124,14 @@
 				["_Test Account Service Tax - _TC", 0.0, 240.43],
 				["_Test Account VAT - _TC", 0.0, 240.43],
 				["Sales - _TC", 0.0, 4007.15],
-				["Round Off - _TC", 0.01, 0],
+				["Round Off - _TC", 0.02, 0.01],
 			]
 		)
 
 		gl_entries = frappe.db.sql(
-			"""select account, debit, credit
+			"""select account, sum(debit) as debit, sum(credit) as credit
 			from `tabGL Entry` where voucher_type='Sales Invoice' and voucher_no=%s
+			group by account
 			order by account asc""",
 			si.name,
 			as_dict=1,
@@ -3376,6 +3376,7 @@
 
 		set_advance_flag(company="_Test Company", flag=0, default_account="")
 
+	@change_settings("Selling Settings", {"allow_negative_rates_for_items": 0})
 	def test_sales_return_negative_rate(self):
 		si = create_sales_invoice(is_return=1, qty=-2, rate=-10, do_not_save=True)
 		self.assertRaises(frappe.ValidationError, si.save)
diff --git a/erpnext/accounts/doctype/subscription/subscription.js b/erpnext/accounts/doctype/subscription/subscription.js
index 1a90664..ae789b5 100644
--- a/erpnext/accounts/doctype/subscription/subscription.js
+++ b/erpnext/accounts/doctype/subscription/subscription.js
@@ -2,16 +2,16 @@
 // For license information, please see license.txt
 
 frappe.ui.form.on('Subscription', {
-	setup: function(frm) {
-		frm.set_query('party_type', function() {
+	setup: function (frm) {
+		frm.set_query('party_type', function () {
 			return {
-				filters : {
+				filters: {
 					name: ['in', ['Customer', 'Supplier']]
 				}
 			}
 		});
 
-		frm.set_query('cost_center', function() {
+		frm.set_query('cost_center', function () {
 			return {
 				filters: {
 					company: frm.doc.company
@@ -20,76 +20,60 @@
 		});
 	},
 
-	refresh: function(frm) {
-		if(!frm.is_new()){
-			if(frm.doc.status !== 'Cancelled'){
-				frm.add_custom_button(
-					__('Cancel Subscription'),
-					() => frm.events.cancel_this_subscription(frm)
-				);
-				frm.add_custom_button(
-					__('Fetch Subscription Updates'),
-					() => frm.events.get_subscription_updates(frm)
-				);
-			}
-			else if(frm.doc.status === 'Cancelled'){
-				frm.add_custom_button(
-					__('Restart Subscription'),
-					() => frm.events.renew_this_subscription(frm)
-				);
-			}
+	refresh: function (frm) {
+		if (frm.is_new()) return;
+
+		if (frm.doc.status !== 'Cancelled') {
+			frm.add_custom_button(
+				__('Fetch Subscription Updates'),
+				() => frm.trigger('get_subscription_updates'),
+				__('Actions')
+			);
+
+			frm.add_custom_button(
+				__('Cancel Subscription'),
+				() => frm.trigger('cancel_this_subscription'),
+				__('Actions')
+			);
+		} else if (frm.doc.status === 'Cancelled') {
+			frm.add_custom_button(
+				__('Restart Subscription'),
+				() => frm.trigger('renew_this_subscription'),
+				__('Actions')
+			);
 		}
 	},
 
-	cancel_this_subscription: function(frm) {
-		const doc = frm.doc;
+	cancel_this_subscription: function (frm) {
 		frappe.confirm(
 			__('This action will stop future billing. Are you sure you want to cancel this subscription?'),
-			function() {
-				frappe.call({
-					method:
-					"erpnext.accounts.doctype.subscription.subscription.cancel_subscription",
-					args: {name: doc.name},
-					callback: function(data){
-						if(!data.exc){
-							frm.reload_doc();
-						}
+			() => {
+				frm.call('cancel_subscription').then(r => {
+					if (!r.exec) {
+						frm.reload_doc();
 					}
 				});
 			}
 		);
 	},
 
-	renew_this_subscription: function(frm) {
-		const doc = frm.doc;
+	renew_this_subscription: function (frm) {
 		frappe.confirm(
-			__('You will lose records of previously generated invoices. Are you sure you want to restart this subscription?'),
-			function() {
-				frappe.call({
-					method:
-					"erpnext.accounts.doctype.subscription.subscription.restart_subscription",
-					args: {name: doc.name},
-					callback: function(data){
-						if(!data.exc){
-							frm.reload_doc();
-						}
+			__('Are you sure you want to restart this subscription?'),
+			() => {
+				frm.call('restart_subscription').then(r => {
+					if (!r.exec) {
+						frm.reload_doc();
 					}
 				});
 			}
 		);
 	},
 
-	get_subscription_updates: function(frm) {
-		const doc = frm.doc;
-		frappe.call({
-			method:
-			"erpnext.accounts.doctype.subscription.subscription.get_subscription_updates",
-			args: {name: doc.name},
-			freeze: true,
-			callback: function(data){
-				if(!data.exc){
-					frm.reload_doc();
-				}
+	get_subscription_updates: function (frm) {
+		frm.call('process').then(r => {
+			if (!r.exec) {
+				frm.reload_doc();
 			}
 		});
 	}
diff --git a/erpnext/accounts/doctype/subscription/subscription.json b/erpnext/accounts/doctype/subscription/subscription.json
index c4e4be7..c15aa1e 100644
--- a/erpnext/accounts/doctype/subscription/subscription.json
+++ b/erpnext/accounts/doctype/subscription/subscription.json
@@ -19,6 +19,7 @@
   "trial_period_end",
   "follow_calendar_months",
   "generate_new_invoices_past_due_date",
+  "submit_invoice",
   "column_break_11",
   "current_invoice_start",
   "current_invoice_end",
@@ -35,12 +36,8 @@
   "cb_2",
   "additional_discount_percentage",
   "additional_discount_amount",
-  "sb_3",
-  "submit_invoice",
-  "invoices",
   "accounting_dimensions_section",
-  "cost_center",
-  "dimension_col_break"
+  "cost_center"
  ],
  "fields": [
   {
@@ -163,29 +160,12 @@
    "label": "Additional DIscount Amount"
   },
   {
-   "depends_on": "eval:doc.invoices",
-   "fieldname": "sb_3",
-   "fieldtype": "Section Break",
-   "label": "Invoices"
-  },
-  {
-   "collapsible": 1,
-   "fieldname": "invoices",
-   "fieldtype": "Table",
-   "label": "Invoices",
-   "options": "Subscription Invoice"
-  },
-  {
    "collapsible": 1,
    "fieldname": "accounting_dimensions_section",
    "fieldtype": "Section Break",
    "label": "Accounting Dimensions"
   },
   {
-   "fieldname": "dimension_col_break",
-   "fieldtype": "Column Break"
-  },
-  {
    "fieldname": "party_type",
    "fieldtype": "Link",
    "label": "Party Type",
@@ -259,15 +239,27 @@
    "default": "1",
    "fieldname": "submit_invoice",
    "fieldtype": "Check",
-   "label": "Submit Invoice Automatically"
+   "label": "Submit Generated Invoices"
   }
  ],
  "index_web_pages_for_search": 1,
- "links": [],
- "modified": "2021-04-19 15:24:27.550797",
+ "links": [
+  {
+   "group": "Buying",
+   "link_doctype": "Purchase Invoice",
+   "link_fieldname": "subscription"
+  },
+  {
+   "group": "Selling",
+   "link_doctype": "Sales Invoice",
+   "link_fieldname": "subscription"
+  }
+ ],
+ "modified": "2022-02-18 23:24:57.185054",
  "modified_by": "Administrator",
  "module": "Accounts",
  "name": "Subscription",
+ "naming_rule": "Expression (old style)",
  "owner": "Administrator",
  "permissions": [
   {
@@ -309,5 +301,6 @@
  ],
  "sort_field": "modified",
  "sort_order": "DESC",
+ "states": [],
  "track_changes": 1
 }
\ No newline at end of file
diff --git a/erpnext/accounts/doctype/subscription/subscription.py b/erpnext/accounts/doctype/subscription/subscription.py
index 8708342..bbcade1 100644
--- a/erpnext/accounts/doctype/subscription/subscription.py
+++ b/erpnext/accounts/doctype/subscription/subscription.py
@@ -2,14 +2,17 @@
 # For license information, please see license.txt
 
 
+from datetime import datetime
+from typing import Dict, List, Optional, Union
+
 import frappe
 from frappe import _
 from frappe.model.document import Document
 from frappe.utils.data import (
 	add_days,
+	add_months,
 	add_to_date,
 	cint,
-	cstr,
 	date_diff,
 	flt,
 	get_last_day,
@@ -17,8 +20,7 @@
 	nowdate,
 )
 
-import erpnext
-from erpnext import get_default_company
+from erpnext import get_default_company, get_default_cost_center
 from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import (
 	get_accounting_dimensions,
 )
@@ -26,33 +28,39 @@
 from erpnext.accounts.party import get_party_account_currency
 
 
+class InvoiceCancelled(frappe.ValidationError):
+	pass
+
+
+class InvoiceNotCancelled(frappe.ValidationError):
+	pass
+
+
 class Subscription(Document):
 	def before_insert(self):
 		# update start just before the subscription doc is created
 		self.update_subscription_period(self.start_date)
 
-	def update_subscription_period(self, date=None, return_date=False):
+	def update_subscription_period(self, date: Optional[Union[datetime.date, str]] = None):
 		"""
 		Subscription period is the period to be billed. This method updates the
 		beginning of the billing period and end of the billing period.
-
 		The beginning of the billing period is represented in the doctype as
 		`current_invoice_start` and the end of the billing period is represented
 		as `current_invoice_end`.
-
-		If return_date is True, it wont update the start and end dates.
-		This is implemented to get the dates to check if is_current_invoice_generated
 		"""
+		self.current_invoice_start = self.get_current_invoice_start(date)
+		self.current_invoice_end = self.get_current_invoice_end(self.current_invoice_start)
+
+	def _get_subscription_period(self, date: Optional[Union[datetime.date, str]] = None):
 		_current_invoice_start = self.get_current_invoice_start(date)
 		_current_invoice_end = self.get_current_invoice_end(_current_invoice_start)
 
-		if return_date:
-			return _current_invoice_start, _current_invoice_end
+		return _current_invoice_start, _current_invoice_end
 
-		self.current_invoice_start = _current_invoice_start
-		self.current_invoice_end = _current_invoice_end
-
-	def get_current_invoice_start(self, date=None):
+	def get_current_invoice_start(
+		self, date: Optional[Union[datetime.date, str]] = None
+	) -> Union[datetime.date, str]:
 		"""
 		This returns the date of the beginning of the current billing period.
 		If the `date` parameter is not given , it will be automatically set as today's
@@ -75,13 +83,13 @@
 
 		return _current_invoice_start
 
-	def get_current_invoice_end(self, date=None):
+	def get_current_invoice_end(
+		self, date: Optional[Union[datetime.date, str]] = None
+	) -> Union[datetime.date, str]:
 		"""
 		This returns the date of the end of the current billing period.
-
 		If the subscription is in trial period, it will be set as the end of the
 		trial period.
-
 		If is not in a trial period, it will be `x` days from the beginning of the
 		current billing period where `x` is the billing interval from the
 		`Subscription Plan` in the `Subscription`.
@@ -105,24 +113,13 @@
 				_current_invoice_end = get_last_day(date)
 
 			if self.follow_calendar_months:
+				# Sets the end date
+				# eg if date is 17-Feb-2022, the invoice will be generated per month ie
+				# the invoice will be created from 17 Feb to 28 Feb
 				billing_info = self.get_billing_cycle_and_interval()
 				billing_interval_count = billing_info[0]["billing_interval_count"]
-				calendar_months = get_calendar_months(billing_interval_count)
-				calendar_month = 0
-				current_invoice_end_month = getdate(_current_invoice_end).month
-				current_invoice_end_year = getdate(_current_invoice_end).year
-
-				for month in calendar_months:
-					if month <= current_invoice_end_month:
-						calendar_month = month
-
-				if cint(calendar_month - billing_interval_count) <= 0 and getdate(date).month != 1:
-					calendar_month = 12
-					current_invoice_end_year -= 1
-
-				_current_invoice_end = get_last_day(
-					cstr(current_invoice_end_year) + "-" + cstr(calendar_month) + "-01"
-				)
+				_end = add_months(getdate(date), billing_interval_count - 1)
+				_current_invoice_end = get_last_day(_end)
 
 			if self.end_date and getdate(_current_invoice_end) > getdate(self.end_date):
 				_current_invoice_end = self.end_date
@@ -130,7 +127,7 @@
 		return _current_invoice_end
 
 	@staticmethod
-	def validate_plans_billing_cycle(billing_cycle_data):
+	def validate_plans_billing_cycle(billing_cycle_data: List[Dict[str, str]]) -> None:
 		"""
 		Makes sure that all `Subscription Plan` in the `Subscription` have the
 		same billing interval
@@ -138,10 +135,9 @@
 		if billing_cycle_data and len(billing_cycle_data) != 1:
 			frappe.throw(_("You can only have Plans with the same billing cycle in a Subscription"))
 
-	def get_billing_cycle_and_interval(self):
+	def get_billing_cycle_and_interval(self) -> List[Dict[str, str]]:
 		"""
 		Returns a dict representing the billing interval and cycle for this `Subscription`.
-
 		You shouldn't need to call this directly. Use `get_billing_cycle` instead.
 		"""
 		plan_names = [plan.plan for plan in self.plans]
@@ -156,72 +152,65 @@
 
 		return billing_info
 
-	def get_billing_cycle_data(self):
+	def get_billing_cycle_data(self) -> Dict[str, int]:
 		"""
 		Returns dict contain the billing cycle data.
-
 		You shouldn't need to call this directly. Use `get_billing_cycle` instead.
 		"""
 		billing_info = self.get_billing_cycle_and_interval()
+		if not billing_info:
+			return None
 
-		self.validate_plans_billing_cycle(billing_info)
+		data = dict()
+		interval = billing_info[0]["billing_interval"]
+		interval_count = billing_info[0]["billing_interval_count"]
 
-		if billing_info:
-			data = dict()
-			interval = billing_info[0]["billing_interval"]
-			interval_count = billing_info[0]["billing_interval_count"]
-			if interval not in ["Day", "Week"]:
-				data["days"] = -1
-			if interval == "Day":
-				data["days"] = interval_count - 1
-			elif interval == "Month":
-				data["months"] = interval_count
-			elif interval == "Year":
-				data["years"] = interval_count
-			# todo: test week
-			elif interval == "Week":
-				data["days"] = interval_count * 7 - 1
+		if interval not in ["Day", "Week"]:
+			data["days"] = -1
 
-			return data
+		if interval == "Day":
+			data["days"] = interval_count - 1
+		elif interval == "Week":
+			data["days"] = interval_count * 7 - 1
+		elif interval == "Month":
+			data["months"] = interval_count
+		elif interval == "Year":
+			data["years"] = interval_count
 
-	def set_status_grace_period(self):
-		"""
-		Sets the `Subscription` `status` based on the preference set in `Subscription Settings`.
+		return data
 
-		Used when the `Subscription` needs to decide what to do after the current generated
-		invoice is past it's due date and grace period.
-		"""
-		subscription_settings = frappe.get_single("Subscription Settings")
-		if self.status == "Past Due Date" and self.is_past_grace_period():
-			self.status = "Cancelled" if cint(subscription_settings.cancel_after_grace) else "Unpaid"
-
-	def set_subscription_status(self):
+	def set_subscription_status(self) -> None:
 		"""
 		Sets the status of the `Subscription`
 		"""
 		if self.is_trialling():
 			self.status = "Trialling"
-		elif self.status == "Active" and self.end_date and getdate() > getdate(self.end_date):
+		elif (
+			self.status == "Active"
+			and self.end_date
+			and getdate(frappe.flags.current_date) > getdate(self.end_date)
+		):
 			self.status = "Completed"
 		elif self.is_past_grace_period():
-			subscription_settings = frappe.get_single("Subscription Settings")
-			self.status = "Cancelled" if cint(subscription_settings.cancel_after_grace) else "Unpaid"
+			self.status = self.get_status_for_past_grace_period()
+			self.cancelation_date = (
+				getdate(frappe.flags.current_date) if self.status == "Cancelled" else None
+			)
 		elif self.current_invoice_is_past_due() and not self.is_past_grace_period():
 			self.status = "Past Due Date"
-		elif not self.has_outstanding_invoice():
+		elif not self.has_outstanding_invoice() or self.is_new_subscription():
 			self.status = "Active"
-		elif self.is_new_subscription():
-			self.status = "Active"
+
 		self.save()
 
-	def is_trialling(self):
+	def is_trialling(self) -> bool:
 		"""
 		Returns `True` if the `Subscription` is in trial period.
 		"""
 		return not self.period_has_passed(self.trial_period_end) and self.is_new_subscription()
 
 	@staticmethod
-	def period_has_passed(end_date):
+	def period_has_passed(end_date: Union[str, datetime.date]) -> bool:
 		"""
 		Returns true if the given `end_date` has passed
 		"""
@@ -229,61 +218,59 @@
 		if not end_date:
 			return True
 
-		end_date = getdate(end_date)
-		return getdate() > getdate(end_date)
+		return getdate(frappe.flags.current_date) > getdate(end_date)
 
-	def is_past_grace_period(self):
+	def get_status_for_past_grace_period(self) -> str:
+		cancel_after_grace = cint(frappe.get_value("Subscription Settings", None, "cancel_after_grace"))
+		status = "Unpaid"
+
+		if cancel_after_grace:
+			status = "Cancelled"
+
+		return status
+
+	def is_past_grace_period(self) -> bool:
 		"""
 		Returns `True` if the grace period for the `Subscription` has passed
 		"""
-		current_invoice = self.get_current_invoice()
-		if self.current_invoice_is_past_due(current_invoice):
-			subscription_settings = frappe.get_single("Subscription Settings")
-			grace_period = cint(subscription_settings.grace_period)
+		if not self.current_invoice_is_past_due():
+			return
 
-			return getdate() > add_days(current_invoice.due_date, grace_period)
+		grace_period = cint(frappe.get_value("Subscription Settings", None, "grace_period"))
+		return getdate(frappe.flags.current_date) >= getdate(
+			add_days(self.current_invoice.due_date, grace_period)
+		)
 
-	def current_invoice_is_past_due(self, current_invoice=None):
+	def current_invoice_is_past_due(self) -> bool:
 		"""
 		Returns `True` if the current generated invoice is overdue
 		"""
-		if not current_invoice:
-			current_invoice = self.get_current_invoice()
-
-		if not current_invoice or self.is_paid(current_invoice):
+		if not self.current_invoice or self.is_paid(self.current_invoice):
 			return False
-		else:
-			return getdate() > getdate(current_invoice.due_date)
 
-	def get_current_invoice(self):
-		"""
-		Returns the most recent generated invoice.
-		"""
-		doctype = "Sales Invoice" if self.party_type == "Customer" else "Purchase Invoice"
+		return getdate(frappe.flags.current_date) >= getdate(self.current_invoice.due_date)
 
-		if len(self.invoices):
-			current = self.invoices[-1]
-			if frappe.db.exists(doctype, current.get("invoice")):
-				doc = frappe.get_doc(doctype, current.get("invoice"))
-				return doc
-			else:
-				frappe.throw(_("Invoice {0} no longer exists").format(current.get("invoice")))
+	@property
+	def invoice_document_type(self) -> str:
+		return "Sales Invoice" if self.party_type == "Customer" else "Purchase Invoice"
 
-	def is_new_subscription(self):
+	def is_new_subscription(self) -> bool:
 		"""
 		Returns `True` if `Subscription` has never generated an invoice
 		"""
-		return len(self.invoices) == 0
+		return self.is_new() or not frappe.db.exists(
+			{"doctype": self.invoice_document_type, "subscription": self.name}
+		)
 
-	def validate(self):
+	def validate(self) -> None:
 		self.validate_trial_period()
 		self.validate_plans_billing_cycle(self.get_billing_cycle_and_interval())
 		self.validate_end_date()
 		self.validate_to_follow_calendar_months()
 		if not self.cost_center:
-			self.cost_center = erpnext.get_default_cost_center(self.get("company"))
+			self.cost_center = get_default_cost_center(self.get("company"))
 
-	def validate_trial_period(self):
+	def validate_trial_period(self) -> None:
 		"""
 		Runs sanity checks on trial period dates for the `Subscription`
 		"""
@@ -297,7 +284,7 @@
 		if self.trial_period_start and getdate(self.trial_period_start) > getdate(self.start_date):
 			frappe.throw(_("Trial Period Start date cannot be after Subscription Start Date"))
 
-	def validate_end_date(self):
+	def validate_end_date(self) -> None:
 		billing_cycle_info = self.get_billing_cycle_data()
 		end_date = add_to_date(self.start_date, **billing_cycle_info)
 
@@ -306,53 +293,53 @@
 				_("Subscription End Date must be after {0} as per the subscription plan").format(end_date)
 			)
 
-	def validate_to_follow_calendar_months(self):
-		if self.follow_calendar_months:
-			billing_info = self.get_billing_cycle_and_interval()
+	def validate_to_follow_calendar_months(self) -> None:
+		if not self.follow_calendar_months:
+			return
 
-			if not self.end_date:
-				frappe.throw(_("Subscription End Date is mandatory to follow calendar months"))
+		billing_info = self.get_billing_cycle_and_interval()
 
-			if billing_info[0]["billing_interval"] != "Month":
-				frappe.throw(
-					_("Billing Interval in Subscription Plan must be Month to follow calendar months")
-				)
+		if not self.end_date:
+			frappe.throw(_("Subscription End Date is mandatory to follow calendar months"))
 
-	def after_insert(self):
+		if billing_info[0]["billing_interval"] != "Month":
+			frappe.throw(_("Billing Interval in Subscription Plan must be Month to follow calendar months"))
+
+	def after_insert(self) -> None:
 		# todo: deal with users who collect prepayments. Maybe a new Subscription Invoice doctype?
 		self.set_subscription_status()
 
-	def generate_invoice(self, prorate=0):
+	def generate_invoice(
+		self,
+		from_date: Optional[Union[str, datetime.date]] = None,
+		to_date: Optional[Union[str, datetime.date]] = None,
+	) -> Document:
 		"""
 		Creates a `Invoice` for the `Subscription`, updates `self.invoices` and
 		saves the `Subscription`.
+		Backwards compatibility
 		"""
+		return self.create_invoice(from_date=from_date, to_date=to_date)
 
-		doctype = "Sales Invoice" if self.party_type == "Customer" else "Purchase Invoice"
-
-		invoice = self.create_invoice(prorate)
-		self.append("invoices", {"document_type": doctype, "invoice": invoice.name})
-
-		self.save()
-
-		return invoice
-
-	def create_invoice(self, prorate):
+	def create_invoice(
+		self,
+		from_date: Optional[Union[str, datetime.date]] = None,
+		to_date: Optional[Union[str, datetime.date]] = None,
+	) -> Document:
 		"""
 		Creates a `Invoice`, submits it and returns it
 		"""
-		doctype = "Sales Invoice" if self.party_type == "Customer" else "Purchase Invoice"
-
-		invoice = frappe.new_doc(doctype)
-
 		# For backward compatibility
 		# Earlier subscription didn't had any company field
 		company = self.get("company") or get_default_company()
 		if not company:
+			# fmt: off
 			frappe.throw(
-				_("Company is mandatory was generating invoice. Please set default company in Global Defaults")
+				_("Company is mandatory was generating invoice. Please set default company in Global Defaults.")
 			)
+			# fmt: on
 
+		invoice = frappe.new_doc(self.invoice_document_type)
 		invoice.company = company
 		invoice.set_posting_time = 1
 		invoice.posting_date = (
@@ -363,17 +350,17 @@
 
 		invoice.cost_center = self.cost_center
 
-		if doctype == "Sales Invoice":
+		if self.invoice_document_type == "Sales Invoice":
 			invoice.customer = self.party
 		else:
 			invoice.supplier = self.party
 			if frappe.db.get_value("Supplier", self.party, "tax_withholding_category"):
 				invoice.apply_tds = 1
 
-		### Add party currency to invoice
+		# Add party currency to invoice
 		invoice.currency = get_party_account_currency(self.party_type, self.party, self.company)
 
-		## Add dimensions in invoice for subscription:
+		# Add dimensions in invoice for subscription:
 		accounting_dimensions = get_accounting_dimensions()
 
 		for dimension in accounting_dimensions:
@@ -382,7 +369,7 @@
 
 		# Subscription is better suited for service items. I won't update `update_stock`
 		# for that reason
-		items_list = self.get_items_from_plans(self.plans, prorate)
+		items_list = self.get_items_from_plans(self.plans, is_prorate())
 		for item in items_list:
 			item["cost_center"] = self.cost_center
 			invoice.append("items", item)
@@ -390,9 +377,9 @@
 		# Taxes
 		tax_template = ""
 
-		if doctype == "Sales Invoice" and self.sales_tax_template:
+		if self.invoice_document_type == "Sales Invoice" and self.sales_tax_template:
 			tax_template = self.sales_tax_template
-		if doctype == "Purchase Invoice" and self.purchase_tax_template:
+		if self.invoice_document_type == "Purchase Invoice" and self.purchase_tax_template:
 			tax_template = self.purchase_tax_template
 
 		if tax_template:
@@ -424,8 +411,9 @@
 				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.subscription = self.name
+		invoice.from_date = from_date or self.current_invoice_start
+		invoice.to_date = to_date or self.current_invoice_end
 
 		invoice.flags.ignore_mandatory = True
 
@@ -437,13 +425,20 @@
 
 		return invoice
 
-	def get_items_from_plans(self, plans, prorate=0):
+	def get_items_from_plans(
+		self, plans: List[Dict[str, str]], prorate: Optional[bool] = None
+	) -> List[Dict]:
 		"""
 		Returns the `Item`s linked to `Subscription Plan`
 		"""
+		if prorate is None:
+			prorate = False
+
 		if prorate:
 			prorate_factor = get_prorata_factor(
-				self.current_invoice_end, self.current_invoice_start, self.generate_invoice_at_period_start
+				self.current_invoice_end,
+				self.current_invoice_start,
+				cint(self.generate_invoice_at_period_start),
 			)
 
 		items = []
@@ -465,7 +460,11 @@
 					"item_code": item_code,
 					"qty": plan.qty,
 					"rate": get_plan_rate(
-						plan.plan, plan.qty, party, self.current_invoice_start, self.current_invoice_end
+						plan.plan,
+						plan.qty,
+						party,
+						self.current_invoice_start,
+						self.current_invoice_end,
 					),
 					"cost_center": plan_doc.cost_center,
 				}
@@ -503,254 +502,184 @@
 
 		return items
 
-	def process(self):
+	@frappe.whitelist()
+	def process(self) -> bool:
 		"""
 		To be called by task periodically. It checks the subscription and takes appropriate action
 		as need be. It calls either of these methods depending the `Subscription` status:
 		1. `process_for_active`
 		2. `process_for_past_due`
 		"""
-		if self.status == "Active":
-			self.process_for_active()
-		elif self.status in ["Past Due Date", "Unpaid"]:
-			self.process_for_past_due_date()
+		if (
+			not self.is_current_invoice_generated(self.current_invoice_start, self.current_invoice_end)
+			and self.can_generate_new_invoice()
+		):
+			self.generate_invoice()
+			self.update_subscription_period(add_days(self.current_invoice_end, 1))
+
+		if self.cancel_at_period_end and (
+			getdate(frappe.flags.current_date) >= getdate(self.current_invoice_end)
+			or getdate(frappe.flags.current_date) >= getdate(self.end_date)
+		):
+			self.cancel_subscription()
 
 		self.set_subscription_status()
 
 		self.save()
 
-	def is_postpaid_to_invoice(self):
-		return getdate() > getdate(self.current_invoice_end) or (
-			getdate() >= getdate(self.current_invoice_end)
-			and getdate(self.current_invoice_end) == getdate(self.current_invoice_start)
-		)
+	def can_generate_new_invoice(self) -> bool:
+		if self.cancelation_date:
+			return False
+		elif self.generate_invoice_at_period_start and (
+			getdate(frappe.flags.current_date) == getdate(self.current_invoice_start)
+			or self.is_new_subscription()
+		):
+			return True
+		elif getdate(frappe.flags.current_date) == getdate(self.current_invoice_end):
+			if self.has_outstanding_invoice() and not self.generate_new_invoices_past_due_date:
+				return False
 
-	def is_prepaid_to_invoice(self):
-		if not self.generate_invoice_at_period_start:
+			return True
+		else:
 			return False
 
-		if self.is_new_subscription() and getdate() >= getdate(self.current_invoice_start):
-			return True
-
-		# Check invoice dates and make sure it doesn't have outstanding invoices
-		return getdate() >= getdate(self.current_invoice_start)
-
-	def is_current_invoice_generated(self, _current_start_date=None, _current_end_date=None):
-		invoice = self.get_current_invoice()
-
+	def is_current_invoice_generated(
+		self,
+		_current_start_date: Union[datetime.date, str] = None,
+		_current_end_date: Union[datetime.date, str] = None,
+	) -> bool:
 		if not (_current_start_date and _current_end_date):
-			_current_start_date, _current_end_date = self.update_subscription_period(
-				date=add_days(self.current_invoice_end, 1), return_date=True
+			_current_start_date, _current_end_date = self._get_subscription_period(
+				date=add_days(self.current_invoice_end, 1)
 			)
 
-		if invoice and getdate(_current_start_date) <= getdate(invoice.posting_date) <= getdate(
-			_current_end_date
-		):
+		if self.current_invoice and getdate(_current_start_date) <= getdate(
+			self.current_invoice.posting_date
+		) <= getdate(_current_end_date):
 			return True
 
 		return False
 
-	def process_for_active(self):
+	@property
+	def current_invoice(self) -> Union[Document, None]:
 		"""
-		Called by `process` if the status of the `Subscription` is 'Active'.
-
-		The possible outcomes of this method are:
-		1. Generate a new invoice
-		2. Change the `Subscription` status to 'Past Due Date'
-		3. Change the `Subscription` status to 'Cancelled'
+		Adds property for accessing the current_invoice
 		"""
+		return self.get_current_invoice()
 
-		if not self.is_current_invoice_generated(
-			self.current_invoice_start, self.current_invoice_end
-		) and (self.is_postpaid_to_invoice() or self.is_prepaid_to_invoice()):
+	def get_current_invoice(self) -> Union[Document, None]:
+		"""
+		Returns the most recent generated invoice.
+		"""
+		invoice = frappe.get_all(
+			self.invoice_document_type,
+			{
+				"subscription": self.name,
+			},
+			limit=1,
+			order_by="to_date desc",
+			pluck="name",
+		)
 
-			prorate = frappe.db.get_single_value("Subscription Settings", "prorate")
-			self.generate_invoice(prorate)
+		if invoice:
+			return frappe.get_doc(self.invoice_document_type, invoice[0])
 
-		if getdate() > getdate(self.current_invoice_end) and self.is_prepaid_to_invoice():
-			self.update_subscription_period(add_days(self.current_invoice_end, 1))
-
-		if self.cancel_at_period_end and getdate() > getdate(self.current_invoice_end):
-			self.cancel_subscription_at_period_end()
-
-	def cancel_subscription_at_period_end(self):
+	def cancel_subscription_at_period_end(self) -> None:
 		"""
 		Called when `Subscription.cancel_at_period_end` is truthy
 		"""
-		if self.end_date and getdate() < getdate(self.end_date):
-			return
-
 		self.status = "Cancelled"
-		if not self.cancelation_date:
-			self.cancelation_date = nowdate()
+		self.cancelation_date = nowdate()
 
-	def process_for_past_due_date(self):
-		"""
-		Called by `process` if the status of the `Subscription` is 'Past Due Date'.
-
-		The possible outcomes of this method are:
-		1. Change the `Subscription` status to 'Active'
-		2. Change the `Subscription` status to 'Cancelled'
-		3. Change the `Subscription` status to 'Unpaid'
-		"""
-		current_invoice = self.get_current_invoice()
-		if not current_invoice:
-			frappe.throw(_("Current invoice {0} is missing").format(current_invoice.invoice))
-		else:
-			if not self.has_outstanding_invoice():
-				self.status = "Active"
-			else:
-				self.set_status_grace_period()
-
-			if getdate() > getdate(self.current_invoice_end):
-				self.update_subscription_period(add_days(self.current_invoice_end, 1))
-
-			# Generate invoices periodically even if current invoice are unpaid
-			if (
-				self.generate_new_invoices_past_due_date
-				and not self.is_current_invoice_generated(self.current_invoice_start, self.current_invoice_end)
-				and (self.is_postpaid_to_invoice() or self.is_prepaid_to_invoice())
-			):
-
-				prorate = frappe.db.get_single_value("Subscription Settings", "prorate")
-				self.generate_invoice(prorate)
+	@property
+	def invoices(self) -> List[Dict]:
+		return frappe.get_all(
+			self.invoice_document_type,
+			filters={"subscription": self.name},
+			order_by="from_date asc",
+		)
 
 	@staticmethod
-	def is_paid(invoice):
+	def is_paid(invoice: Document) -> bool:
 		"""
 		Return `True` if the given invoice is paid
 		"""
 		return invoice.status == "Paid"
 
-	def has_outstanding_invoice(self):
+	def has_outstanding_invoice(self) -> int:
 		"""
 		Returns `True` if the most recent invoice for the `Subscription` is not paid
 		"""
-		doctype = "Sales Invoice" if self.party_type == "Customer" else "Purchase Invoice"
-		current_invoice = self.get_current_invoice()
-		invoice_list = [d.invoice for d in self.invoices]
-
-		outstanding_invoices = frappe.get_all(
-			doctype, fields=["name"], filters={"status": ("!=", "Paid"), "name": ("in", invoice_list)}
+		return frappe.db.count(
+			self.invoice_document_type,
+			{
+				"subscription": self.name,
+				"status": ["!=", "Paid"],
+			},
 		)
 
-		if outstanding_invoices:
-			return True
-		else:
-			False
-
-	def cancel_subscription(self):
+	@frappe.whitelist()
+	def cancel_subscription(self) -> None:
 		"""
 		This sets the subscription as cancelled. It will stop invoices from being generated
 		but it will not affect already created invoices.
 		"""
-		if self.status != "Cancelled":
-			to_generate_invoice = (
-				True if self.status == "Active" and not self.generate_invoice_at_period_start else False
-			)
-			to_prorate = frappe.db.get_single_value("Subscription Settings", "prorate")
-			self.status = "Cancelled"
-			self.cancelation_date = nowdate()
-			if to_generate_invoice:
-				self.generate_invoice(prorate=to_prorate)
-			self.save()
+		if self.status == "Cancelled":
+			frappe.throw(_("subscription is already cancelled."), InvoiceCancelled)
 
-	def restart_subscription(self):
+		to_generate_invoice = (
+			True if self.status == "Active" and not self.generate_invoice_at_period_start else False
+		)
+		self.status = "Cancelled"
+		self.cancelation_date = nowdate()
+
+		if to_generate_invoice:
+			self.generate_invoice(self.current_invoice_start, self.cancelation_date)
+
+		self.save()
+
+	@frappe.whitelist()
+	def restart_subscription(self) -> None:
 		"""
 		This sets the subscription as active. The subscription will be made to be like a new
 		subscription and the `Subscription` will lose all the history of generated invoices
 		it has.
 		"""
-		if self.status == "Cancelled":
-			self.status = "Active"
-			self.db_set("start_date", nowdate())
-			self.update_subscription_period(nowdate())
-			self.invoices = []
-			self.save()
-		else:
-			frappe.throw(_("You cannot restart a Subscription that is not cancelled."))
+		if not self.status == "Cancelled":
+			frappe.throw(_("You cannot restart a Subscription that is not cancelled."), InvoiceNotCancelled)
 
-	def get_precision(self):
-		invoice = self.get_current_invoice()
-		if invoice:
-			return invoice.precision("grand_total")
+		self.status = "Active"
+		self.cancelation_date = None
+		self.update_subscription_period(frappe.flags.current_date or nowdate())
+		self.save()
 
 
-def get_calendar_months(billing_interval):
-	calendar_months = []
-	start = 0
-	while start < 12:
-		start += billing_interval
-		calendar_months.append(start)
-
-	return calendar_months
+def is_prorate() -> int:
+	return cint(frappe.db.get_single_value("Subscription Settings", "prorate"))
 
 
-def get_prorata_factor(period_end, period_start, is_prepaid):
+def get_prorata_factor(
+	period_end: Union[datetime.date, str],
+	period_start: Union[datetime.date, str],
+	is_prepaid: Optional[int] = None,
+) -> Union[int, float]:
 	if is_prepaid:
-		prorate_factor = 1
-	else:
-		diff = flt(date_diff(nowdate(), period_start) + 1)
-		plan_days = flt(date_diff(period_end, period_start) + 1)
-		prorate_factor = diff / plan_days
+		return 1
 
-	return prorate_factor
+	diff = flt(date_diff(nowdate(), period_start) + 1)
+	plan_days = flt(date_diff(period_end, period_start) + 1)
+	return diff / plan_days
 
 
-def process_all():
+def process_all() -> None:
 	"""
 	Task to updates the status of all `Subscription` apart from those that are cancelled
 	"""
-	subscriptions = get_all_subscriptions()
-	for subscription in subscriptions:
-		process(subscription)
-
-
-def get_all_subscriptions():
-	"""
-	Returns all `Subscription` documents
-	"""
-	return frappe.db.get_all("Subscription", {"status": ("!=", "Cancelled")})
-
-
-def process(data):
-	"""
-	Checks a `Subscription` and updates it status as necessary
-	"""
-	if data:
+	for subscription in frappe.get_all("Subscription", {"status": ("!=", "Cancelled")}, pluck="name"):
 		try:
-			subscription = frappe.get_doc("Subscription", data["name"])
+			subscription = frappe.get_doc("Subscription", subscription)
 			subscription.process()
 			frappe.db.commit()
 		except frappe.ValidationError:
 			frappe.db.rollback()
 			subscription.log_error("Subscription failed")
-
-
-@frappe.whitelist()
-def cancel_subscription(name):
-	"""
-	Cancels a `Subscription`. This will stop the `Subscription` from further invoicing the
-	`Subscriber` but all already outstanding invoices will not be affected.
-	"""
-	subscription = frappe.get_doc("Subscription", name)
-	subscription.cancel_subscription()
-
-
-@frappe.whitelist()
-def restart_subscription(name):
-	"""
-	Restarts a cancelled `Subscription`. The `Subscription` will 'forget' the history of
-	all invoices it has generated
-	"""
-	subscription = frappe.get_doc("Subscription", name)
-	subscription.restart_subscription()
-
-
-@frappe.whitelist()
-def get_subscription_updates(name):
-	"""
-	Use this to get the latest state of the given `Subscription`
-	"""
-	subscription = frappe.get_doc("Subscription", name)
-	subscription.process()
diff --git a/erpnext/accounts/doctype/subscription/test_subscription.py b/erpnext/accounts/doctype/subscription/test_subscription.py
index eb17daa..0bb171f 100644
--- a/erpnext/accounts/doctype/subscription/test_subscription.py
+++ b/erpnext/accounts/doctype/subscription/test_subscription.py
@@ -11,6 +11,7 @@
 	date_diff,
 	flt,
 	get_date_str,
+	getdate,
 	nowdate,
 )
 
@@ -90,10 +91,18 @@
 		customer.insert()
 
 
+def reset_settings():
+	settings = frappe.get_single("Subscription Settings")
+	settings.grace_period = 0
+	settings.cancel_after_grace = 0
+	settings.save()
+
+
 class TestSubscription(unittest.TestCase):
 	def setUp(self):
 		create_plan()
 		create_parties()
+		reset_settings()
 
 	def test_create_subscription_with_trial_with_correct_period(self):
 		subscription = frappe.new_doc("Subscription")
@@ -116,8 +125,6 @@
 		self.assertEqual(subscription.invoices, [])
 		self.assertEqual(subscription.status, "Trialling")
 
-		subscription.delete()
-
 	def test_create_subscription_without_trial_with_correct_period(self):
 		subscription = frappe.new_doc("Subscription")
 		subscription.party_type = "Customer"
@@ -133,8 +140,6 @@
 		self.assertEqual(len(subscription.invoices), 0)
 		self.assertEqual(subscription.status, "Active")
 
-		subscription.delete()
-
 	def test_create_subscription_trial_with_wrong_dates(self):
 		subscription = frappe.new_doc("Subscription")
 		subscription.party_type = "Customer"
@@ -144,7 +149,6 @@
 		subscription.append("plans", {"plan": "_Test Plan Name", "qty": 1})
 
 		self.assertRaises(frappe.ValidationError, subscription.save)
-		subscription.delete()
 
 	def test_create_subscription_multi_with_different_billing_fails(self):
 		subscription = frappe.new_doc("Subscription")
@@ -156,7 +160,6 @@
 		subscription.append("plans", {"plan": "_Test Plan Name 3", "qty": 1})
 
 		self.assertRaises(frappe.ValidationError, subscription.save)
-		subscription.delete()
 
 	def test_invoice_is_generated_at_end_of_billing_period(self):
 		subscription = frappe.new_doc("Subscription")
@@ -169,13 +172,13 @@
 		self.assertEqual(subscription.status, "Active")
 		self.assertEqual(subscription.current_invoice_start, "2018-01-01")
 		self.assertEqual(subscription.current_invoice_end, "2018-01-31")
+		frappe.flags.current_date = "2018-01-31"
 		subscription.process()
 
 		self.assertEqual(len(subscription.invoices), 1)
-		self.assertEqual(subscription.current_invoice_start, "2018-01-01")
-		subscription.process()
+		self.assertEqual(subscription.current_invoice_start, "2018-02-01")
+		self.assertEqual(subscription.current_invoice_end, "2018-02-28")
 		self.assertEqual(subscription.status, "Unpaid")
-		subscription.delete()
 
 	def test_status_goes_back_to_active_after_invoice_is_paid(self):
 		subscription = frappe.new_doc("Subscription")
@@ -183,7 +186,9 @@
 		subscription.party = "_Test Customer"
 		subscription.append("plans", {"plan": "_Test Plan Name", "qty": 1})
 		subscription.start_date = "2018-01-01"
+		subscription.generate_invoice_at_period_start = True
 		subscription.insert()
+		frappe.flags.current_date = "2018-01-01"
 		subscription.process()  # generate first invoice
 		self.assertEqual(len(subscription.invoices), 1)
 
@@ -203,11 +208,8 @@
 		self.assertEqual(subscription.current_invoice_start, add_months(subscription.start_date, 1))
 		self.assertEqual(len(subscription.invoices), 1)
 
-		subscription.delete()
-
 	def test_subscription_cancel_after_grace_period(self):
 		settings = frappe.get_single("Subscription Settings")
-		default_grace_period_action = settings.cancel_after_grace
 		settings.cancel_after_grace = 1
 		settings.save()
 
@@ -215,20 +217,18 @@
 		subscription.party_type = "Customer"
 		subscription.party = "_Test Customer"
 		subscription.append("plans", {"plan": "_Test Plan Name", "qty": 1})
+		# subscription.generate_invoice_at_period_start = True
 		subscription.start_date = "2018-01-01"
 		subscription.insert()
 
 		self.assertEqual(subscription.status, "Active")
 
+		frappe.flags.current_date = "2018-01-31"
 		subscription.process()  # generate first invoice
 		# This should change status to Cancelled since grace period is 0
 		# And is backdated subscription so subscription will be cancelled after processing
 		self.assertEqual(subscription.status, "Cancelled")
 
-		settings.cancel_after_grace = default_grace_period_action
-		settings.save()
-		subscription.delete()
-
 	def test_subscription_unpaid_after_grace_period(self):
 		settings = frappe.get_single("Subscription Settings")
 		default_grace_period_action = settings.cancel_after_grace
@@ -248,21 +248,26 @@
 
 		settings.cancel_after_grace = default_grace_period_action
 		settings.save()
-		subscription.delete()
 
 	def test_subscription_invoice_days_until_due(self):
+		_date = add_months(nowdate(), -1)
 		subscription = frappe.new_doc("Subscription")
 		subscription.party_type = "Customer"
 		subscription.party = "_Test Customer"
-		subscription.append("plans", {"plan": "_Test Plan Name", "qty": 1})
 		subscription.days_until_due = 10
-		subscription.start_date = add_months(nowdate(), -1)
+		subscription.start_date = _date
+		subscription.append("plans", {"plan": "_Test Plan Name", "qty": 1})
 		subscription.insert()
+
+		frappe.flags.current_date = subscription.current_invoice_end
+
 		subscription.process()  # generate first invoice
 		self.assertEqual(len(subscription.invoices), 1)
 		self.assertEqual(subscription.status, "Active")
 
-		subscription.delete()
+		frappe.flags.current_date = add_days(subscription.current_invoice_end, 3)
+		self.assertEqual(len(subscription.invoices), 1)
+		self.assertEqual(subscription.status, "Active")
 
 	def test_subscription_is_past_due_doesnt_change_within_grace_period(self):
 		settings = frappe.get_single("Subscription Settings")
@@ -276,6 +281,8 @@
 		subscription.append("plans", {"plan": "_Test Plan Name", "qty": 1})
 		subscription.start_date = add_days(nowdate(), -1000)
 		subscription.insert()
+
+		frappe.flags.current_date = subscription.current_invoice_end
 		subscription.process()  # generate first invoice
 
 		self.assertEqual(subscription.status, "Past Due Date")
@@ -292,7 +299,6 @@
 
 		settings.grace_period = grace_period
 		settings.save()
-		subscription.delete()
 
 	def test_subscription_remains_active_during_invoice_period(self):
 		subscription = frappe.new_doc("Subscription")
@@ -319,8 +325,6 @@
 		self.assertEqual(subscription.current_invoice_end, add_to_date(nowdate(), months=1, days=-1))
 		self.assertEqual(len(subscription.invoices), 0)
 
-		subscription.delete()
-
 	def test_subscription_cancelation(self):
 		subscription = frappe.new_doc("Subscription")
 		subscription.party_type = "Customer"
@@ -331,8 +335,6 @@
 
 		self.assertEqual(subscription.status, "Cancelled")
 
-		subscription.delete()
-
 	def test_subscription_cancellation_invoices(self):
 		settings = frappe.get_single("Subscription Settings")
 		to_prorate = settings.prorate
@@ -372,7 +374,6 @@
 		self.assertEqual(flt(invoice.grand_total, 2), flt(prorate_factor * 900, 2))
 		self.assertEqual(subscription.status, "Cancelled")
 
-		subscription.delete()
 		settings.prorate = to_prorate
 		settings.save()
 
@@ -395,8 +396,6 @@
 		settings.prorate = to_prorate
 		settings.save()
 
-		subscription.delete()
-
 	def test_subscription_cancellation_invoices_with_prorata_true(self):
 		settings = frappe.get_single("Subscription Settings")
 		to_prorate = settings.prorate
@@ -422,8 +421,6 @@
 		settings.prorate = to_prorate
 		settings.save()
 
-		subscription.delete()
-
 	def test_subcription_cancellation_and_process(self):
 		settings = frappe.get_single("Subscription Settings")
 		default_grace_period_action = settings.cancel_after_grace
@@ -437,23 +434,22 @@
 		subscription.start_date = "2018-01-01"
 		subscription.insert()
 		subscription.process()  # generate first invoice
-		invoices = len(subscription.invoices)
 
+		# Generate an invoice for the cancelled period
 		subscription.cancel_subscription()
 		self.assertEqual(subscription.status, "Cancelled")
-		self.assertEqual(len(subscription.invoices), invoices)
+		self.assertEqual(len(subscription.invoices), 1)
 
 		subscription.process()
 		self.assertEqual(subscription.status, "Cancelled")
-		self.assertEqual(len(subscription.invoices), invoices)
+		self.assertEqual(len(subscription.invoices), 1)
 
 		subscription.process()
 		self.assertEqual(subscription.status, "Cancelled")
-		self.assertEqual(len(subscription.invoices), invoices)
+		self.assertEqual(len(subscription.invoices), 1)
 
 		settings.cancel_after_grace = default_grace_period_action
 		settings.save()
-		subscription.delete()
 
 	def test_subscription_restart_and_process(self):
 		settings = frappe.get_single("Subscription Settings")
@@ -468,6 +464,7 @@
 		subscription.append("plans", {"plan": "_Test Plan Name", "qty": 1})
 		subscription.start_date = "2018-01-01"
 		subscription.insert()
+		frappe.flags.current_date = "2018-01-31"
 		subscription.process()  # generate first invoice
 
 		# Status is unpaid as Days until Due is zero and grace period is Zero
@@ -478,19 +475,18 @@
 
 		subscription.restart_subscription()
 		self.assertEqual(subscription.status, "Active")
-		self.assertEqual(len(subscription.invoices), 0)
+		self.assertEqual(len(subscription.invoices), 1)
 
 		subscription.process()
-		self.assertEqual(subscription.status, "Active")
-		self.assertEqual(len(subscription.invoices), 0)
+		self.assertEqual(subscription.status, "Unpaid")
+		self.assertEqual(len(subscription.invoices), 1)
 
 		subscription.process()
-		self.assertEqual(subscription.status, "Active")
-		self.assertEqual(len(subscription.invoices), 0)
+		self.assertEqual(subscription.status, "Unpaid")
+		self.assertEqual(len(subscription.invoices), 1)
 
 		settings.cancel_after_grace = default_grace_period_action
 		settings.save()
-		subscription.delete()
 
 	def test_subscription_unpaid_back_to_active(self):
 		settings = frappe.get_single("Subscription Settings")
@@ -503,8 +499,11 @@
 		subscription.party = "_Test Customer"
 		subscription.append("plans", {"plan": "_Test Plan Name", "qty": 1})
 		subscription.start_date = "2018-01-01"
+		subscription.generate_invoice_at_period_start = True
 		subscription.insert()
 
+		frappe.flags.current_date = subscription.current_invoice_start
+
 		subscription.process()  # generate first invoice
 		# This should change status to Unpaid since grace period is 0
 		self.assertEqual(subscription.status, "Unpaid")
@@ -517,12 +516,12 @@
 		self.assertEqual(subscription.status, "Active")
 
 		# A new invoice is generated
+		frappe.flags.current_date = subscription.current_invoice_start
 		subscription.process()
 		self.assertEqual(subscription.status, "Unpaid")
 
 		settings.cancel_after_grace = default_grace_period_action
 		settings.save()
-		subscription.delete()
 
 	def test_restart_active_subscription(self):
 		subscription = frappe.new_doc("Subscription")
@@ -533,8 +532,6 @@
 
 		self.assertRaises(frappe.ValidationError, subscription.restart_subscription)
 
-		subscription.delete()
-
 	def test_subscription_invoice_discount_percentage(self):
 		subscription = frappe.new_doc("Subscription")
 		subscription.party_type = "Customer"
@@ -549,8 +546,6 @@
 		self.assertEqual(invoice.additional_discount_percentage, 10)
 		self.assertEqual(invoice.apply_discount_on, "Grand Total")
 
-		subscription.delete()
-
 	def test_subscription_invoice_discount_amount(self):
 		subscription = frappe.new_doc("Subscription")
 		subscription.party_type = "Customer"
@@ -565,8 +560,6 @@
 		self.assertEqual(invoice.discount_amount, 11)
 		self.assertEqual(invoice.apply_discount_on, "Grand Total")
 
-		subscription.delete()
-
 	def test_prepaid_subscriptions(self):
 		# Create a non pre-billed subscription, processing should not create
 		# invoices.
@@ -614,8 +607,6 @@
 		settings.prorate = to_prorate
 		settings.save()
 
-		subscription.delete()
-
 	def test_subscription_with_follow_calendar_months(self):
 		subscription = frappe.new_doc("Subscription")
 		subscription.party_type = "Supplier"
@@ -623,14 +614,14 @@
 		subscription.generate_invoice_at_period_start = 1
 		subscription.follow_calendar_months = 1
 
-		# select subscription start date as '2018-01-15'
+		# select subscription start date as "2018-01-15"
 		subscription.start_date = "2018-01-15"
 		subscription.end_date = "2018-07-15"
 		subscription.append("plans", {"plan": "_Test Plan Name 4", "qty": 1})
 		subscription.save()
 
-		# even though subscription starts at '2018-01-15' and Billing interval is Month and count 3
-		# First invoice will end at '2018-03-31' instead of '2018-04-14'
+		# even though subscription starts at "2018-01-15" and Billing interval is Month and count 3
+		# First invoice will end at "2018-03-31" instead of "2018-04-14"
 		self.assertEqual(get_date_str(subscription.current_invoice_end), "2018-03-31")
 
 	def test_subscription_generate_invoice_past_due(self):
@@ -639,11 +630,12 @@
 		subscription.party = "_Test Supplier"
 		subscription.generate_invoice_at_period_start = 1
 		subscription.generate_new_invoices_past_due_date = 1
-		# select subscription start date as '2018-01-15'
+		# select subscription start date as "2018-01-15"
 		subscription.start_date = "2018-01-01"
 		subscription.append("plans", {"plan": "_Test Plan Name 4", "qty": 1})
 		subscription.save()
 
+		frappe.flags.current_date = "2018-01-01"
 		# Process subscription and create first invoice
 		# Subscription status will be unpaid since due date has already passed
 		subscription.process()
@@ -652,8 +644,8 @@
 
 		# Now the Subscription is unpaid
 		# Even then new invoice should be created as we have enabled `generate_new_invoices_past_due_date` in
-		# subscription
-
+		# subscription and the interval between the subscriptions is 3 months
+		frappe.flags.current_date = "2018-04-01"
 		subscription.process()
 		self.assertEqual(len(subscription.invoices), 2)
 
@@ -662,7 +654,7 @@
 		subscription.party_type = "Supplier"
 		subscription.party = "_Test Supplier"
 		subscription.generate_invoice_at_period_start = 1
-		# select subscription start date as '2018-01-15'
+		# select subscription start date as "2018-01-15"
 		subscription.start_date = "2018-01-01"
 		subscription.append("plans", {"plan": "_Test Plan Name 4", "qty": 1})
 		subscription.save()
@@ -682,7 +674,7 @@
 		subscription.party = "_Test Subscription Customer"
 		subscription.generate_invoice_at_period_start = 1
 		subscription.company = "_Test Company"
-		# select subscription start date as '2018-01-15'
+		# select subscription start date as "2018-01-15"
 		subscription.start_date = "2018-01-01"
 		subscription.append("plans", {"plan": "_Test Plan Multicurrency", "qty": 1})
 		subscription.save()
@@ -692,5 +684,47 @@
 		self.assertEqual(subscription.status, "Unpaid")
 
 		# Check the currency of the created invoice
-		currency = frappe.db.get_value("Sales Invoice", subscription.invoices[0].invoice, "currency")
+		currency = frappe.db.get_value("Sales Invoice", subscription.invoices[0].name, "currency")
 		self.assertEqual(currency, "USD")
+
+	def test_subscription_recovery(self):
+		"""Test if Subscription recovers when start/end date run out of sync with created invoices."""
+		subscription = frappe.new_doc("Subscription")
+		subscription.party_type = "Customer"
+		subscription.party = "_Test Subscription Customer"
+		subscription.company = "_Test Company"
+		subscription.start_date = "2021-12-01"
+		subscription.generate_new_invoices_past_due_date = 1
+		subscription.append("plans", {"plan": "_Test Plan Name", "qty": 1})
+		subscription.submit_invoice = 0
+		subscription.save()
+
+		# create invoices for the first two moths
+		frappe.flags.current_date = "2021-12-31"
+		subscription.process()
+
+		frappe.flags.current_date = "2022-01-31"
+		subscription.process()
+
+		self.assertEqual(len(subscription.invoices), 2)
+		self.assertEqual(
+			getdate(frappe.db.get_value("Sales Invoice", subscription.invoices[0].name, "from_date")),
+			getdate("2021-12-01"),
+		)
+		self.assertEqual(
+			getdate(frappe.db.get_value("Sales Invoice", subscription.invoices[1].name, "from_date")),
+			getdate("2022-01-01"),
+		)
+
+		# recreate most recent invoice
+		subscription.process()
+
+		self.assertEqual(len(subscription.invoices), 2)
+		self.assertEqual(
+			getdate(frappe.db.get_value("Sales Invoice", subscription.invoices[0].name, "from_date")),
+			getdate("2021-12-01"),
+		)
+		self.assertEqual(
+			getdate(frappe.db.get_value("Sales Invoice", subscription.invoices[1].name, "from_date")),
+			getdate("2022-01-01"),
+		)
diff --git a/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py b/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py
index 58792d1..d17ca08 100644
--- a/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py
+++ b/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py
@@ -262,14 +262,20 @@
 		if tax_deducted:
 			net_total = inv.tax_withholding_net_total
 			if ldc:
-				tax_amount = get_tds_amount_from_ldc(ldc, parties, tax_details, posting_date, net_total)
+				limit_consumed = get_limit_consumed(ldc, parties)
+				if is_valid_certificate(ldc, posting_date, limit_consumed):
+					tax_amount = get_lower_deduction_amount(
+						net_total, limit_consumed, ldc.certificate_limit, ldc.rate, tax_details
+					)
+				else:
+					tax_amount = net_total * tax_details.rate / 100 if net_total > 0 else 0
 			else:
 				tax_amount = net_total * tax_details.rate / 100 if net_total > 0 else 0
 
 			# once tds is deducted, not need to add vouchers in the invoice
 			voucher_wise_amount = {}
 		else:
-			tax_amount = get_tds_amount(ldc, parties, inv, tax_details, tax_deducted, vouchers)
+			tax_amount = get_tds_amount(ldc, parties, inv, tax_details, vouchers)
 
 	elif party_type == "Customer":
 		if tax_deducted:
@@ -416,7 +422,7 @@
 	return sum(entries)
 
 
-def get_tds_amount(ldc, parties, inv, tax_details, tax_deducted, vouchers):
+def get_tds_amount(ldc, parties, inv, tax_details, vouchers):
 	tds_amount = 0
 	invoice_filters = {"name": ("in", vouchers), "docstatus": 1, "apply_tds": 1}
 
@@ -476,7 +482,12 @@
 	threshold = tax_details.get("threshold", 0)
 	cumulative_threshold = tax_details.get("cumulative_threshold", 0)
 
-	if (threshold and inv.tax_withholding_net_total >= threshold) or (
+	if inv.doctype != "Payment Entry":
+		tax_withholding_net_total = inv.base_tax_withholding_net_total
+	else:
+		tax_withholding_net_total = inv.tax_withholding_net_total
+
+	if (threshold and tax_withholding_net_total >= threshold) or (
 		cumulative_threshold and supp_credit_amt >= cumulative_threshold
 	):
 		if (cumulative_threshold and supp_credit_amt >= cumulative_threshold) and cint(
@@ -491,15 +502,10 @@
 			net_total += inv.tax_withholding_net_total
 			supp_credit_amt = net_total - cumulative_threshold
 
-		if ldc and is_valid_certificate(
-			ldc.valid_from,
-			ldc.valid_upto,
-			inv.get("posting_date") or inv.get("transaction_date"),
-			tax_deducted,
-			inv.tax_withholding_net_total,
-			ldc.certificate_limit,
-		):
-			tds_amount = get_ltds_amount(supp_credit_amt, 0, ldc.certificate_limit, ldc.rate, tax_details)
+		if ldc and is_valid_certificate(ldc, inv.get("posting_date") or inv.get("transaction_date"), 0):
+			tds_amount = get_lower_deduction_amount(
+				supp_credit_amt, 0, ldc.certificate_limit, ldc.rate, tax_details
+			)
 		else:
 			tds_amount = supp_credit_amt * tax_details.rate / 100 if supp_credit_amt > 0 else 0
 
@@ -577,8 +583,7 @@
 	return inv.grand_total - tcs_tax_row_amount
 
 
-def get_tds_amount_from_ldc(ldc, parties, tax_details, posting_date, net_total):
-	tds_amount = 0
+def get_limit_consumed(ldc, parties):
 	limit_consumed = frappe.db.get_value(
 		"Purchase Invoice",
 		{
@@ -592,37 +597,29 @@
 		"sum(tax_withholding_net_total)",
 	)
 
-	if is_valid_certificate(
-		ldc.valid_from, ldc.valid_upto, posting_date, limit_consumed, net_total, ldc.certificate_limit
-	):
-		tds_amount = get_ltds_amount(
-			net_total, limit_consumed, ldc.certificate_limit, ldc.rate, tax_details
-		)
-
-	return tds_amount
+	return limit_consumed
 
 
-def get_ltds_amount(current_amount, deducted_amount, certificate_limit, rate, tax_details):
-	if certificate_limit - flt(deducted_amount) - flt(current_amount) >= 0:
+def get_lower_deduction_amount(
+	current_amount, limit_consumed, certificate_limit, rate, tax_details
+):
+	if certificate_limit - flt(limit_consumed) - flt(current_amount) >= 0:
 		return current_amount * rate / 100
 	else:
-		ltds_amount = certificate_limit - flt(deducted_amount)
+		ltds_amount = certificate_limit - flt(limit_consumed)
 		tds_amount = current_amount - ltds_amount
 
 		return ltds_amount * rate / 100 + tds_amount * tax_details.rate / 100
 
 
-def is_valid_certificate(
-	valid_from, valid_upto, posting_date, deducted_amount, current_amount, certificate_limit
-):
-	valid = False
+def is_valid_certificate(ldc, posting_date, limit_consumed):
+	available_amount = flt(ldc.certificate_limit) - flt(limit_consumed)
+	if (
+		getdate(ldc.valid_from) <= getdate(posting_date) <= getdate(ldc.valid_upto)
+	) and available_amount > 0:
+		return True
 
-	available_amount = flt(certificate_limit) - flt(deducted_amount)
-
-	if (getdate(valid_from) <= getdate(posting_date) <= getdate(valid_upto)) and available_amount > 0:
-		valid = True
-
-	return valid
+	return False
 
 
 def normal_round(number):
diff --git a/erpnext/accounts/doctype/tax_withholding_category/test_tax_withholding_category.py b/erpnext/accounts/doctype/tax_withholding_category/test_tax_withholding_category.py
index 4580b13..0fbaf23 100644
--- a/erpnext/accounts/doctype/tax_withholding_category/test_tax_withholding_category.py
+++ b/erpnext/accounts/doctype/tax_withholding_category/test_tax_withholding_category.py
@@ -4,6 +4,7 @@
 import unittest
 
 import frappe
+from frappe.custom.doctype.custom_field.custom_field import create_custom_fields
 from frappe.utils import today
 
 from erpnext.accounts.utils import get_fiscal_year
@@ -17,6 +18,7 @@
 		# create relevant supplier, etc
 		create_records()
 		create_tax_withholding_category_records()
+		make_pan_no_field()
 
 	def tearDown(self):
 		cancel_invoices()
@@ -316,6 +318,42 @@
 		for d in reversed(orders):
 			d.cancel()
 
+	def test_tds_deduction_for_po_via_payment_entry(self):
+		from erpnext.accounts.doctype.payment_entry.payment_entry import get_payment_entry
+
+		frappe.db.set_value(
+			"Supplier", "Test TDS Supplier8", "tax_withholding_category", "Cumulative Threshold TDS"
+		)
+		order = create_purchase_order(supplier="Test TDS Supplier8", rate=40000, do_not_save=True)
+
+		# Add some tax on the order
+		order.append(
+			"taxes",
+			{
+				"category": "Total",
+				"charge_type": "Actual",
+				"account_head": "_Test Account VAT - _TC",
+				"cost_center": "Main - _TC",
+				"tax_amount": 8000,
+				"description": "Test",
+				"add_deduct_tax": "Add",
+			},
+		)
+
+		order.save()
+
+		order.apply_tds = 1
+		order.tax_withholding_category = "Cumulative Threshold TDS"
+		order.submit()
+
+		self.assertEqual(order.taxes[0].tax_amount, 4000)
+
+		payment = get_payment_entry(order.doctype, order.name)
+		payment.apply_tax_withholding_amount = 1
+		payment.tax_withholding_category = "Cumulative Threshold TDS"
+		payment.submit()
+		self.assertEqual(payment.taxes[0].tax_amount, 4000)
+
 	def test_multi_category_single_supplier(self):
 		frappe.db.set_value(
 			"Supplier", "Test TDS Supplier5", "tax_withholding_category", "Test Service Category"
@@ -415,6 +453,40 @@
 		pe2.cancel()
 		pe3.cancel()
 
+	def test_lower_deduction_certificate_application(self):
+		frappe.db.set_value(
+			"Supplier",
+			"Test LDC Supplier",
+			{
+				"tax_withholding_category": "Test Service Category",
+				"pan": "ABCTY1234D",
+			},
+		)
+
+		create_lower_deduction_certificate(
+			supplier="Test LDC Supplier",
+			certificate_no="1AE0423AAJ",
+			tax_withholding_category="Test Service Category",
+			tax_rate=2,
+			limit=50000,
+		)
+
+		pi1 = create_purchase_invoice(supplier="Test LDC Supplier", rate=35000)
+		pi1.submit()
+		self.assertEqual(pi1.taxes[0].tax_amount, 700)
+
+		pi2 = create_purchase_invoice(supplier="Test LDC Supplier", rate=35000)
+		pi2.submit()
+		self.assertEqual(pi2.taxes[0].tax_amount, 2300)
+
+		pi3 = create_purchase_invoice(supplier="Test LDC Supplier", rate=35000)
+		pi3.submit()
+		self.assertEqual(pi3.taxes[0].tax_amount, 3500)
+
+		pi1.cancel()
+		pi2.cancel()
+		pi3.cancel()
+
 
 def cancel_invoices():
 	purchase_invoices = frappe.get_all(
@@ -573,6 +645,8 @@
 		"Test TDS Supplier5",
 		"Test TDS Supplier6",
 		"Test TDS Supplier7",
+		"Test TDS Supplier8",
+		"Test LDC Supplier",
 	]:
 		if frappe.db.exists("Supplier", name):
 			continue
@@ -769,3 +843,39 @@
 				"accounts": [{"company": "_Test Company", "account": account}],
 			}
 		).insert()
+
+
+def create_lower_deduction_certificate(
+	supplier, tax_withholding_category, tax_rate, certificate_no, limit
+):
+	fiscal_year = get_fiscal_year(today(), company="_Test Company")
+	if not frappe.db.exists("Lower Deduction Certificate", certificate_no):
+		frappe.get_doc(
+			{
+				"doctype": "Lower Deduction Certificate",
+				"company": "_Test Company",
+				"supplier": supplier,
+				"certificate_no": certificate_no,
+				"tax_withholding_category": tax_withholding_category,
+				"fiscal_year": fiscal_year[0],
+				"valid_from": fiscal_year[1],
+				"valid_upto": fiscal_year[2],
+				"rate": tax_rate,
+				"certificate_limit": limit,
+			}
+		).insert()
+
+
+def make_pan_no_field():
+	pan_field = {
+		"Supplier": [
+			{
+				"fieldname": "pan",
+				"label": "PAN",
+				"fieldtype": "Data",
+				"translatable": 0,
+			}
+		]
+	}
+
+	create_custom_fields(pan_field, update=1)
diff --git a/erpnext/accounts/party.py b/erpnext/accounts/party.py
index 895c314..0d67752 100644
--- a/erpnext/accounts/party.py
+++ b/erpnext/accounts/party.py
@@ -707,6 +707,7 @@
 	if party_type not in ("Customer", "Supplier"):
 		return
 	template = None
+
 	if party_type == "Customer":
 		customer = frappe.get_cached_value(
 			"Customer", party_name, fieldname=["payment_terms", "customer_group"], as_dict=1
diff --git a/erpnext/accounts/report/accounts_receivable/accounts_receivable.py b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py
index 11bbb6f..f78a840 100755
--- a/erpnext/accounts/report/accounts_receivable/accounts_receivable.py
+++ b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py
@@ -436,12 +436,11 @@
 	def allocate_outstanding_based_on_payment_terms(self, row):
 		self.get_payment_terms(row)
 		for term in row.payment_terms:
-
-			# update "paid" and "oustanding" for this term
+			# update "paid" and "outstanding" for this term
 			if not term.paid:
 				self.allocate_closing_to_term(row, term, "paid")
 
-			# update "credit_note" and "oustanding" for this term
+			# update "credit_note" and "outstanding" for this term
 			if term.outstanding:
 				self.allocate_closing_to_term(row, term, "credit_note")
 
@@ -453,7 +452,8 @@
 			"""
 			select
 				si.name, si.party_account_currency, si.currency, si.conversion_rate,
-				ps.due_date, ps.payment_term, ps.payment_amount, ps.description, ps.paid_amount, ps.discounted_amount
+				si.total_advance, ps.due_date, ps.payment_term, ps.payment_amount, ps.base_payment_amount,
+				ps.description, ps.paid_amount, ps.discounted_amount
 			from `tab{0}` si, `tabPayment Schedule` ps
 			where
 				si.name = ps.parent and
@@ -469,6 +469,10 @@
 		original_row = frappe._dict(row)
 		row.payment_terms = []
 
+		# Advance allocated during invoicing is not considered in payment terms
+		# Deduct that from paid amount pre allocation
+		row.paid -= flt(payment_terms_details[0].total_advance)
+
 		# If no or single payment terms, no need to split the row
 		if len(payment_terms_details) <= 1:
 			return
@@ -483,7 +487,7 @@
 		) and d.currency == d.party_account_currency:
 			invoiced = d.payment_amount
 		else:
-			invoiced = flt(flt(d.payment_amount) * flt(d.conversion_rate), self.currency_precision)
+			invoiced = d.base_payment_amount
 
 		row.payment_terms.append(
 			term.update(
diff --git a/erpnext/accounts/report/accounts_receivable_summary/accounts_receivable_summary.py b/erpnext/accounts/report/accounts_receivable_summary/accounts_receivable_summary.py
index 3aa1ae7..da4c9da 100644
--- a/erpnext/accounts/report/accounts_receivable_summary/accounts_receivable_summary.py
+++ b/erpnext/accounts/report/accounts_receivable_summary/accounts_receivable_summary.py
@@ -145,13 +145,13 @@
 	def get_columns(self):
 		self.columns = []
 		self.add_column(
-			label="Party Type",
+			label=_("Party Type"),
 			fieldname="party_type",
 			fieldtype="Data",
 			width=100,
 		)
 		self.add_column(
-			label="Party",
+			label=_("Party"),
 			fieldname="party",
 			fieldtype="Dynamic Link",
 			options="party_type",
@@ -160,7 +160,7 @@
 
 		if self.party_naming_by == "Naming Series":
 			self.add_column(
-				label="Supplier Name" if self.account_type == "Payable" else "Customer Name",
+				label=_("Supplier Name") if self.account_type == "Payable" else _("Customer Name"),
 				fieldname="party_name",
 				fieldtype="Data",
 			)
diff --git a/erpnext/accounts/report/financial_statements.py b/erpnext/accounts/report/financial_statements.py
index a76dea6..693725d 100644
--- a/erpnext/accounts/report/financial_statements.py
+++ b/erpnext/accounts/report/financial_statements.py
@@ -335,12 +335,10 @@
 			for period in period_list:
 				total_row.setdefault(period.key, 0.0)
 				total_row[period.key] += row.get(period.key, 0.0)
-				row[period.key] = row.get(period.key, 0.0)
 
 			total_row.setdefault("total", 0.0)
 			total_row["total"] += flt(row["total"])
 			total_row["opening_balance"] += row["opening_balance"]
-			row["total"] = ""
 
 	if "total" in total_row:
 		out.append(total_row)
@@ -639,7 +637,13 @@
 	if periodicity != "Yearly":
 		if not accumulated_values:
 			columns.append(
-				{"fieldname": "total", "label": _("Total"), "fieldtype": "Currency", "width": 150}
+				{
+					"fieldname": "total",
+					"label": _("Total"),
+					"fieldtype": "Currency",
+					"width": 150,
+					"options": "currency",
+				}
 			)
 
 	return columns
diff --git a/erpnext/accounts/report/general_and_payment_ledger_comparison/__init__.py b/erpnext/accounts/report/general_and_payment_ledger_comparison/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/erpnext/accounts/report/general_and_payment_ledger_comparison/__init__.py
diff --git a/erpnext/accounts/report/general_and_payment_ledger_comparison/general_and_payment_ledger_comparison.js b/erpnext/accounts/report/general_and_payment_ledger_comparison/general_and_payment_ledger_comparison.js
new file mode 100644
index 0000000..7e6b053
--- /dev/null
+++ b/erpnext/accounts/report/general_and_payment_ledger_comparison/general_and_payment_ledger_comparison.js
@@ -0,0 +1,52 @@
+// Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and contributors
+// For license information, please see license.txt
+
+function get_filters() {
+	let filters = [
+		{
+			"fieldname":"company",
+			"label": __("Company"),
+			"fieldtype": "Link",
+			"options": "Company",
+			"default": frappe.defaults.get_user_default("Company"),
+			"reqd": 1
+		},
+		{
+			"fieldname":"period_start_date",
+			"label": __("Start Date"),
+			"fieldtype": "Date",
+			"reqd": 1,
+			"default": frappe.datetime.add_months(frappe.datetime.get_today(), -1)
+		},
+		{
+			"fieldname":"period_end_date",
+			"label": __("End Date"),
+			"fieldtype": "Date",
+			"reqd": 1,
+			"default": frappe.datetime.get_today()
+		},
+		{
+			"fieldname":"account",
+			"label": __("Account"),
+			"fieldtype": "MultiSelectList",
+			"options": "Account",
+			get_data: function(txt) {
+				return frappe.db.get_link_options('Account', txt, {
+					company: frappe.query_report.get_filter_value("company"),
+					account_type: ['in', ["Receivable", "Payable"]]
+				});
+			}
+		},
+		{
+			"fieldname":"voucher_no",
+			"label": __("Voucher No"),
+			"fieldtype": "Data",
+			"width": 100,
+		},
+	]
+	return filters;
+}
+
+frappe.query_reports["General and Payment Ledger Comparison"] = {
+	"filters": get_filters()
+};
diff --git a/erpnext/accounts/report/general_and_payment_ledger_comparison/general_and_payment_ledger_comparison.json b/erpnext/accounts/report/general_and_payment_ledger_comparison/general_and_payment_ledger_comparison.json
new file mode 100644
index 0000000..1d0d9d1
--- /dev/null
+++ b/erpnext/accounts/report/general_and_payment_ledger_comparison/general_and_payment_ledger_comparison.json
@@ -0,0 +1,32 @@
+{
+ "add_total_row": 0,
+ "columns": [],
+ "creation": "2023-08-02 17:30:29.494907",
+ "disabled": 0,
+ "docstatus": 0,
+ "doctype": "Report",
+ "filters": [],
+ "idx": 0,
+ "is_standard": "Yes",
+ "letterhead": null,
+ "modified": "2023-08-02 17:30:29.494907",
+ "modified_by": "Administrator",
+ "module": "Accounts",
+ "name": "General and Payment Ledger Comparison",
+ "owner": "Administrator",
+ "prepared_report": 0,
+ "ref_doctype": "GL Entry",
+ "report_name": "General and Payment Ledger Comparison",
+ "report_type": "Script Report",
+ "roles": [
+  {
+   "role": "Accounts User"
+  },
+  {
+   "role": "Accounts Manager"
+  },
+  {
+   "role": "Auditor"
+  }
+ ]
+}
\ No newline at end of file
diff --git a/erpnext/accounts/report/general_and_payment_ledger_comparison/general_and_payment_ledger_comparison.py b/erpnext/accounts/report/general_and_payment_ledger_comparison/general_and_payment_ledger_comparison.py
new file mode 100644
index 0000000..553c137
--- /dev/null
+++ b/erpnext/accounts/report/general_and_payment_ledger_comparison/general_and_payment_ledger_comparison.py
@@ -0,0 +1,221 @@
+# Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and contributors
+# For license information, please see license.txt
+
+import frappe
+from frappe import _, qb
+from frappe.query_builder import Criterion
+from frappe.query_builder.functions import Sum
+
+
+class General_Payment_Ledger_Comparison(object):
+	"""
+	A Utility report to compare Voucher-wise balance between General and Payment Ledger
+	"""
+
+	def __init__(self, filters=None):
+		self.filters = filters
+		self.gle = []
+		self.ple = []
+
+	def get_accounts(self):
+		receivable_accounts = [
+			x[0]
+			for x in frappe.db.get_all(
+				"Account",
+				filters={"company": self.filters.company, "account_type": "Receivable"},
+				as_list=True,
+			)
+		]
+		payable_accounts = [
+			x[0]
+			for x in frappe.db.get_all(
+				"Account", filters={"company": self.filters.company, "account_type": "Payable"}, as_list=True
+			)
+		]
+
+		self.account_types = frappe._dict(
+			{
+				"receivable": frappe._dict({"accounts": receivable_accounts, "gle": [], "ple": []}),
+				"payable": frappe._dict({"accounts": payable_accounts, "gle": [], "ple": []}),
+			}
+		)
+
+	def generate_filters(self):
+		if self.filters.account:
+			self.account_types.receivable.accounts = []
+			self.account_types.payable.accounts = []
+
+			for acc in frappe.db.get_all(
+				"Account", filters={"name": ["in", self.filters.account]}, fields=["name", "account_type"]
+			):
+				if acc.account_type == "Receivable":
+					self.account_types.receivable.accounts.append(acc.name)
+				else:
+					self.account_types.payable.accounts.append(acc.name)
+
+	def get_gle(self):
+		gle = qb.DocType("GL Entry")
+
+		for acc_type, val in self.account_types.items():
+			if val.accounts:
+
+				filter_criterion = []
+				if self.filters.voucher_no:
+					filter_criterion.append((gle.voucher_no == self.filters.voucher_no))
+
+				if self.filters.period_start_date:
+					filter_criterion.append(gle.posting_date.gte(self.filters.period_start_date))
+
+				if self.filters.period_end_date:
+					filter_criterion.append(gle.posting_date.lte(self.filters.period_end_date))
+
+				if acc_type == "receivable":
+					outstanding = (Sum(gle.debit) - Sum(gle.credit)).as_("outstanding")
+				else:
+					outstanding = (Sum(gle.credit) - Sum(gle.debit)).as_("outstanding")
+
+				self.account_types[acc_type].gle = (
+					qb.from_(gle)
+					.select(
+						gle.company,
+						gle.account,
+						gle.voucher_no,
+						gle.party,
+						outstanding,
+					)
+					.where(
+						(gle.company == self.filters.company)
+						& (gle.is_cancelled == 0)
+						& (gle.account.isin(val.accounts))
+					)
+					.where(Criterion.all(filter_criterion))
+					.groupby(gle.company, gle.account, gle.voucher_no, gle.party)
+					.run()
+				)
+
+	def get_ple(self):
+		ple = qb.DocType("Payment Ledger Entry")
+
+		for acc_type, val in self.account_types.items():
+			if val.accounts:
+
+				filter_criterion = []
+				if self.filters.voucher_no:
+					filter_criterion.append((ple.voucher_no == self.filters.voucher_no))
+
+				if self.filters.period_start_date:
+					filter_criterion.append(ple.posting_date.gte(self.filters.period_start_date))
+
+				if self.filters.period_end_date:
+					filter_criterion.append(ple.posting_date.lte(self.filters.period_end_date))
+
+				self.account_types[acc_type].ple = (
+					qb.from_(ple)
+					.select(
+						ple.company, ple.account, ple.voucher_no, ple.party, Sum(ple.amount).as_("outstanding")
+					)
+					.where(
+						(ple.company == self.filters.company)
+						& (ple.delinked == 0)
+						& (ple.account.isin(val.accounts))
+					)
+					.where(Criterion.all(filter_criterion))
+					.groupby(ple.company, ple.account, ple.voucher_no, ple.party)
+					.run()
+				)
+
+	def compare(self):
+		self.gle_balances = set()
+		self.ple_balances = set()
+
+		# consolidate both receivable and payable balances in one set
+		for acc_type, val in self.account_types.items():
+			self.gle_balances = set(val.gle) | self.gle_balances
+			self.ple_balances = set(val.ple) | self.ple_balances
+
+		self.diff1 = self.gle_balances.difference(self.ple_balances)
+		self.diff2 = self.ple_balances.difference(self.gle_balances)
+		self.diff = frappe._dict({})
+
+		for x in self.diff1:
+			self.diff[(x[0], x[1], x[2], x[3])] = frappe._dict({"gl_balance": x[4]})
+
+		for x in self.diff2:
+			self.diff[(x[0], x[1], x[2], x[3])].update(frappe._dict({"pl_balance": x[4]}))
+
+	def generate_data(self):
+		self.data = []
+		for key, val in self.diff.items():
+			self.data.append(
+				frappe._dict(
+					{
+						"voucher_no": key[2],
+						"party": key[3],
+						"gl_balance": val.gl_balance,
+						"pl_balance": val.pl_balance,
+					}
+				)
+			)
+
+	def get_columns(self):
+		self.columns = []
+		options = None
+		self.columns.append(
+			dict(
+				label=_("Voucher No"),
+				fieldname="voucher_no",
+				fieldtype="Data",
+				options=options,
+				width="100",
+			)
+		)
+
+		self.columns.append(
+			dict(
+				label=_("Party"),
+				fieldname="party",
+				fieldtype="Data",
+				options=options,
+				width="100",
+			)
+		)
+
+		self.columns.append(
+			dict(
+				label=_("GL Balance"),
+				fieldname="gl_balance",
+				fieldtype="Currency",
+				options="Company:company:default_currency",
+				width="100",
+			)
+		)
+
+		self.columns.append(
+			dict(
+				label=_("Payment Ledger Balance"),
+				fieldname="pl_balance",
+				fieldtype="Currency",
+				options="Company:company:default_currency",
+				width="100",
+			)
+		)
+
+	def run(self):
+		self.get_accounts()
+		self.generate_filters()
+		self.get_gle()
+		self.get_ple()
+		self.compare()
+		self.generate_data()
+		self.get_columns()
+
+		return self.columns, self.data
+
+
+def execute(filters=None):
+	columns, data = [], []
+
+	rpt = General_Payment_Ledger_Comparison(filters)
+	columns, data = rpt.run()
+
+	return columns, data
diff --git a/erpnext/accounts/report/general_and_payment_ledger_comparison/test_general_and_payment_ledger_comparison.py b/erpnext/accounts/report/general_and_payment_ledger_comparison/test_general_and_payment_ledger_comparison.py
new file mode 100644
index 0000000..4b0e99d
--- /dev/null
+++ b/erpnext/accounts/report/general_and_payment_ledger_comparison/test_general_and_payment_ledger_comparison.py
@@ -0,0 +1,100 @@
+import unittest
+
+import frappe
+from frappe import qb
+from frappe.tests.utils import FrappeTestCase
+from frappe.utils import add_days
+
+from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice
+from erpnext.accounts.report.general_and_payment_ledger_comparison.general_and_payment_ledger_comparison import (
+	execute,
+)
+from erpnext.accounts.test.accounts_mixin import AccountsTestMixin
+
+
+class TestGeneralAndPaymentLedger(FrappeTestCase, AccountsTestMixin):
+	def setUp(self):
+		self.create_company()
+		self.cleanup()
+
+	def tearDown(self):
+		frappe.db.rollback()
+
+	def cleanup(self):
+		doctypes = []
+		doctypes.append(qb.DocType("GL Entry"))
+		doctypes.append(qb.DocType("Payment Ledger Entry"))
+		doctypes.append(qb.DocType("Sales Invoice"))
+
+		for doctype in doctypes:
+			qb.from_(doctype).delete().where(doctype.company == self.company).run()
+
+	def test_01_basic_report_functionality(self):
+		sinv = create_sales_invoice(
+			company=self.company,
+			debit_to=self.debit_to,
+			expense_account=self.expense_account,
+			cost_center=self.cost_center,
+			income_account=self.income_account,
+			warehouse=self.warehouse,
+		)
+
+		# manually edit the payment ledger entry
+		ple = frappe.db.get_all(
+			"Payment Ledger Entry", filters={"voucher_no": sinv.name, "delinked": 0}
+		)[0]
+		frappe.db.set_value("Payment Ledger Entry", ple.name, "amount", sinv.grand_total - 1)
+
+		filters = frappe._dict({"company": self.company})
+		columns, data = execute(filters=filters)
+		self.assertEqual(len(data), 1)
+
+		expected = {
+			"voucher_no": sinv.name,
+			"party": sinv.customer,
+			"gl_balance": sinv.grand_total,
+			"pl_balance": sinv.grand_total - 1,
+		}
+		self.assertEqual(expected, data[0])
+
+		# account filter
+		filters = frappe._dict({"company": self.company, "account": self.debit_to})
+		columns, data = execute(filters=filters)
+		self.assertEqual(len(data), 1)
+		self.assertEqual(expected, data[0])
+
+		filters = frappe._dict({"company": self.company, "account": self.creditors})
+		columns, data = execute(filters=filters)
+		self.assertEqual([], data)
+
+		# voucher_no filter
+		filters = frappe._dict({"company": self.company, "voucher_no": sinv.name})
+		columns, data = execute(filters=filters)
+		self.assertEqual(len(data), 1)
+		self.assertEqual(expected, data[0])
+
+		filters = frappe._dict({"company": self.company, "voucher_no": sinv.name + "-1"})
+		columns, data = execute(filters=filters)
+		self.assertEqual([], data)
+
+		# date range filter
+		filters = frappe._dict(
+			{
+				"company": self.company,
+				"period_start_date": sinv.posting_date,
+				"period_end_date": sinv.posting_date,
+			}
+		)
+		columns, data = execute(filters=filters)
+		self.assertEqual(len(data), 1)
+		self.assertEqual(expected, data[0])
+
+		filters = frappe._dict(
+			{
+				"company": self.company,
+				"period_start_date": add_days(sinv.posting_date, -1),
+				"period_end_date": add_days(sinv.posting_date, -1),
+			}
+		)
+		columns, data = execute(filters=filters)
+		self.assertEqual([], data)
diff --git a/erpnext/accounts/report/tax_withholding_details/tax_withholding_details.js b/erpnext/accounts/report/tax_withholding_details/tax_withholding_details.js
index b66a555..8808165 100644
--- a/erpnext/accounts/report/tax_withholding_details/tax_withholding_details.js
+++ b/erpnext/accounts/report/tax_withholding_details/tax_withholding_details.js
@@ -33,7 +33,14 @@
 					frappe.throw(__("Please select Party Type first"));
 				}
 				return party_type;
-			}
+			},
+			"get_query": function() {
+				return {
+					"filters": {
+						"tax_withholding_category": ["!=",""],
+					}
+				}
+			},
 		},
 		{
 			"fieldname":"from_date",
diff --git a/erpnext/accounts/report/tax_withholding_details/tax_withholding_details.py b/erpnext/accounts/report/tax_withholding_details/tax_withholding_details.py
index ddd049a..7d16661 100644
--- a/erpnext/accounts/report/tax_withholding_details/tax_withholding_details.py
+++ b/erpnext/accounts/report/tax_withholding_details/tax_withholding_details.py
@@ -7,19 +7,26 @@
 
 
 def execute(filters=None):
+	if filters.get("party_type") == "Customer":
+		party_naming_by = frappe.db.get_single_value("Selling Settings", "cust_master_name")
+	else:
+		party_naming_by = frappe.db.get_single_value("Buying Settings", "supp_master_name")
+
+	filters.update({"naming_series": party_naming_by})
+
 	validate_filters(filters)
 	(
 		tds_docs,
 		tds_accounts,
 		tax_category_map,
 		journal_entry_party_map,
-		invoice_net_total_map,
+		net_total_map,
 	) = get_tds_docs(filters)
 
 	columns = get_columns(filters)
 
 	res = get_result(
-		filters, tds_docs, tds_accounts, tax_category_map, journal_entry_party_map, invoice_net_total_map
+		filters, tds_docs, tds_accounts, tax_category_map, journal_entry_party_map, net_total_map
 	)
 	return columns, res
 
@@ -31,7 +38,7 @@
 
 
 def get_result(
-	filters, tds_docs, tds_accounts, tax_category_map, journal_entry_party_map, invoice_net_total_map
+	filters, tds_docs, tds_accounts, tax_category_map, journal_entry_party_map, net_total_map
 ):
 	party_map = get_party_pan_map(filters.get("party_type"))
 	tax_rate_map = get_tax_rate_map(filters)
@@ -39,7 +46,7 @@
 
 	out = []
 	for name, details in gle_map.items():
-		tax_amount, total_amount = 0, 0
+		tax_amount, total_amount, grand_total, base_total = 0, 0, 0, 0
 		tax_withholding_category = tax_category_map.get(name)
 		rate = tax_rate_map.get(tax_withholding_category)
 
@@ -60,8 +67,8 @@
 			if entry.account in tds_accounts:
 				tax_amount += entry.credit - entry.debit
 
-			if invoice_net_total_map.get(name):
-				total_amount = invoice_net_total_map.get(name)
+			if net_total_map.get(name):
+				total_amount, grand_total, base_total = net_total_map.get(name)
 			else:
 				total_amount += entry.credit
 
@@ -69,15 +76,13 @@
 			if party_map.get(party, {}).get("party_type") == "Supplier":
 				party_name = "supplier_name"
 				party_type = "supplier_type"
-				table_name = "Supplier"
 			else:
 				party_name = "customer_name"
 				party_type = "customer_type"
-				table_name = "Customer"
 
 			row = {
 				"pan"
-				if frappe.db.has_column(table_name, "pan")
+				if frappe.db.has_column(filters.party_type, "pan")
 				else "tax_id": party_map.get(party, {}).get("pan"),
 				"party": party_map.get(party, {}).get("name"),
 			}
@@ -91,6 +96,8 @@
 					"entity_type": party_map.get(party, {}).get(party_type),
 					"rate": rate,
 					"total_amount": total_amount,
+					"grand_total": grand_total,
+					"base_total": base_total,
 					"tax_amount": tax_amount,
 					"transaction_date": posting_date,
 					"transaction_type": voucher_type,
@@ -144,9 +151,9 @@
 
 
 def get_columns(filters):
-	pan = "pan" if frappe.db.has_column("Supplier", "pan") else "tax_id"
+	pan = "pan" if frappe.db.has_column(filters.party_type, "pan") else "tax_id"
 	columns = [
-		{"label": _(frappe.unscrub(pan)), "fieldname": pan, "fieldtype": "Data", "width": 90},
+		{"label": _(frappe.unscrub(pan)), "fieldname": pan, "fieldtype": "Data", "width": 60},
 		{
 			"label": _(filters.get("party_type")),
 			"fieldname": "party",
@@ -158,25 +165,30 @@
 
 	if filters.naming_series == "Naming Series":
 		columns.append(
-			{"label": _("Party Name"), "fieldname": "party_name", "fieldtype": "Data", "width": 180}
+			{
+				"label": _(filters.party_type + " Name"),
+				"fieldname": "party_name",
+				"fieldtype": "Data",
+				"width": 180,
+			}
 		)
 
 	columns.extend(
 		[
 			{
+				"label": _("Date of Transaction"),
+				"fieldname": "transaction_date",
+				"fieldtype": "Date",
+				"width": 100,
+			},
+			{
 				"label": _("Section Code"),
 				"options": "Tax Withholding Category",
 				"fieldname": "section_code",
 				"fieldtype": "Link",
-				"width": 180,
-			},
-			{"label": _("Entity Type"), "fieldname": "entity_type", "fieldtype": "Data", "width": 120},
-			{
-				"label": _("TDS Rate %") if filters.get("party_type") == "Supplier" else _("TCS Rate %"),
-				"fieldname": "rate",
-				"fieldtype": "Percent",
 				"width": 90,
 			},
+			{"label": _("Entity Type"), "fieldname": "entity_type", "fieldtype": "Data", "width": 100},
 			{
 				"label": _("Total Amount"),
 				"fieldname": "total_amount",
@@ -184,15 +196,27 @@
 				"width": 90,
 			},
 			{
-				"label": _("TDS Amount") if filters.get("party_type") == "Supplier" else _("TCS Amount"),
+				"label": _("TDS Rate %") if filters.get("party_type") == "Supplier" else _("TCS Rate %"),
+				"fieldname": "rate",
+				"fieldtype": "Percent",
+				"width": 90,
+			},
+			{
+				"label": _("Tax Amount"),
 				"fieldname": "tax_amount",
 				"fieldtype": "Float",
 				"width": 90,
 			},
 			{
-				"label": _("Date of Transaction"),
-				"fieldname": "transaction_date",
-				"fieldtype": "Date",
+				"label": _("Grand Total"),
+				"fieldname": "grand_total",
+				"fieldtype": "Float",
+				"width": 90,
+			},
+			{
+				"label": _("Base Total"),
+				"fieldname": "base_total",
+				"fieldtype": "Float",
 				"width": 90,
 			},
 			{"label": _("Transaction Type"), "fieldname": "transaction_type", "width": 100},
@@ -216,7 +240,7 @@
 	payment_entries = []
 	journal_entries = []
 	tax_category_map = frappe._dict()
-	invoice_net_total_map = frappe._dict()
+	net_total_map = frappe._dict()
 	or_filters = frappe._dict()
 	journal_entry_party_map = frappe._dict()
 	bank_accounts = frappe.get_all("Account", {"is_group": 0, "account_type": "Bank"}, pluck="name")
@@ -260,13 +284,13 @@
 		tds_documents.append(d.voucher_no)
 
 	if purchase_invoices:
-		get_doc_info(purchase_invoices, "Purchase Invoice", tax_category_map, invoice_net_total_map)
+		get_doc_info(purchase_invoices, "Purchase Invoice", tax_category_map, net_total_map)
 
 	if sales_invoices:
-		get_doc_info(sales_invoices, "Sales Invoice", tax_category_map, invoice_net_total_map)
+		get_doc_info(sales_invoices, "Sales Invoice", tax_category_map, net_total_map)
 
 	if payment_entries:
-		get_doc_info(payment_entries, "Payment Entry", tax_category_map)
+		get_doc_info(payment_entries, "Payment Entry", tax_category_map, net_total_map)
 
 	if journal_entries:
 		journal_entry_party_map = get_journal_entry_party_map(journal_entries)
@@ -277,7 +301,7 @@
 		tds_accounts,
 		tax_category_map,
 		journal_entry_party_map,
-		invoice_net_total_map,
+		net_total_map,
 	)
 
 
@@ -295,11 +319,25 @@
 	return journal_entry_party_map
 
 
-def get_doc_info(vouchers, doctype, tax_category_map, invoice_net_total_map=None):
+def get_doc_info(vouchers, doctype, tax_category_map, net_total_map=None):
 	if doctype == "Purchase Invoice":
-		fields = ["name", "tax_withholding_category", "base_tax_withholding_net_total"]
-	if doctype == "Sales Invoice":
-		fields = ["name", "base_net_total"]
+		fields = [
+			"name",
+			"tax_withholding_category",
+			"base_tax_withholding_net_total",
+			"grand_total",
+			"base_total",
+		]
+	elif doctype == "Sales Invoice":
+		fields = ["name", "base_net_total", "grand_total", "base_total"]
+	elif doctype == "Payment Entry":
+		fields = [
+			"name",
+			"tax_withholding_category",
+			"paid_amount",
+			"paid_amount_after_tax",
+			"base_paid_amount",
+		]
 	else:
 		fields = ["name", "tax_withholding_category"]
 
@@ -308,9 +346,15 @@
 	for entry in entries:
 		tax_category_map.update({entry.name: entry.tax_withholding_category})
 		if doctype == "Purchase Invoice":
-			invoice_net_total_map.update({entry.name: entry.base_tax_withholding_net_total})
-		if doctype == "Sales Invoice":
-			invoice_net_total_map.update({entry.name: entry.base_net_total})
+			net_total_map.update(
+				{entry.name: [entry.base_tax_withholding_net_total, entry.grand_total, entry.base_total]}
+			)
+		elif doctype == "Sales Invoice":
+			net_total_map.update({entry.name: [entry.base_net_total, entry.grand_total, entry.base_total]})
+		elif doctype == "Payment Entry":
+			net_total_map.update(
+				{entry.name: [entry.paid_amount, entry.paid_amount_after_tax, entry.base_paid_amount]}
+			)
 
 
 def get_tax_rate_map(filters):
diff --git a/erpnext/accounts/report/tds_computation_summary/tds_computation_summary.js b/erpnext/accounts/report/tds_computation_summary/tds_computation_summary.js
index d334846..a0be1b5 100644
--- a/erpnext/accounts/report/tds_computation_summary/tds_computation_summary.js
+++ b/erpnext/accounts/report/tds_computation_summary/tds_computation_summary.js
@@ -12,17 +12,35 @@
 			"default": frappe.defaults.get_default('company')
 		},
 		{
-			"fieldname":"supplier",
-			"label": __("Supplier"),
-			"fieldtype": "Link",
-			"options": "Supplier",
+			"fieldname":"party_type",
+			"label": __("Party Type"),
+			"fieldtype": "Select",
+			"options": ["Supplier", "Customer"],
+			"reqd": 1,
+			"default": "Supplier",
+			"on_change": function(){
+				frappe.query_report.set_filter_value("party", "");
+			}
+		},
+		{
+			"fieldname":"party",
+			"label": __("Party"),
+			"fieldtype": "Dynamic Link",
+			"get_options": function() {
+				var party_type = frappe.query_report.get_filter_value('party_type');
+				var party = frappe.query_report.get_filter_value('party');
+				if(party && !party_type) {
+					frappe.throw(__("Please select Party Type first"));
+				}
+				return party_type;
+			},
 			"get_query": function() {
 				return {
 					"filters": {
 						"tax_withholding_category": ["!=",""],
 					}
 				}
-			}
+			},
 		},
 		{
 			"fieldname":"from_date",
diff --git a/erpnext/accounts/report/tds_computation_summary/tds_computation_summary.py b/erpnext/accounts/report/tds_computation_summary/tds_computation_summary.py
index c6aa21c..82f97f1 100644
--- a/erpnext/accounts/report/tds_computation_summary/tds_computation_summary.py
+++ b/erpnext/accounts/report/tds_computation_summary/tds_computation_summary.py
@@ -9,9 +9,14 @@
 
 
 def execute(filters=None):
-	validate_filters(filters)
+	if filters.get("party_type") == "Customer":
+		party_naming_by = frappe.db.get_single_value("Selling Settings", "cust_master_name")
+	else:
+		party_naming_by = frappe.db.get_single_value("Buying Settings", "supp_master_name")
 
-	filters.naming_series = frappe.db.get_single_value("Buying Settings", "supp_master_name")
+	filters.update({"naming_series": party_naming_by})
+
+	validate_filters(filters)
 
 	columns = get_columns(filters)
 	(
@@ -25,7 +30,7 @@
 	res = get_result(
 		filters, tds_docs, tds_accounts, tax_category_map, journal_entry_party_map, invoice_total_map
 	)
-	final_result = group_by_supplier_and_category(res)
+	final_result = group_by_party_and_category(res, filters)
 
 	return columns, final_result
 
@@ -43,60 +48,67 @@
 	filters["fiscal_year"] = from_year
 
 
-def group_by_supplier_and_category(data):
-	supplier_category_wise_map = {}
+def group_by_party_and_category(data, filters):
+	party_category_wise_map = {}
 
 	for row in data:
-		supplier_category_wise_map.setdefault(
-			(row.get("supplier"), row.get("section_code")),
+		party_category_wise_map.setdefault(
+			(row.get("party"), row.get("section_code")),
 			{
 				"pan": row.get("pan"),
-				"supplier": row.get("supplier"),
-				"supplier_name": row.get("supplier_name"),
+				"tax_id": row.get("tax_id"),
+				"party": row.get("party"),
+				"party_name": row.get("party_name"),
 				"section_code": row.get("section_code"),
 				"entity_type": row.get("entity_type"),
-				"tds_rate": row.get("tds_rate"),
-				"total_amount_credited": 0.0,
-				"tds_deducted": 0.0,
+				"rate": row.get("rate"),
+				"total_amount": 0.0,
+				"tax_amount": 0.0,
 			},
 		)
 
-		supplier_category_wise_map.get((row.get("supplier"), row.get("section_code")))[
-			"total_amount_credited"
-		] += row.get("total_amount_credited", 0.0)
+		party_category_wise_map.get((row.get("party"), row.get("section_code")))[
+			"total_amount"
+		] += row.get("total_amount", 0.0)
 
-		supplier_category_wise_map.get((row.get("supplier"), row.get("section_code")))[
-			"tds_deducted"
-		] += row.get("tds_deducted", 0.0)
+		party_category_wise_map.get((row.get("party"), row.get("section_code")))[
+			"tax_amount"
+		] += row.get("tax_amount", 0.0)
 
-	final_result = get_final_result(supplier_category_wise_map)
+	final_result = get_final_result(party_category_wise_map)
 
 	return final_result
 
 
-def get_final_result(supplier_category_wise_map):
+def get_final_result(party_category_wise_map):
 	out = []
-	for key, value in supplier_category_wise_map.items():
+	for key, value in party_category_wise_map.items():
 		out.append(value)
 
 	return out
 
 
 def get_columns(filters):
+	pan = "pan" if frappe.db.has_column(filters.party_type, "pan") else "tax_id"
 	columns = [
-		{"label": _("PAN"), "fieldname": "pan", "fieldtype": "Data", "width": 90},
+		{"label": _(frappe.unscrub(pan)), "fieldname": pan, "fieldtype": "Data", "width": 90},
 		{
-			"label": _("Supplier"),
-			"options": "Supplier",
-			"fieldname": "supplier",
-			"fieldtype": "Link",
+			"label": _(filters.get("party_type")),
+			"fieldname": "party",
+			"fieldtype": "Dynamic Link",
+			"options": "party_type",
 			"width": 180,
 		},
 	]
 
 	if filters.naming_series == "Naming Series":
 		columns.append(
-			{"label": _("Supplier Name"), "fieldname": "supplier_name", "fieldtype": "Data", "width": 180}
+			{
+				"label": _(filters.party_type + " Name"),
+				"fieldname": "party_name",
+				"fieldtype": "Data",
+				"width": 180,
+			}
 		)
 
 	columns.extend(
@@ -109,18 +121,23 @@
 				"width": 180,
 			},
 			{"label": _("Entity Type"), "fieldname": "entity_type", "fieldtype": "Data", "width": 180},
-			{"label": _("TDS Rate %"), "fieldname": "tds_rate", "fieldtype": "Percent", "width": 90},
 			{
-				"label": _("Total Amount Credited"),
-				"fieldname": "total_amount_credited",
-				"fieldtype": "Float",
-				"width": 90,
+				"label": _("TDS Rate %") if filters.get("party_type") == "Supplier" else _("TCS Rate %"),
+				"fieldname": "rate",
+				"fieldtype": "Percent",
+				"width": 120,
 			},
 			{
-				"label": _("Amount of TDS Deducted"),
-				"fieldname": "tds_deducted",
+				"label": _("Total Amount"),
+				"fieldname": "total_amount",
 				"fieldtype": "Float",
-				"width": 90,
+				"width": 120,
+			},
+			{
+				"label": _("Tax Amount"),
+				"fieldname": "tax_amount",
+				"fieldtype": "Float",
+				"width": 120,
 			},
 		]
 	)
diff --git a/erpnext/accounts/test/accounts_mixin.py b/erpnext/accounts/test/accounts_mixin.py
index c82164e..70bbf7e 100644
--- a/erpnext/accounts/test/accounts_mixin.py
+++ b/erpnext/accounts/test/accounts_mixin.py
@@ -4,7 +4,7 @@
 
 
 class AccountsTestMixin:
-	def create_customer(self, customer_name, currency=None):
+	def create_customer(self, customer_name="_Test Customer", currency=None):
 		if not frappe.db.exists("Customer", customer_name):
 			customer = frappe.new_doc("Customer")
 			customer.customer_name = customer_name
@@ -17,7 +17,7 @@
 		else:
 			self.customer = customer_name
 
-	def create_supplier(self, supplier_name, currency=None):
+	def create_supplier(self, supplier_name="_Test Supplier", currency=None):
 		if not frappe.db.exists("Supplier", supplier_name):
 			supplier = frappe.new_doc("Supplier")
 			supplier.supplier_name = supplier_name
@@ -31,7 +31,7 @@
 		else:
 			self.supplier = supplier_name
 
-	def create_item(self, item_name, is_stock=0, warehouse=None, company=None):
+	def create_item(self, item_name="_Test Item", is_stock=0, warehouse=None, company=None):
 		item = create_item(item_name, is_stock_item=is_stock, warehouse=warehouse, company=company)
 		self.item = item.name
 
@@ -62,19 +62,44 @@
 		self.debit_usd = "Debtors USD - " + abbr
 		self.cash = "Cash - " + abbr
 		self.creditors = "Creditors - " + abbr
+		self.retained_earnings = "Retained Earnings - " + abbr
 
-		# create bank account
-		bank_account = "HDFC - " + abbr
-		if frappe.db.exists("Account", bank_account):
-			self.bank = bank_account
-		else:
-			bank_acc = frappe.get_doc(
+		# Deferred revenue, expense and bank accounts
+		other_accounts = [
+			frappe._dict(
 				{
-					"doctype": "Account",
+					"attribute_name": "deferred_revenue",
+					"account_name": "Deferred Revenue",
+					"parent_account": "Current Liabilities - " + abbr,
+				}
+			),
+			frappe._dict(
+				{
+					"attribute_name": "deferred_expense",
+					"account_name": "Deferred Expense",
+					"parent_account": "Current Assets - " + abbr,
+				}
+			),
+			frappe._dict(
+				{
+					"attribute_name": "bank",
 					"account_name": "HDFC",
 					"parent_account": "Bank Accounts - " + abbr,
-					"company": self.company,
 				}
-			)
-			bank_acc.save()
-			self.bank = bank_acc.name
+			),
+		]
+		for acc in other_accounts:
+			acc_name = acc.account_name + " - " + abbr
+			if frappe.db.exists("Account", acc_name):
+				setattr(self, acc.attribute_name, acc_name)
+			else:
+				new_acc = frappe.get_doc(
+					{
+						"doctype": "Account",
+						"account_name": acc.account_name,
+						"parent_account": acc.parent_account,
+						"company": self.company,
+					}
+				)
+				new_acc.save()
+				setattr(self, acc.attribute_name, new_acc.name)
diff --git a/erpnext/accounts/utils.py b/erpnext/accounts/utils.py
index c24442e..bccf6f1 100644
--- a/erpnext/accounts/utils.py
+++ b/erpnext/accounts/utils.py
@@ -908,6 +908,7 @@
 	min_outstanding=None,
 	max_outstanding=None,
 	accounting_dimensions=None,
+	vouchers=None,
 ):
 
 	ple = qb.DocType("Payment Ledger Entry")
@@ -933,6 +934,7 @@
 
 	ple_query = QueryPaymentLedger()
 	invoice_list = ple_query.get_voucher_outstandings(
+		vouchers=vouchers,
 		common_filter=common_filter,
 		posting_date=posting_date,
 		min_outstanding=min_outstanding,
diff --git a/erpnext/accounts/workspace/accounting/accounting.json b/erpnext/accounts/workspace/accounting/accounting.json
index c27ede2..dfdae1d 100644
--- a/erpnext/accounts/workspace/accounting/accounting.json
+++ b/erpnext/accounts/workspace/accounting/accounting.json
@@ -5,7 +5,7 @@
    "label": "Profit and Loss"
   }
  ],
- "content": "[{\"id\":\"MmUf9abwxg\",\"type\":\"onboarding\",\"data\":{\"onboarding_name\":\"Accounts\",\"col\":12}},{\"id\":\"i0EtSjDAXq\",\"type\":\"chart\",\"data\":{\"chart_name\":\"Profit and Loss\",\"col\":12}},{\"id\":\"X78jcbq1u3\",\"type\":\"spacer\",\"data\":{\"col\":12}},{\"id\":\"vikWSkNm6_\",\"type\":\"header\",\"data\":{\"text\":\"<span class=\\\"h4\\\"><b>Your Shortcuts</b></span>\",\"col\":12}},{\"id\":\"pMywM0nhlj\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Chart of Accounts\",\"col\":3}},{\"id\":\"_pRdD6kqUG\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Sales Invoice\",\"col\":3}},{\"id\":\"G984SgVRJN\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Purchase Invoice\",\"col\":3}},{\"id\":\"1ArNvt9qhz\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Journal Entry\",\"col\":3}},{\"id\":\"F9f4I1viNr\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Payment Entry\",\"col\":3}},{\"id\":\"4IBBOIxfqW\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Accounts Receivable\",\"col\":3}},{\"id\":\"El2anpPaFY\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"General Ledger\",\"col\":3}},{\"id\":\"1nwcM9upJo\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Trial Balance\",\"col\":3}},{\"id\":\"OF9WOi1Ppc\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Dashboard\",\"col\":3}},{\"id\":\"iAwpe-Chra\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Learn Accounting\",\"col\":3}},{\"id\":\"B7-uxs8tkU\",\"type\":\"spacer\",\"data\":{\"col\":12}},{\"id\":\"tHb3yxthkR\",\"type\":\"header\",\"data\":{\"text\":\"<span class=\\\"h4\\\"><b>Reports &amp; Masters</b></span>\",\"col\":12}},{\"id\":\"DnNtsmxpty\",\"type\":\"card\",\"data\":{\"card_name\":\"Accounting Masters\",\"col\":4}},{\"id\":\"nKKr6fjgjb\",\"type\":\"card\",\"data\":{\"card_name\":\"General Ledger\",\"col\":4}},{\"id\":\"xOHTyD8b5l\",\"type\":\"card\",\"data\":{\"card_name\":\"Accounts Receivable\",\"col\":4}},{\"id\":\"_Cb7C8XdJJ\",\"type\":\"card\",\"data\":{\"card_name\":\"Accounts Payable\",\"col\":4}},{\"id\":\"p7NY6MHe2Y\",\"type\":\"card\",\"data\":{\"card_name\":\"Financial Statements\",\"col\":4}},{\"id\":\"KlqilF5R_V\",\"type\":\"card\",\"data\":{\"card_name\":\"Taxes\",\"col\":4}},{\"id\":\"jTUy8LB0uw\",\"type\":\"card\",\"data\":{\"card_name\":\"Cost Center and Budgeting\",\"col\":4}},{\"id\":\"Wn2lhs7WLn\",\"type\":\"card\",\"data\":{\"card_name\":\"Multi Currency\",\"col\":4}},{\"id\":\"PAQMqqNkBM\",\"type\":\"card\",\"data\":{\"card_name\":\"Bank Statement\",\"col\":4}},{\"id\":\"Q_hBCnSeJY\",\"type\":\"card\",\"data\":{\"card_name\":\"Reports\",\"col\":4}},{\"id\":\"3AK1Zf0oew\",\"type\":\"card\",\"data\":{\"card_name\":\"Profitability\",\"col\":4}},{\"id\":\"kxhoaiqdLq\",\"type\":\"card\",\"data\":{\"card_name\":\"Opening and Closing\",\"col\":4}},{\"id\":\"q0MAlU2j_Z\",\"type\":\"card\",\"data\":{\"card_name\":\"Subscription Management\",\"col\":4}},{\"id\":\"ptm7T6Hwu-\",\"type\":\"card\",\"data\":{\"card_name\":\"Share Management\",\"col\":4}},{\"id\":\"OX7lZHbiTr\",\"type\":\"card\",\"data\":{\"card_name\":\"Settings\",\"col\":4}}]",
+ "content": "[{\"id\":\"MmUf9abwxg\",\"type\":\"onboarding\",\"data\":{\"onboarding_name\":\"Accounts\",\"col\":12}},{\"id\":\"VVvJ1lUcfc\",\"type\":\"number_card\",\"data\":{\"number_card_name\":\"Total Outgoing Bills\",\"col\":3}},{\"id\":\"Vlj2FZtlHV\",\"type\":\"number_card\",\"data\":{\"number_card_name\":\"Total Incoming Bills\",\"col\":3}},{\"id\":\"VVVjQVAhPf\",\"type\":\"number_card\",\"data\":{\"number_card_name\":\"Total Incoming Payment\",\"col\":3}},{\"id\":\"DySNdlysIW\",\"type\":\"number_card\",\"data\":{\"number_card_name\":\"Total Outgoing Payment\",\"col\":3}},{\"id\":\"i0EtSjDAXq\",\"type\":\"chart\",\"data\":{\"chart_name\":\"Profit and Loss\",\"col\":12}},{\"id\":\"X78jcbq1u3\",\"type\":\"spacer\",\"data\":{\"col\":12}},{\"id\":\"vikWSkNm6_\",\"type\":\"header\",\"data\":{\"text\":\"<span class=\\\"h4\\\"><b>Your Shortcuts</b></span>\",\"col\":12}},{\"id\":\"pMywM0nhlj\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Chart of Accounts\",\"col\":3}},{\"id\":\"_pRdD6kqUG\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Sales Invoice\",\"col\":3}},{\"id\":\"G984SgVRJN\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Purchase Invoice\",\"col\":3}},{\"id\":\"1ArNvt9qhz\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Journal Entry\",\"col\":3}},{\"id\":\"F9f4I1viNr\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Payment Entry\",\"col\":3}},{\"id\":\"4IBBOIxfqW\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Accounts Receivable\",\"col\":3}},{\"id\":\"El2anpPaFY\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"General Ledger\",\"col\":3}},{\"id\":\"1nwcM9upJo\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Trial Balance\",\"col\":3}},{\"id\":\"OF9WOi1Ppc\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Dashboard\",\"col\":3}},{\"id\":\"iAwpe-Chra\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Learn Accounting\",\"col\":3}},{\"id\":\"B7-uxs8tkU\",\"type\":\"spacer\",\"data\":{\"col\":12}},{\"id\":\"tHb3yxthkR\",\"type\":\"header\",\"data\":{\"text\":\"<span class=\\\"h4\\\"><b>Reports &amp; Masters</b></span>\",\"col\":12}},{\"id\":\"DnNtsmxpty\",\"type\":\"card\",\"data\":{\"card_name\":\"Accounting Masters\",\"col\":4}},{\"id\":\"nKKr6fjgjb\",\"type\":\"card\",\"data\":{\"card_name\":\"General Ledger\",\"col\":4}},{\"id\":\"xOHTyD8b5l\",\"type\":\"card\",\"data\":{\"card_name\":\"Accounts Receivable\",\"col\":4}},{\"id\":\"_Cb7C8XdJJ\",\"type\":\"card\",\"data\":{\"card_name\":\"Accounts Payable\",\"col\":4}},{\"id\":\"p7NY6MHe2Y\",\"type\":\"card\",\"data\":{\"card_name\":\"Financial Statements\",\"col\":4}},{\"id\":\"KlqilF5R_V\",\"type\":\"card\",\"data\":{\"card_name\":\"Taxes\",\"col\":4}},{\"id\":\"jTUy8LB0uw\",\"type\":\"card\",\"data\":{\"card_name\":\"Cost Center and Budgeting\",\"col\":4}},{\"id\":\"Wn2lhs7WLn\",\"type\":\"card\",\"data\":{\"card_name\":\"Multi Currency\",\"col\":4}},{\"id\":\"PAQMqqNkBM\",\"type\":\"card\",\"data\":{\"card_name\":\"Bank Statement\",\"col\":4}},{\"id\":\"Q_hBCnSeJY\",\"type\":\"card\",\"data\":{\"card_name\":\"Reports\",\"col\":4}},{\"id\":\"3AK1Zf0oew\",\"type\":\"card\",\"data\":{\"card_name\":\"Profitability\",\"col\":4}},{\"id\":\"kxhoaiqdLq\",\"type\":\"card\",\"data\":{\"card_name\":\"Opening and Closing\",\"col\":4}},{\"id\":\"q0MAlU2j_Z\",\"type\":\"card\",\"data\":{\"card_name\":\"Subscription Management\",\"col\":4}},{\"id\":\"ptm7T6Hwu-\",\"type\":\"card\",\"data\":{\"card_name\":\"Share Management\",\"col\":4}},{\"id\":\"OX7lZHbiTr\",\"type\":\"card\",\"data\":{\"card_name\":\"Settings\",\"col\":4}}]",
  "creation": "2020-03-02 15:41:59.515192",
  "custom_blocks": [],
  "docstatus": 0,
@@ -1061,11 +1061,28 @@
    "type": "Link"
   }
  ],
- "modified": "2023-07-04 14:32:15.842044",
+ "modified": "2023-08-10 17:41:14.059005",
  "modified_by": "Administrator",
  "module": "Accounts",
  "name": "Accounting",
- "number_cards": [],
+ "number_cards": [
+  {
+   "label": "Total Outgoing Bills",
+   "number_card_name": "Total Outgoing Bills"
+  },
+  {
+   "label": "Total Incoming Bills",
+   "number_card_name": "Total Incoming Bills"
+  },
+  {
+   "label": "Total Incoming Payment",
+   "number_card_name": "Total Incoming Payment"
+  },
+  {
+   "label": "Total Outgoing Payment",
+   "number_card_name": "Total Outgoing Payment"
+  }
+ ],
  "owner": "Administrator",
  "parent_page": "",
  "public": 1,
diff --git a/erpnext/assets/doctype/asset/asset.py b/erpnext/assets/doctype/asset/asset.py
index 04ec7be..2060c6c 100644
--- a/erpnext/assets/doctype/asset/asset.py
+++ b/erpnext/assets/doctype/asset/asset.py
@@ -478,7 +478,9 @@
 
 	@frappe.whitelist()
 	def get_manual_depreciation_entries(self):
-		(_, _, depreciation_expense_account) = get_depreciation_accounts(self)
+		(_, _, depreciation_expense_account) = get_depreciation_accounts(
+			self.asset_category, self.company
+		)
 
 		gle = frappe.qb.DocType("GL Entry")
 
@@ -821,10 +823,10 @@
 def make_journal_entry(asset_name):
 	asset = frappe.get_doc("Asset", asset_name)
 	(
-		fixed_asset_account,
+		_,
 		accumulated_depreciation_account,
 		depreciation_expense_account,
-	) = get_depreciation_accounts(asset)
+	) = get_depreciation_accounts(asset.asset_category, asset.company)
 
 	depreciation_cost_center, depreciation_series = frappe.get_cached_value(
 		"Company", asset.company, ["depreciation_cost_center", "series_for_depreciation_entry"]
diff --git a/erpnext/assets/doctype/asset/depreciation.py b/erpnext/assets/doctype/asset/depreciation.py
index 0588065..e2a4b29 100644
--- a/erpnext/assets/doctype/asset/depreciation.py
+++ b/erpnext/assets/doctype/asset/depreciation.py
@@ -4,6 +4,8 @@
 
 import frappe
 from frappe import _
+from frappe.query_builder import Order
+from frappe.query_builder.functions import Max, Min
 from frappe.utils import (
 	add_months,
 	cint,
@@ -43,11 +45,48 @@
 	failed_asset_names = []
 	error_log_names = []
 
-	for asset_name in get_depreciable_assets(date):
-		asset_doc = frappe.get_doc("Asset", asset_name)
+	depreciable_asset_depr_schedules_data = get_depreciable_asset_depr_schedules_data(date)
+
+	credit_and_debit_accounts_for_asset_category_and_company = {}
+	depreciation_cost_center_and_depreciation_series_for_company = (
+		get_depreciation_cost_center_and_depreciation_series_for_company()
+	)
+
+	accounting_dimensions = get_checks_for_pl_and_bs_accounts()
+
+	for asset_depr_schedule_data in depreciable_asset_depr_schedules_data:
+		(
+			asset_depr_schedule_name,
+			asset_name,
+			asset_category,
+			asset_company,
+			sch_start_idx,
+			sch_end_idx,
+		) = asset_depr_schedule_data
+
+		if (
+			asset_category,
+			asset_company,
+		) not in credit_and_debit_accounts_for_asset_category_and_company:
+			credit_and_debit_accounts_for_asset_category_and_company.update(
+				{
+					(asset_category, asset_company): get_credit_and_debit_accounts_for_asset_category_and_company(
+						asset_category, asset_company
+					),
+				}
+			)
 
 		try:
-			make_depreciation_entry_for_all_asset_depr_schedules(asset_doc, date)
+			make_depreciation_entry(
+				asset_depr_schedule_name,
+				date,
+				sch_start_idx,
+				sch_end_idx,
+				credit_and_debit_accounts_for_asset_category_and_company[(asset_category, asset_company)],
+				depreciation_cost_center_and_depreciation_series_for_company[asset_company],
+				accounting_dimensions,
+			)
+
 			frappe.db.commit()
 		except Exception as e:
 			frappe.db.rollback()
@@ -62,18 +101,36 @@
 	frappe.db.commit()
 
 
-def get_depreciable_assets(date):
-	return frappe.db.sql_list(
-		"""select distinct a.name
-		from tabAsset a, `tabAsset Depreciation Schedule` ads, `tabDepreciation Schedule` ds
-		where a.name = ads.asset and ads.name = ds.parent and a.docstatus=1 and ads.docstatus=1
-			and a.status in ('Submitted', 'Partially Depreciated')
-			and a.calculate_depreciation = 1
-			and ds.schedule_date<=%s
-			and ifnull(ds.journal_entry, '')=''""",
-		date,
+def get_depreciable_asset_depr_schedules_data(date):
+	a = frappe.qb.DocType("Asset")
+	ads = frappe.qb.DocType("Asset Depreciation Schedule")
+	ds = frappe.qb.DocType("Depreciation Schedule")
+
+	res = (
+		frappe.qb.from_(ads)
+		.join(a)
+		.on(ads.asset == a.name)
+		.join(ds)
+		.on(ads.name == ds.parent)
+		.select(ads.name, a.name, a.asset_category, a.company, Min(ds.idx) - 1, Max(ds.idx))
+		.where(a.calculate_depreciation == 1)
+		.where(a.docstatus == 1)
+		.where(ads.docstatus == 1)
+		.where(a.status.isin(["Submitted", "Partially Depreciated"]))
+		.where(ds.journal_entry.isnull())
+		.where(ds.schedule_date <= date)
+		.groupby(ads.name)
+		.orderby(a.creation, order=Order.desc)
 	)
 
+	acc_frozen_upto = get_acc_frozen_upto()
+	if acc_frozen_upto:
+		res = res.where(ds.schedule_date > acc_frozen_upto)
+
+	res = res.run()
+
+	return res
+
 
 def make_depreciation_entry_for_all_asset_depr_schedules(asset_doc, date=None):
 	for row in asset_doc.get("finance_books"):
@@ -83,8 +140,60 @@
 		make_depreciation_entry(asset_depr_schedule_name, date)
 
 
+def get_acc_frozen_upto():
+	acc_frozen_upto = frappe.db.get_single_value("Accounts Settings", "acc_frozen_upto")
+
+	if not acc_frozen_upto:
+		return
+
+	frozen_accounts_modifier = frappe.db.get_single_value(
+		"Accounts Settings", "frozen_accounts_modifier"
+	)
+
+	if frozen_accounts_modifier not in frappe.get_roles() or frappe.session.user == "Administrator":
+		return getdate(acc_frozen_upto)
+
+	return
+
+
+def get_credit_and_debit_accounts_for_asset_category_and_company(asset_category, company):
+	(
+		_,
+		accumulated_depreciation_account,
+		depreciation_expense_account,
+	) = get_depreciation_accounts(asset_category, company)
+
+	credit_account, debit_account = get_credit_and_debit_accounts(
+		accumulated_depreciation_account, depreciation_expense_account
+	)
+
+	return (credit_account, debit_account)
+
+
+def get_depreciation_cost_center_and_depreciation_series_for_company():
+	company_names = frappe.db.get_all("Company", pluck="name")
+
+	res = {}
+
+	for company_name in company_names:
+		depreciation_cost_center, depreciation_series = frappe.get_cached_value(
+			"Company", company_name, ["depreciation_cost_center", "series_for_depreciation_entry"]
+		)
+		res.update({company_name: (depreciation_cost_center, depreciation_series)})
+
+	return res
+
+
 @frappe.whitelist()
-def make_depreciation_entry(asset_depr_schedule_name, date=None):
+def make_depreciation_entry(
+	asset_depr_schedule_name,
+	date=None,
+	sch_start_idx=None,
+	sch_end_idx=None,
+	credit_and_debit_accounts=None,
+	depreciation_cost_center_and_depreciation_series=None,
+	accounting_dimensions=None,
+):
 	frappe.has_permission("Journal Entry", throw=True)
 
 	if not date:
@@ -92,100 +201,144 @@
 
 	asset_depr_schedule_doc = frappe.get_doc("Asset Depreciation Schedule", asset_depr_schedule_name)
 
-	asset_name = asset_depr_schedule_doc.asset
+	asset = frappe.get_doc("Asset", asset_depr_schedule_doc.asset)
 
-	asset = frappe.get_doc("Asset", asset_name)
-	(
-		fixed_asset_account,
-		accumulated_depreciation_account,
-		depreciation_expense_account,
-	) = get_depreciation_accounts(asset)
+	if credit_and_debit_accounts:
+		credit_account, debit_account = credit_and_debit_accounts
+	else:
+		credit_account, debit_account = get_credit_and_debit_accounts_for_asset_category_and_company(
+			asset.asset_category, asset.company
+		)
 
-	depreciation_cost_center, depreciation_series = frappe.get_cached_value(
-		"Company", asset.company, ["depreciation_cost_center", "series_for_depreciation_entry"]
-	)
+	if depreciation_cost_center_and_depreciation_series:
+		depreciation_cost_center, depreciation_series = depreciation_cost_center_and_depreciation_series
+	else:
+		depreciation_cost_center, depreciation_series = frappe.get_cached_value(
+			"Company", asset.company, ["depreciation_cost_center", "series_for_depreciation_entry"]
+		)
 
 	depreciation_cost_center = asset.cost_center or depreciation_cost_center
 
-	accounting_dimensions = get_checks_for_pl_and_bs_accounts()
+	if not accounting_dimensions:
+		accounting_dimensions = get_checks_for_pl_and_bs_accounts()
 
-	for d in asset_depr_schedule_doc.get("depreciation_schedule"):
-		if not d.journal_entry and getdate(d.schedule_date) <= getdate(date):
-			je = frappe.new_doc("Journal Entry")
-			je.voucher_type = "Depreciation Entry"
-			je.naming_series = depreciation_series
-			je.posting_date = d.schedule_date
-			je.company = asset.company
-			je.finance_book = asset_depr_schedule_doc.finance_book
-			je.remark = "Depreciation Entry against {0} worth {1}".format(asset_name, d.depreciation_amount)
+	depreciation_posting_error = None
 
-			credit_account, debit_account = get_credit_and_debit_accounts(
-				accumulated_depreciation_account, depreciation_expense_account
+	for d in asset_depr_schedule_doc.get("depreciation_schedule")[
+		sch_start_idx or 0 : sch_end_idx or len(asset_depr_schedule_doc.get("depreciation_schedule"))
+	]:
+		try:
+			_make_journal_entry_for_depreciation(
+				asset_depr_schedule_doc,
+				asset,
+				date,
+				d,
+				sch_start_idx,
+				sch_end_idx,
+				depreciation_cost_center,
+				depreciation_series,
+				credit_account,
+				debit_account,
+				accounting_dimensions,
 			)
-
-			credit_entry = {
-				"account": credit_account,
-				"credit_in_account_currency": d.depreciation_amount,
-				"reference_type": "Asset",
-				"reference_name": asset.name,
-				"cost_center": depreciation_cost_center,
-			}
-
-			debit_entry = {
-				"account": debit_account,
-				"debit_in_account_currency": d.depreciation_amount,
-				"reference_type": "Asset",
-				"reference_name": asset.name,
-				"cost_center": depreciation_cost_center,
-			}
-
-			for dimension in accounting_dimensions:
-				if asset.get(dimension["fieldname"]) or dimension.get("mandatory_for_bs"):
-					credit_entry.update(
-						{
-							dimension["fieldname"]: asset.get(dimension["fieldname"])
-							or dimension.get("default_dimension")
-						}
-					)
-
-				if asset.get(dimension["fieldname"]) or dimension.get("mandatory_for_pl"):
-					debit_entry.update(
-						{
-							dimension["fieldname"]: asset.get(dimension["fieldname"])
-							or dimension.get("default_dimension")
-						}
-					)
-
-			je.append("accounts", credit_entry)
-
-			je.append("accounts", debit_entry)
-
-			je.flags.ignore_permissions = True
-			je.flags.planned_depr_entry = True
-			je.save()
-
-			d.db_set("journal_entry", je.name)
-
-			if not je.meta.get_workflow():
-				je.submit()
-				idx = cint(asset_depr_schedule_doc.finance_book_id)
-				row = asset.get("finance_books")[idx - 1]
-				row.value_after_depreciation -= d.depreciation_amount
-				row.db_update()
-
-	asset.db_set("depr_entry_posting_status", "Successful")
+			frappe.db.commit()
+		except Exception as e:
+			frappe.db.rollback()
+			depreciation_posting_error = e
 
 	asset.set_status()
 
-	return asset_depr_schedule_doc
+	if not depreciation_posting_error:
+		asset.db_set("depr_entry_posting_status", "Successful")
+		return asset_depr_schedule_doc
+
+	raise depreciation_posting_error
 
 
-def get_depreciation_accounts(asset):
+def _make_journal_entry_for_depreciation(
+	asset_depr_schedule_doc,
+	asset,
+	date,
+	depr_schedule,
+	sch_start_idx,
+	sch_end_idx,
+	depreciation_cost_center,
+	depreciation_series,
+	credit_account,
+	debit_account,
+	accounting_dimensions,
+):
+	if not (sch_start_idx and sch_end_idx) and not (
+		not depr_schedule.journal_entry and getdate(depr_schedule.schedule_date) <= getdate(date)
+	):
+		return
+
+	je = frappe.new_doc("Journal Entry")
+	je.voucher_type = "Depreciation Entry"
+	je.naming_series = depreciation_series
+	je.posting_date = depr_schedule.schedule_date
+	je.company = asset.company
+	je.finance_book = asset_depr_schedule_doc.finance_book
+	je.remark = "Depreciation Entry against {0} worth {1}".format(
+		asset.name, depr_schedule.depreciation_amount
+	)
+
+	credit_entry = {
+		"account": credit_account,
+		"credit_in_account_currency": depr_schedule.depreciation_amount,
+		"reference_type": "Asset",
+		"reference_name": asset.name,
+		"cost_center": depreciation_cost_center,
+	}
+
+	debit_entry = {
+		"account": debit_account,
+		"debit_in_account_currency": depr_schedule.depreciation_amount,
+		"reference_type": "Asset",
+		"reference_name": asset.name,
+		"cost_center": depreciation_cost_center,
+	}
+
+	for dimension in accounting_dimensions:
+		if asset.get(dimension["fieldname"]) or dimension.get("mandatory_for_bs"):
+			credit_entry.update(
+				{
+					dimension["fieldname"]: asset.get(dimension["fieldname"])
+					or dimension.get("default_dimension")
+				}
+			)
+
+		if asset.get(dimension["fieldname"]) or dimension.get("mandatory_for_pl"):
+			debit_entry.update(
+				{
+					dimension["fieldname"]: asset.get(dimension["fieldname"])
+					or dimension.get("default_dimension")
+				}
+			)
+
+	je.append("accounts", credit_entry)
+	je.append("accounts", debit_entry)
+
+	je.flags.ignore_permissions = True
+	je.flags.planned_depr_entry = True
+	je.save()
+
+	depr_schedule.db_set("journal_entry", je.name)
+
+	if not je.meta.get_workflow():
+		je.submit()
+		idx = cint(asset_depr_schedule_doc.finance_book_id)
+		row = asset.get("finance_books")[idx - 1]
+		row.value_after_depreciation -= depr_schedule.depreciation_amount
+		row.db_update()
+
+
+def get_depreciation_accounts(asset_category, company):
 	fixed_asset_account = accumulated_depreciation_account = depreciation_expense_account = None
 
 	accounts = frappe.db.get_value(
 		"Asset Category Account",
-		filters={"parent": asset.asset_category, "company_name": asset.company},
+		filters={"parent": asset_category, "company_name": company},
 		fieldname=[
 			"fixed_asset_account",
 			"accumulated_depreciation_account",
@@ -201,7 +354,7 @@
 
 	if not accumulated_depreciation_account or not depreciation_expense_account:
 		accounts = frappe.get_cached_value(
-			"Company", asset.company, ["accumulated_depreciation_account", "depreciation_expense_account"]
+			"Company", company, ["accumulated_depreciation_account", "depreciation_expense_account"]
 		)
 
 		if not accumulated_depreciation_account:
@@ -216,7 +369,7 @@
 	):
 		frappe.throw(
 			_("Please set Depreciation related Accounts in Asset Category {0} or Company {1}").format(
-				asset.asset_category, asset.company
+				asset_category, company
 			)
 		)
 
@@ -565,8 +718,8 @@
 
 
 def get_asset_details(asset, finance_book=None):
-	fixed_asset_account, accumulated_depr_account, depr_expense_account = get_depreciation_accounts(
-		asset
+	fixed_asset_account, accumulated_depr_account, _ = get_depreciation_accounts(
+		asset.asset_category, asset.company
 	)
 	disposal_account, depreciation_cost_center = get_disposal_account_and_cost_center(asset.company)
 	depreciation_cost_center = asset.cost_center or depreciation_cost_center
diff --git a/erpnext/assets/doctype/asset/test_asset.py b/erpnext/assets/doctype/asset/test_asset.py
index 2a74f20..cd66f1d 100644
--- a/erpnext/assets/doctype/asset/test_asset.py
+++ b/erpnext/assets/doctype/asset/test_asset.py
@@ -754,6 +754,40 @@
 
 		self.assertEqual(schedules, expected_schedules)
 
+	def test_schedule_for_straight_line_method_with_daily_depreciation(self):
+		asset = create_asset(
+			calculate_depreciation=1,
+			available_for_use_date="2023-01-01",
+			purchase_date="2023-01-01",
+			gross_purchase_amount=12000,
+			depreciation_start_date="2023-01-31",
+			total_number_of_depreciations=12,
+			frequency_of_depreciation=1,
+			daily_depreciation=1,
+		)
+
+		expected_schedules = [
+			["2023-01-31", 1019.18, 1019.18],
+			["2023-02-28", 920.55, 1939.73],
+			["2023-03-31", 1019.18, 2958.91],
+			["2023-04-30", 986.3, 3945.21],
+			["2023-05-31", 1019.18, 4964.39],
+			["2023-06-30", 986.3, 5950.69],
+			["2023-07-31", 1019.18, 6969.87],
+			["2023-08-31", 1019.18, 7989.05],
+			["2023-09-30", 986.3, 8975.35],
+			["2023-10-31", 1019.18, 9994.53],
+			["2023-11-30", 986.3, 10980.83],
+			["2023-12-31", 1019.17, 12000.0],
+		]
+
+		schedules = [
+			[cstr(d.schedule_date), d.depreciation_amount, d.accumulated_depreciation_amount]
+			for d in get_depr_schedule(asset.name, "Draft")
+		]
+
+		self.assertEqual(schedules, expected_schedules)
+
 	def test_schedule_for_straight_line_method_for_existing_asset(self):
 		asset = create_asset(
 			calculate_depreciation=1,
@@ -1724,6 +1758,7 @@
 				"total_number_of_depreciations": args.total_number_of_depreciations or 5,
 				"expected_value_after_useful_life": args.expected_value_after_useful_life or 0,
 				"depreciation_start_date": args.depreciation_start_date,
+				"daily_depreciation": args.daily_depreciation or 0,
 			},
 		)
 
diff --git a/erpnext/assets/doctype/asset_capitalization/asset_capitalization.py b/erpnext/assets/doctype/asset_capitalization/asset_capitalization.py
index 858c1db..324b739 100644
--- a/erpnext/assets/doctype/asset_capitalization/asset_capitalization.py
+++ b/erpnext/assets/doctype/asset_capitalization/asset_capitalization.py
@@ -330,7 +330,7 @@
 				gl_entries = self.get_gl_entries()
 
 			if gl_entries:
-				make_gl_entries(gl_entries, from_repost=from_repost)
+				make_gl_entries(gl_entries, merge_entries=False, from_repost=from_repost)
 		elif self.docstatus == 2:
 			make_reverse_gl_entries(voucher_type=self.doctype, voucher_no=self.name)
 
@@ -360,9 +360,6 @@
 			gl_entries, target_account, target_against, precision
 		)
 
-		if not self.stock_items and not self.service_items and self.are_all_asset_items_non_depreciable:
-			return []
-
 		self.get_gl_entries_for_target_item(gl_entries, target_against, precision)
 
 		return gl_entries
diff --git a/erpnext/assets/doctype/asset_category/asset_category.js b/erpnext/assets/doctype/asset_category/asset_category.js
index c702687..7dde14e 100644
--- a/erpnext/assets/doctype/asset_category/asset_category.js
+++ b/erpnext/assets/doctype/asset_category/asset_category.js
@@ -33,6 +33,7 @@
 			var d  = locals[cdt][cdn];
 			return {
 				"filters": {
+					"account_type": "Depreciation",
 					"root_type": ["in", ["Expense", "Income"]],
 					"is_group": 0,
 					"company": d.company_name
diff --git a/erpnext/assets/doctype/asset_category/asset_category.py b/erpnext/assets/doctype/asset_category/asset_category.py
index 2e1def9..8d35141 100644
--- a/erpnext/assets/doctype/asset_category/asset_category.py
+++ b/erpnext/assets/doctype/asset_category/asset_category.py
@@ -53,7 +53,7 @@
 		account_type_map = {
 			"fixed_asset_account": {"account_type": ["Fixed Asset"]},
 			"accumulated_depreciation_account": {"account_type": ["Accumulated Depreciation"]},
-			"depreciation_expense_account": {"root_type": ["Expense", "Income"]},
+			"depreciation_expense_account": {"account_type": ["Depreciation"]},
 			"capital_work_in_progress_account": {"account_type": ["Capital Work in Progress"]},
 		}
 		for d in self.accounts:
diff --git a/erpnext/assets/doctype/asset_depreciation_schedule/asset_depreciation_schedule.json b/erpnext/assets/doctype/asset_depreciation_schedule/asset_depreciation_schedule.json
index d38508d..3772ef4 100644
--- a/erpnext/assets/doctype/asset_depreciation_schedule/asset_depreciation_schedule.json
+++ b/erpnext/assets/doctype/asset_depreciation_schedule/asset_depreciation_schedule.json
@@ -19,6 +19,7 @@
   "depreciation_method",
   "total_number_of_depreciations",
   "rate_of_depreciation",
+  "daily_depreciation",
   "column_break_8",
   "frequency_of_depreciation",
   "expected_value_after_useful_life",
@@ -174,12 +175,21 @@
    "label": "Number of Depreciations Booked",
    "print_hide": 1,
    "read_only": 1
+  },
+  {
+   "default": "0",
+   "depends_on": "eval:doc.depreciation_method == \"Straight Line\" || doc.depreciation_method == \"Manual\"",
+   "fieldname": "daily_depreciation",
+   "fieldtype": "Check",
+   "label": "Daily Depreciation",
+   "print_hide": 1,
+   "read_only": 1
   }
  ],
  "index_web_pages_for_search": 1,
  "is_submittable": 1,
  "links": [],
- "modified": "2023-02-26 16:37:23.734806",
+ "modified": "2023-08-10 22:22:09.722968",
  "modified_by": "Administrator",
  "module": "Assets",
  "name": "Asset Depreciation Schedule",
diff --git a/erpnext/assets/doctype/asset_depreciation_schedule/asset_depreciation_schedule.py b/erpnext/assets/doctype/asset_depreciation_schedule/asset_depreciation_schedule.py
index e616665..39ebd4e 100644
--- a/erpnext/assets/doctype/asset_depreciation_schedule/asset_depreciation_schedule.py
+++ b/erpnext/assets/doctype/asset_depreciation_schedule/asset_depreciation_schedule.py
@@ -153,6 +153,7 @@
 		self.frequency_of_depreciation = row.frequency_of_depreciation
 		self.rate_of_depreciation = row.rate_of_depreciation
 		self.expected_value_after_useful_life = row.expected_value_after_useful_life
+		self.daily_depreciation = row.daily_depreciation
 		self.status = "Draft"
 
 	def make_depr_schedule(
@@ -499,29 +500,36 @@
 	return date_diff(date, period_start_date)
 
 
-@erpnext.allow_regional
 def get_depreciation_amount(
 	asset,
 	depreciable_value,
-	row,
+	fb_row,
 	schedule_idx=0,
 	prev_depreciation_amount=0,
 	has_wdv_or_dd_non_yearly_pro_rata=False,
 ):
-	if row.depreciation_method in ("Straight Line", "Manual"):
-		return get_straight_line_or_manual_depr_amount(asset, row)
+	if fb_row.depreciation_method in ("Straight Line", "Manual"):
+		return get_straight_line_or_manual_depr_amount(asset, fb_row, schedule_idx)
 	else:
+		rate_of_depreciation = get_updated_rate_of_depreciation_for_wdv_and_dd(
+			asset, depreciable_value, fb_row
+		)
 		return get_wdv_or_dd_depr_amount(
 			depreciable_value,
-			row.rate_of_depreciation,
-			row.frequency_of_depreciation,
+			rate_of_depreciation,
+			fb_row.frequency_of_depreciation,
 			schedule_idx,
 			prev_depreciation_amount,
 			has_wdv_or_dd_non_yearly_pro_rata,
 		)
 
 
-def get_straight_line_or_manual_depr_amount(asset, row):
+@erpnext.allow_regional
+def get_updated_rate_of_depreciation_for_wdv_and_dd(asset, depreciable_value, fb_row):
+	return fb_row.rate_of_depreciation
+
+
+def get_straight_line_or_manual_depr_amount(asset, row, schedule_idx):
 	# if the Depreciation Schedule is being modified after Asset Repair due to increase in asset life and value
 	if asset.flags.increase_in_asset_life:
 		return (flt(row.value_after_depreciation) - flt(row.expected_value_after_useful_life)) / (
@@ -534,11 +542,30 @@
 		)
 	# if the Depreciation Schedule is being prepared for the first time
 	else:
-		return (
-			flt(asset.gross_purchase_amount)
-			- flt(asset.opening_accumulated_depreciation)
-			- flt(row.expected_value_after_useful_life)
-		) / flt(row.total_number_of_depreciations - asset.number_of_depreciations_booked)
+		if row.daily_depreciation:
+			daily_depr_amount = (
+				flt(asset.gross_purchase_amount)
+				- flt(asset.opening_accumulated_depreciation)
+				- flt(row.expected_value_after_useful_life)
+			) / date_diff(
+				add_months(
+					row.depreciation_start_date,
+					flt(row.total_number_of_depreciations - asset.number_of_depreciations_booked)
+					* row.frequency_of_depreciation,
+				),
+				row.depreciation_start_date,
+			)
+			to_date = add_months(row.depreciation_start_date, schedule_idx * row.frequency_of_depreciation)
+			from_date = add_months(
+				row.depreciation_start_date, (schedule_idx - 1) * row.frequency_of_depreciation
+			)
+			return daily_depr_amount * date_diff(to_date, from_date)
+		else:
+			return (
+				flt(asset.gross_purchase_amount)
+				- flt(asset.opening_accumulated_depreciation)
+				- flt(row.expected_value_after_useful_life)
+			) / flt(row.total_number_of_depreciations - asset.number_of_depreciations_booked)
 
 
 def get_wdv_or_dd_depr_amount(
diff --git a/erpnext/assets/doctype/asset_finance_book/asset_finance_book.json b/erpnext/assets/doctype/asset_finance_book/asset_finance_book.json
index e5a5f19..4121302 100644
--- a/erpnext/assets/doctype/asset_finance_book/asset_finance_book.json
+++ b/erpnext/assets/doctype/asset_finance_book/asset_finance_book.json
@@ -8,6 +8,7 @@
   "finance_book",
   "depreciation_method",
   "total_number_of_depreciations",
+  "daily_depreciation",
   "column_break_5",
   "frequency_of_depreciation",
   "depreciation_start_date",
@@ -17,6 +18,7 @@
  ],
  "fields": [
   {
+   "columns": 2,
    "fieldname": "finance_book",
    "fieldtype": "Link",
    "in_list_view": 1,
@@ -32,6 +34,7 @@
    "reqd": 1
   },
   {
+   "columns": 2,
    "fieldname": "total_number_of_depreciations",
    "fieldtype": "Int",
    "in_list_view": 1,
@@ -43,6 +46,7 @@
    "fieldtype": "Column Break"
   },
   {
+   "columns": 2,
    "fieldname": "frequency_of_depreciation",
    "fieldtype": "Int",
    "in_list_view": 1,
@@ -57,6 +61,7 @@
    "mandatory_depends_on": "eval:parent.doctype == 'Asset'"
   },
   {
+   "columns": 1,
    "default": "0",
    "depends_on": "eval:parent.doctype == 'Asset'",
    "fieldname": "expected_value_after_useful_life",
@@ -79,12 +84,19 @@
    "fieldname": "rate_of_depreciation",
    "fieldtype": "Percent",
    "label": "Rate of Depreciation"
+  },
+  {
+   "default": "0",
+   "depends_on": "eval:doc.depreciation_method == \"Straight Line\" || doc.depreciation_method == \"Manual\"",
+   "fieldname": "daily_depreciation",
+   "fieldtype": "Check",
+   "label": "Daily Depreciation"
   }
  ],
  "index_web_pages_for_search": 1,
  "istable": 1,
  "links": [],
- "modified": "2021-06-17 12:59:05.743683",
+ "modified": "2023-08-10 22:10:36.576199",
  "modified_by": "Administrator",
  "module": "Assets",
  "name": "Asset Finance Book",
@@ -93,5 +105,6 @@
  "quick_entry": 1,
  "sort_field": "modified",
  "sort_order": "DESC",
+ "states": [],
  "track_changes": 1
 }
\ No newline at end of file
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 a1f0473..823b6e9 100644
--- a/erpnext/assets/doctype/asset_value_adjustment/asset_value_adjustment.py
+++ b/erpnext/assets/doctype/asset_value_adjustment/asset_value_adjustment.py
@@ -64,10 +64,10 @@
 	def make_depreciation_entry(self):
 		asset = frappe.get_doc("Asset", self.asset)
 		(
-			fixed_asset_account,
+			_,
 			accumulated_depreciation_account,
 			depreciation_expense_account,
-		) = get_depreciation_accounts(asset)
+		) = get_depreciation_accounts(asset.asset_category, asset.company)
 
 		depreciation_cost_center, depreciation_series = frappe.get_cached_value(
 			"Company", asset.company, ["depreciation_cost_center", "series_for_depreciation_entry"]
@@ -78,9 +78,7 @@
 		je.naming_series = depreciation_series
 		je.posting_date = self.date
 		je.company = self.company
-		je.remark = _("Depreciation Entry against {0} worth {1}").format(
-			self.asset, self.difference_amount
-		)
+		je.remark = "Depreciation Entry against {0} worth {1}".format(self.asset, self.difference_amount)
 		je.finance_book = self.finance_book
 
 		credit_entry = {
diff --git a/erpnext/assets/report/fixed_asset_register/fixed_asset_register.py b/erpnext/assets/report/fixed_asset_register/fixed_asset_register.py
index 94c77ea..bf62a8f 100644
--- a/erpnext/assets/report/fixed_asset_register/fixed_asset_register.py
+++ b/erpnext/assets/report/fixed_asset_register/fixed_asset_register.py
@@ -7,13 +7,14 @@
 import frappe
 from frappe import _
 from frappe.query_builder.functions import IfNull, Sum
-from frappe.utils import cstr, flt, formatdate, getdate
+from frappe.utils import add_months, cstr, flt, formatdate, getdate, nowdate, today
 
 from erpnext.accounts.report.financial_statements import (
 	get_fiscal_year_data,
 	get_period_list,
 	validate_fiscal_year,
 )
+from erpnext.accounts.utils import get_fiscal_year
 from erpnext.assets.doctype.asset.asset import get_asset_value_after_depreciation
 
 
@@ -37,15 +38,26 @@
 
 	if filters.get("company"):
 		conditions["company"] = filters.company
+
 	if filters.filter_based_on == "Date Range":
+		if not filters.from_date and not filters.to_date:
+			filters.from_date = add_months(nowdate(), -12)
+			filters.to_date = nowdate()
+
 		conditions[date_field] = ["between", [filters.from_date, filters.to_date]]
-	if filters.filter_based_on == "Fiscal Year":
+	elif filters.filter_based_on == "Fiscal Year":
+		if not filters.from_fiscal_year and not filters.to_fiscal_year:
+			default_fiscal_year = get_fiscal_year(today())[0]
+			filters.from_fiscal_year = default_fiscal_year
+			filters.to_fiscal_year = default_fiscal_year
+
 		fiscal_year = get_fiscal_year_data(filters.from_fiscal_year, filters.to_fiscal_year)
 		validate_fiscal_year(fiscal_year, filters.from_fiscal_year, filters.to_fiscal_year)
 		filters.year_start_date = getdate(fiscal_year.year_start_date)
 		filters.year_end_date = getdate(fiscal_year.year_end_date)
 
 		conditions[date_field] = ["between", [filters.year_start_date, filters.year_end_date]]
+
 	if filters.get("only_existing_assets"):
 		conditions["is_existing_asset"] = filters.get("only_existing_assets")
 	if filters.get("asset_category"):
diff --git a/erpnext/buying/doctype/purchase_order/purchase_order.js b/erpnext/buying/doctype/purchase_order/purchase_order.js
index a7f0304..7c33056 100644
--- a/erpnext/buying/doctype/purchase_order/purchase_order.js
+++ b/erpnext/buying/doctype/purchase_order/purchase_order.js
@@ -366,7 +366,7 @@
 					},
 					allow_child_item_selection: true,
 					child_fieldname: "items",
-					child_columns: ["item_code", "qty"]
+					child_columns: ["item_code", "qty", "ordered_qty"]
 				})
 			}, __("Get Items From"));
 
diff --git a/erpnext/buying/doctype/request_for_quotation/request_for_quotation.js b/erpnext/buying/doctype/request_for_quotation/request_for_quotation.js
index 0cdb915..31a06cf 100644
--- a/erpnext/buying/doctype/request_for_quotation/request_for_quotation.js
+++ b/erpnext/buying/doctype/request_for_quotation/request_for_quotation.js
@@ -244,19 +244,21 @@
 			]
 		});
 
-		dialog.fields_dict['supplier'].df.onchange = () => {
-			var supplier = dialog.get_value('supplier');
-			frm.call('get_supplier_email_preview', {supplier: supplier}).then(result => {
+		dialog.fields_dict["supplier"].df.onchange = () => {
+			frm.call("get_supplier_email_preview", {
+				supplier: dialog.get_value("supplier"),
+			}).then(({ message }) => {
 				dialog.fields_dict.email_preview.$wrapper.empty();
-				dialog.fields_dict.email_preview.$wrapper.append(result.message);
+				dialog.fields_dict.email_preview.$wrapper.append(
+					message.message
+				);
+				dialog.set_value("subject", message.subject);
 			});
-
-		}
+		};
 
 		dialog.fields_dict.note.$wrapper.append(`<p class="small text-muted">This is a preview of the email to be sent. A PDF of the document will
 			automatically be attached with the email.</p>`);
 
-		dialog.set_value("subject", frm.doc.subject);
 		dialog.show();
 	}
 })
diff --git a/erpnext/buying/doctype/request_for_quotation/request_for_quotation.json b/erpnext/buying/doctype/request_for_quotation/request_for_quotation.json
index bd65b0c..fbfc1ac 100644
--- a/erpnext/buying/doctype/request_for_quotation/request_for_quotation.json
+++ b/erpnext/buying/doctype/request_for_quotation/request_for_quotation.json
@@ -20,11 +20,11 @@
   "items_section",
   "items",
   "supplier_response_section",
-  "salutation",
-  "subject",
-  "col_break_email_1",
   "email_template",
   "preview",
+  "col_break_email_1",
+  "html_llwp",
+  "send_attached_files",
   "sec_break_email_2",
   "message_for_supplier",
   "terms_section_break",
@@ -237,23 +237,6 @@
    "read_only": 1
   },
   {
-   "fetch_from": "email_template.subject",
-   "fetch_if_empty": 1,
-   "fieldname": "subject",
-   "fieldtype": "Data",
-   "label": "Subject",
-   "print_hide": 1
-  },
-  {
-   "description": "Select a greeting for the receiver. E.g. Mr., Ms., etc.",
-   "fieldname": "salutation",
-   "fieldtype": "Link",
-   "label": "Salutation",
-   "no_copy": 1,
-   "options": "Salutation",
-   "print_hide": 1
-  },
-  {
    "fieldname": "col_break_email_1",
    "fieldtype": "Column Break"
   },
@@ -285,13 +268,28 @@
    "fieldname": "named_place",
    "fieldtype": "Data",
    "label": "Named Place"
+  },
+  {
+   "fieldname": "html_llwp",
+   "fieldtype": "HTML",
+   "options": "<p>In your <b>Email Template</b>, you can use the following special variables:\n</p>\n<ul>\n        <li>\n            <code>{{ update_password_link }}</code>: A link where your supplier can set a new password to log into your portal.\n        </li>\n        <li>\n            <code>{{ portal_link }}</code>: A link to this RFQ in your supplier portal.\n        </li>\n        <li>\n            <code>{{ supplier_name }}</code>: The company name of your supplier.\n        </li>\n        <li>\n            <code>{{ contact.salutation }} {{ contact.last_name }}</code>: The contact person of your supplier.\n        </li><li>\n            <code>{{ user_fullname }}</code>: Your full name.\n        </li>\n    </ul>\n<p></p>\n<p>Apart from these, you can access all values in this RFQ, like <code>{{ message_for_supplier }}</code> or <code>{{ terms }}</code>.</p>",
+   "print_hide": 1,
+   "read_only": 1,
+   "report_hide": 1
+  },
+  {
+   "default": "1",
+   "description": "If enabled, all files attached to this document will be attached to each email",
+   "fieldname": "send_attached_files",
+   "fieldtype": "Check",
+   "label": "Send Attached Files"
   }
  ],
  "icon": "fa fa-shopping-cart",
  "index_web_pages_for_search": 1,
  "is_submittable": 1,
  "links": [],
- "modified": "2023-01-31 23:22:06.684694",
+ "modified": "2023-08-08 16:30:10.870429",
  "modified_by": "Administrator",
  "module": "Buying",
  "name": "Request for Quotation",
diff --git a/erpnext/buying/doctype/request_for_quotation/request_for_quotation.py b/erpnext/buying/doctype/request_for_quotation/request_for_quotation.py
index 4590f8c..e938577 100644
--- a/erpnext/buying/doctype/request_for_quotation/request_for_quotation.py
+++ b/erpnext/buying/doctype/request_for_quotation/request_for_quotation.py
@@ -116,7 +116,10 @@
 		route = frappe.db.get_value(
 			"Portal Menu Item", {"reference_doctype": "Request for Quotation"}, ["route"]
 		)
-		return get_url("/app/{0}/".format(route) + self.name)
+		if not route:
+			frappe.throw(_("Please add Request for Quotation to the sidebar in Portal Settings."))
+
+		return get_url(f"{route}/{self.name}")
 
 	def update_supplier_part_no(self, supplier):
 		self.vendor = supplier
@@ -179,37 +182,32 @@
 		if full_name == "Guest":
 			full_name = "Administrator"
 
-		# send document dict and some important data from suppliers row
-		# to render message_for_supplier from any template
 		doc_args = self.as_dict()
-		doc_args.update({"supplier": data.get("supplier"), "supplier_name": data.get("supplier_name")})
 
-		# Get Contact Full Name
-		supplier_name = None
 		if data.get("contact"):
-			contact_name = frappe.db.get_value(
-				"Contact", data.get("contact"), ["first_name", "middle_name", "last_name"]
-			)
-			supplier_name = (" ").join(x for x in contact_name if x)  # remove any blank values
+			contact = frappe.get_doc("Contact", data.get("contact"))
+			doc_args["contact"] = contact.as_dict()
 
-		args = {
-			"update_password_link": update_password_link,
-			"message": frappe.render_template(self.message_for_supplier, doc_args),
-			"rfq_link": rfq_link,
-			"user_fullname": full_name,
-			"supplier_name": supplier_name or data.get("supplier_name"),
-			"supplier_salutation": self.salutation or "Dear Mx.",
-		}
-
-		subject = self.subject or _("Request for Quotation")
-		template = "templates/emails/request_for_quotation.html"
+		doc_args.update(
+			{
+				"supplier": data.get("supplier"),
+				"supplier_name": data.get("supplier_name"),
+				"update_password_link": f'<a href="{update_password_link}" class="btn btn-default btn-xs" target="_blank">{_("Set Password")}</a>',
+				"portal_link": f'<a href="{rfq_link}" class="btn btn-default btn-sm" target="_blank"> {_("Submit your Quotation")} </a>',
+				"user_fullname": full_name,
+			}
+		)
+		email_template = frappe.get_doc("Email Template", self.email_template)
+		message = frappe.render_template(email_template.response_, doc_args)
+		subject = frappe.render_template(email_template.subject, doc_args)
 		sender = frappe.session.user not in STANDARD_USERS and frappe.session.user or None
-		message = frappe.get_template(template).render(args)
 
 		if preview:
-			return message
+			return {"message": message, "subject": subject}
 
-		attachments = self.get_attachments()
+		attachments = None
+		if self.send_attached_files:
+			attachments = self.get_attachments()
 
 		self.send_email(data, sender, subject, message, attachments)
 
diff --git a/erpnext/buying/doctype/request_for_quotation/test_request_for_quotation.py b/erpnext/buying/doctype/request_for_quotation/test_request_for_quotation.py
index d250e6f..42fa1d9 100644
--- a/erpnext/buying/doctype/request_for_quotation/test_request_for_quotation.py
+++ b/erpnext/buying/doctype/request_for_quotation/test_request_for_quotation.py
@@ -2,11 +2,14 @@
 # See license.txt
 
 
+from urllib.parse import urlparse
+
 import frappe
 from frappe.tests.utils import FrappeTestCase
 from frappe.utils import nowdate
 
 from erpnext.buying.doctype.request_for_quotation.request_for_quotation import (
+	RequestforQuotation,
 	create_supplier_quotation,
 	get_pdf,
 	make_supplier_quotation_from_rfq,
@@ -125,13 +128,18 @@
 		rfq.status = "Draft"
 		rfq.submit()
 
+	def test_get_link(self):
+		rfq = make_request_for_quotation()
+		parsed_link = urlparse(rfq.get_link())
+		self.assertEqual(parsed_link.path, f"/rfq/{rfq.name}")
+
 	def test_get_pdf(self):
 		rfq = make_request_for_quotation()
 		get_pdf(rfq.name, rfq.get("suppliers")[0].supplier)
 		self.assertEqual(frappe.local.response.type, "pdf")
 
 
-def make_request_for_quotation(**args):
+def make_request_for_quotation(**args) -> "RequestforQuotation":
 	"""
 	:param supplier_data: List containing supplier data
 	"""
diff --git a/erpnext/buying/doctype/supplier_scorecard/supplier_scorecard.py b/erpnext/buying/doctype/supplier_scorecard/supplier_scorecard.py
index 58da851..6e22acf 100644
--- a/erpnext/buying/doctype/supplier_scorecard/supplier_scorecard.py
+++ b/erpnext/buying/doctype/supplier_scorecard/supplier_scorecard.py
@@ -339,29 +339,35 @@
 		{
 			"min_grade": 0.0,
 			"prevent_rfqs": 1,
+			"warn_rfqs": 0,
 			"notify_supplier": 0,
 			"max_grade": 30.0,
 			"prevent_pos": 1,
+			"warn_pos": 0,
 			"standing_color": "Red",
 			"notify_employee": 0,
 			"standing_name": "Very Poor",
 		},
 		{
 			"min_grade": 30.0,
-			"prevent_rfqs": 1,
+			"prevent_rfqs": 0,
+			"warn_rfqs": 1,
 			"notify_supplier": 0,
 			"max_grade": 50.0,
 			"prevent_pos": 0,
-			"standing_color": "Red",
+			"warn_pos": 1,
+			"standing_color": "Yellow",
 			"notify_employee": 0,
 			"standing_name": "Poor",
 		},
 		{
 			"min_grade": 50.0,
 			"prevent_rfqs": 0,
+			"warn_rfqs": 0,
 			"notify_supplier": 0,
 			"max_grade": 80.0,
 			"prevent_pos": 0,
+			"warn_pos": 0,
 			"standing_color": "Green",
 			"notify_employee": 0,
 			"standing_name": "Average",
@@ -369,9 +375,11 @@
 		{
 			"min_grade": 80.0,
 			"prevent_rfqs": 0,
+			"warn_rfqs": 0,
 			"notify_supplier": 0,
 			"max_grade": 100.0,
 			"prevent_pos": 0,
+			"warn_pos": 0,
 			"standing_color": "Blue",
 			"notify_employee": 0,
 			"standing_name": "Excellent",
diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py
index b2cfc39..fbf97aa 100644
--- a/erpnext/controllers/accounts_controller.py
+++ b/erpnext/controllers/accounts_controller.py
@@ -32,6 +32,7 @@
 	apply_pricing_rule_on_transaction,
 	get_applied_pricing_rules,
 )
+from erpnext.accounts.general_ledger import get_round_off_account_and_cost_center
 from erpnext.accounts.party import (
 	get_party_account,
 	get_party_account_currency,
@@ -973,6 +974,33 @@
 
 				d.exchange_gain_loss = difference
 
+	def make_precision_loss_gl_entry(self, gl_entries):
+		round_off_account, round_off_cost_center = get_round_off_account_and_cost_center(
+			self.company, "Purchase Invoice", self.name, self.use_company_roundoff_cost_center
+		)
+
+		precision_loss = self.get("base_net_total") - flt(
+			self.get("net_total") * self.conversion_rate, self.precision("net_total")
+		)
+
+		credit_or_debit = "credit" if self.doctype == "Purchase Invoice" else "debit"
+		against = self.supplier if self.doctype == "Purchase Invoice" else self.customer
+
+		if precision_loss:
+			gl_entries.append(
+				self.get_gl_dict(
+					{
+						"account": round_off_account,
+						"against": against,
+						credit_or_debit: precision_loss,
+						"cost_center": round_off_cost_center
+						if self.use_company_roundoff_cost_center
+						else self.cost_center or round_off_cost_center,
+						"remarks": _("Net total calculation precision loss"),
+					}
+				)
+			)
+
 	def make_exchange_gain_loss_journal(self, args: dict = None) -> None:
 		"""
 		Make Exchange Gain/Loss journal for Invoices and Payments
diff --git a/erpnext/controllers/print_settings.py b/erpnext/controllers/print_settings.py
index d86607d..59f13c6 100644
--- a/erpnext/controllers/print_settings.py
+++ b/erpnext/controllers/print_settings.py
@@ -14,9 +14,6 @@
 		}
 	}
 
-	if doc.meta.get_field("items"):
-		doc.meta.get_field("items").hide_in_print_layout = ["uom", "stock_uom"]
-
 	doc.flags.compact_item_fields = ["description", "qty", "rate", "amount"]
 
 	if settings.compact_item_print:
diff --git a/erpnext/controllers/status_updater.py b/erpnext/controllers/status_updater.py
index a4bc4a9..73a248f 100644
--- a/erpnext/controllers/status_updater.py
+++ b/erpnext/controllers/status_updater.py
@@ -5,7 +5,7 @@
 import frappe
 from frappe import _
 from frappe.model.document import Document
-from frappe.utils import comma_or, flt, getdate, now, nowdate
+from frappe.utils import comma_or, flt, get_link_to_form, getdate, now, nowdate
 
 
 class OverAllowanceError(frappe.ValidationError):
@@ -233,8 +233,17 @@
 				if hasattr(d, "qty") and d.qty > 0 and self.get("is_return"):
 					frappe.throw(_("For an item {0}, quantity must be negative number").format(d.item_code))
 
-				if hasattr(d, "item_code") and hasattr(d, "rate") and d.rate < 0:
-					frappe.throw(_("For an item {0}, rate must be a positive number").format(d.item_code))
+				if not frappe.db.get_single_value("Selling Settings", "allow_negative_rates_for_items"):
+					if hasattr(d, "item_code") and hasattr(d, "rate") and flt(d.rate) < 0:
+						frappe.throw(
+							_(
+								"For item {0}, rate must be a positive number. To Allow negative rates, enable {1} in {2}"
+							).format(
+								frappe.bold(d.item_code),
+								frappe.bold(_("`Allow Negative rates for Items`")),
+								get_link_to_form("Selling Settings", "Selling Settings"),
+							),
+						)
 
 				if d.doctype == args["source_dt"] and d.get(args["join_field"]):
 					args["name"] = d.get(args["join_field"])
diff --git a/erpnext/controllers/subcontracting_controller.py b/erpnext/controllers/subcontracting_controller.py
index 57339bf..6633f4f 100644
--- a/erpnext/controllers/subcontracting_controller.py
+++ b/erpnext/controllers/subcontracting_controller.py
@@ -550,7 +550,7 @@
 			if rm_obj.serial_and_batch_bundle:
 				args["serial_and_batch_bundle"] = rm_obj.serial_and_batch_bundle
 
-			rm_obj.rate = bom_item.rate if self.backflush_based_on == "BOM" else get_incoming_rate(args)
+			rm_obj.rate = get_incoming_rate(args)
 
 	def __get_qty_based_on_material_transfer(self, item_row, transfer_item):
 		key = (item_row.item_code, item_row.get(self.subcontract_data.order_field))
diff --git a/erpnext/controllers/tests/test_accounts_controller.py b/erpnext/controllers/tests/test_accounts_controller.py
index 8e5f813..0f8e133 100644
--- a/erpnext/controllers/tests/test_accounts_controller.py
+++ b/erpnext/controllers/tests/test_accounts_controller.py
@@ -347,18 +347,23 @@
 		for exc_rate in [75.9, 83.1, 80.01]:
 			with self.subTest(exc_rate=exc_rate):
 				si = self.create_sales_invoice(qty=1, conversion_rate=exc_rate, rate=1, do_not_submit=True)
+				advances = si.get_advance_entries()
+				self.assertEqual(len(advances), 1)
+				self.assertEqual(advances[0].reference_name, adv.name)
 				si.append(
 					"advances",
 					{
 						"doctype": "Sales Invoice Advance",
-						"reference_type": adv.doctype,
-						"reference_name": adv.name,
+						"reference_type": advances[0].reference_type,
+						"reference_name": advances[0].reference_name,
+						"reference_row": advances[0].reference_row,
 						"advance_amount": 1,
 						"allocated_amount": 1,
-						"ref_exchange_rate": 85,
-						"remarks": "Test",
+						"ref_exchange_rate": advances[0].exchange_rate,
+						"remarks": advances[0].remarks,
 					},
 				)
+
 				si = si.save()
 				si = si.submit()
 
@@ -398,16 +403,19 @@
 		si = self.create_sales_invoice(
 			qty=2, conversion_rate=80, rate=rate_in_account_currency, do_not_submit=True
 		)
+		advances = si.get_advance_entries()
+		self.assertEqual(len(advances), 1)
+		self.assertEqual(advances[0].reference_name, adv.name)
 		si.append(
 			"advances",
 			{
 				"doctype": "Sales Invoice Advance",
-				"reference_type": adv.doctype,
-				"reference_name": adv.name,
+				"reference_type": advances[0].reference_type,
+				"reference_name": advances[0].reference_name,
 				"advance_amount": 1,
 				"allocated_amount": 1,
-				"ref_exchange_rate": 85,
-				"remarks": "Test",
+				"ref_exchange_rate": advances[0].exchange_rate,
+				"remarks": advances[0].remarks,
 			},
 		)
 		si = si.save()
@@ -470,16 +478,19 @@
 
 		# invoice with advance(partial amount)
 		si = self.create_sales_invoice(qty=2, conversion_rate=80, rate=1, do_not_submit=True)
+		advances = si.get_advance_entries()
+		self.assertEqual(len(advances), 1)
+		self.assertEqual(advances[0].reference_name, adv.name)
 		si.append(
 			"advances",
 			{
 				"doctype": "Sales Invoice Advance",
-				"reference_type": adv.doctype,
-				"reference_name": adv.name,
+				"reference_type": advances[0].reference_type,
+				"reference_name": advances[0].reference_name,
 				"advance_amount": 1,
 				"allocated_amount": 1,
-				"ref_exchange_rate": 85,
-				"remarks": "Test",
+				"ref_exchange_rate": advances[0].exchange_rate,
+				"remarks": advances[0].remarks,
 			},
 		)
 		si = si.save()
@@ -678,22 +689,26 @@
 		adv.reload()
 
 		# Sales Invoices in different exchange rates
-		for exc_rate in [75.9, 83.1, 80.01]:
+		for exc_rate in [75.9, 83.1]:
 			with self.subTest(exc_rate=exc_rate):
 				si = self.create_sales_invoice(qty=1, conversion_rate=exc_rate, rate=1, do_not_submit=True)
+				advances = si.get_advance_entries()
+				self.assertEqual(len(advances), 1)
+				self.assertEqual(advances[0].reference_name, adv.name)
 				si.append(
 					"advances",
 					{
 						"doctype": "Sales Invoice Advance",
-						"reference_type": adv.doctype,
-						"reference_name": adv.name,
-						"reference_row": adv.accounts[0].name,
+						"reference_type": advances[0].reference_type,
+						"reference_name": advances[0].reference_name,
+						"reference_row": advances[0].reference_row,
 						"advance_amount": 1,
 						"allocated_amount": 1,
-						"ref_exchange_rate": adv_exc_rate,
-						"remarks": "Test",
+						"ref_exchange_rate": advances[0].exchange_rate,
+						"remarks": advances[0].remarks,
 					},
 				)
+
 				si = si.save()
 				si = si.submit()
 
@@ -741,19 +756,23 @@
 
 		# invoice with advance(partial amount)
 		si = self.create_sales_invoice(qty=3, conversion_rate=80, rate=1, do_not_submit=True)
+		advances = si.get_advance_entries()
+		self.assertEqual(len(advances), 1)
+		self.assertEqual(advances[0].reference_name, adv.name)
 		si.append(
 			"advances",
 			{
 				"doctype": "Sales Invoice Advance",
-				"reference_type": adv.doctype,
-				"reference_name": adv.name,
-				"reference_row": adv.accounts[0].name,
+				"reference_type": advances[0].reference_type,
+				"reference_name": advances[0].reference_name,
+				"reference_row": advances[0].reference_row,
 				"advance_amount": 1,
 				"allocated_amount": 1,
-				"ref_exchange_rate": adv_exc_rate,
-				"remarks": "Test",
+				"ref_exchange_rate": advances[0].exchange_rate,
+				"remarks": advances[0].remarks,
 			},
 		)
+
 		si = si.save()
 		si = si.submit()
 
diff --git a/erpnext/e_commerce/web_template/hero_slider/hero_slider.html b/erpnext/e_commerce/web_template/hero_slider/hero_slider.html
index e560f4a..fe4fee3 100644
--- a/erpnext/e_commerce/web_template/hero_slider/hero_slider.html
+++ b/erpnext/e_commerce/web_template/hero_slider/hero_slider.html
@@ -1,7 +1,7 @@
 {%- macro slide(image, title, subtitle, action, label, index, align="Left", theme="Dark") -%}
 {%- set align_class = resolve_class({
 	'text-right': align == 'Right',
-	'text-centre': align == 'Centre',
+	'text-center': align == 'Centre',
 	'text-left': align == 'Left',
 }) -%}
 
diff --git a/erpnext/hooks.py b/erpnext/hooks.py
index d5eb464..7eaa146 100644
--- a/erpnext/hooks.py
+++ b/erpnext/hooks.py
@@ -70,6 +70,19 @@
 	"Department",
 ]
 
+demo_master_doctypes = [
+	"item_group",
+	"item",
+	"customer_group",
+	"supplier_group",
+	"customer",
+	"supplier",
+]
+demo_transaction_doctypes = [
+	"purchase_order",
+	"sales_order",
+]
+
 jinja = {
 	"methods": [
 		"erpnext.stock.serial_batch_bundle.get_serial_or_batch_nos",
@@ -429,7 +442,6 @@
 		"erpnext.controllers.accounts_controller.update_invoice_status",
 		"erpnext.accounts.doctype.fiscal_year.fiscal_year.auto_create_fiscal_year",
 		"erpnext.projects.doctype.task.task.set_tasks_as_overdue",
-		"erpnext.assets.doctype.asset.depreciation.post_depreciation_entries",
 		"erpnext.stock.doctype.serial_no.serial_no.update_maintenance_status",
 		"erpnext.buying.doctype.supplier_scorecard.supplier_scorecard.refresh_scorecards",
 		"erpnext.setup.doctype.company.company.cache_companies_monthly_sales_history",
@@ -454,6 +466,7 @@
 		"erpnext.setup.doctype.email_digest.email_digest.send",
 		"erpnext.manufacturing.doctype.bom_update_tool.bom_update_tool.auto_update_latest_price_in_all_boms",
 		"erpnext.crm.utils.open_leads_opportunities_based_on_todays_event",
+		"erpnext.assets.doctype.asset.depreciation.post_depreciation_entries",
 	],
 	"monthly_long": [
 		"erpnext.accounts.deferred_revenue.process_deferred_accounting",
diff --git a/erpnext/manufacturing/doctype/work_order/work_order.json b/erpnext/manufacturing/doctype/work_order/work_order.json
index a236f2a..1996e19 100644
--- a/erpnext/manufacturing/doctype/work_order/work_order.json
+++ b/erpnext/manufacturing/doctype/work_order/work_order.json
@@ -404,6 +404,8 @@
    "read_only": 1
   },
   {
+   "fetch_from": "production_item.stock_uom",
+   "fetch_if_empty": 1,
    "fieldname": "stock_uom",
    "fieldtype": "Link",
    "label": "Stock UOM",
@@ -590,7 +592,7 @@
  "image_field": "image",
  "is_submittable": 1,
  "links": [],
- "modified": "2023-06-09 13:20:09.154362",
+ "modified": "2023-08-11 18:35:49.852069",
  "modified_by": "Administrator",
  "module": "Manufacturing",
  "name": "Work Order",
@@ -610,7 +612,6 @@
    "read": 1,
    "report": 1,
    "role": "Manufacturing User",
-   "set_user_permissions": 1,
    "share": 1,
    "submit": 1,
    "write": 1
diff --git a/erpnext/patches.txt b/erpnext/patches.txt
index 641d755..a25c7c2 100644
--- a/erpnext/patches.txt
+++ b/erpnext/patches.txt
@@ -321,8 +321,7 @@
 erpnext.patches.v14_0.update_closing_balances #14-07-2023
 execute:frappe.db.set_single_value("Accounts Settings", "merge_similar_account_heads", 0)
 erpnext.patches.v14_0.update_reference_type_in_journal_entry_accounts
-# below migration patches should always run last
-erpnext.patches.v14_0.migrate_gl_to_payment_ledger
+erpnext.patches.v14_0.update_subscription_details
 execute:frappe.delete_doc_if_exists("Report", "Tax Detail")
 erpnext.patches.v15_0.enable_all_leads
 erpnext.patches.v14_0.update_company_in_ldc
@@ -339,3 +338,6 @@
 execute:frappe.defaults.clear_default("fiscal_year")
 erpnext.patches.v15_0.remove_exotel_integration
 erpnext.patches.v14_0.single_to_multi_dunning
+execute:frappe.db.set_single_value('Selling Settings', 'allow_negative_rates_for_items', 0)
+# below migration patch should always run last
+erpnext.patches.v14_0.migrate_gl_to_payment_ledger
diff --git a/erpnext/patches/v14_0/update_subscription_details.py b/erpnext/patches/v14_0/update_subscription_details.py
new file mode 100644
index 0000000..58ab16d
--- /dev/null
+++ b/erpnext/patches/v14_0/update_subscription_details.py
@@ -0,0 +1,17 @@
+import frappe
+
+
+def execute():
+	subscription_invoices = frappe.get_all(
+		"Subscription Invoice", fields=["document_type", "invoice", "parent"]
+	)
+
+	for subscription_invoice in subscription_invoices:
+		frappe.db.set_value(
+			subscription_invoice.document_type,
+			subscription_invoice.invoice,
+			"subscription",
+			subscription_invoice.parent,
+		)
+
+	frappe.delete_doc_if_exists("DocType", "Subscription Invoice")
diff --git a/erpnext/projects/report/billing_summary.py b/erpnext/projects/report/billing_summary.py
index bc8f2af..ac1524a 100644
--- a/erpnext/projects/report/billing_summary.py
+++ b/erpnext/projects/report/billing_summary.py
@@ -98,9 +98,11 @@
 	record_filters = [
 		["start_date", "<=", filters.to_date],
 		["end_date", ">=", filters.from_date],
-		["docstatus", "=", 1],
 	]
-
+	if not filters.get("include_draft_timesheets"):
+		record_filters.append(["docstatus", "=", 1])
+	else:
+		record_filters.append(["docstatus", "!=", 2])
 	if "employee" in filters:
 		record_filters.append(["employee", "=", filters.employee])
 
diff --git a/erpnext/projects/report/employee_billing_summary/employee_billing_summary.js b/erpnext/projects/report/employee_billing_summary/employee_billing_summary.js
index 8566b1f..2c25465 100644
--- a/erpnext/projects/report/employee_billing_summary/employee_billing_summary.js
+++ b/erpnext/projects/report/employee_billing_summary/employee_billing_summary.js
@@ -25,5 +25,10 @@
 			default: frappe.datetime.add_days(frappe.datetime.month_start(), -1),
 			reqd: 1
 		},
+		{
+			fieldname:"include_draft_timesheets",
+			label: __("Include Timesheets in Draft Status"),
+			fieldtype: "Check",
+		},
 	]
 }
diff --git a/erpnext/projects/report/project_billing_summary/project_billing_summary.js b/erpnext/projects/report/project_billing_summary/project_billing_summary.js
index 0242036..fce0c68 100644
--- a/erpnext/projects/report/project_billing_summary/project_billing_summary.js
+++ b/erpnext/projects/report/project_billing_summary/project_billing_summary.js
@@ -25,5 +25,10 @@
 			default: frappe.datetime.add_days(frappe.datetime.month_start(),-1),
 			reqd: 1
 		},
+		{
+			fieldname:"include_draft_timesheets",
+			label: __("Include Timesheets in Draft Status"),
+			fieldtype: "Check",
+		},
 	]
 }
diff --git a/erpnext/public/build.json b/erpnext/public/build.json
deleted file mode 100644
index b9b48ab..0000000
--- a/erpnext/public/build.json
+++ /dev/null
@@ -1,68 +0,0 @@
-{
-	"css/erpnext.css": [
-		"public/less/erpnext.less",
-		"public/scss/call_popup.scss",
-		"public/scss/point-of-sale.scss"
-	],
-	"js/erpnext-web.min.js": [
-		"public/js/website_utils.js",
-		"public/js/shopping_cart.js",
-		"public/js/wishlist.js"
-	],
-	"css/erpnext-web.css": [
-		"public/scss/website.scss",
-		"public/scss/shopping_cart.scss"
-	],
-	"js/erpnext.min.js": [
-		"public/js/conf.js",
-		"public/js/utils.js",
-		"public/js/queries.js",
-		"public/js/sms_manager.js",
-		"public/js/utils/party.js",
-		"public/js/controllers/stock_controller.js",
-		"public/js/payment/payments.js",
-		"public/js/controllers/taxes_and_totals.js",
-		"public/js/controllers/transaction.js",
-		"public/js/templates/item_selector.html",
-		"public/js/utils/item_selector.js",
-		"public/js/help_links.js",
-		"public/js/templates/item_quick_entry.html",
-		"public/js/utils/customer_quick_entry.js",
-		"public/js/utils/supplier_quick_entry.js",
-		"public/js/education/student_button.html",
-		"public/js/education/assessment_result_tool.html",
-		"public/js/call_popup/call_popup.js",
-		"public/js/utils/dimension_tree_filter.js",
-		"public/js/telephony.js",
-		"public/js/templates/call_link.html",
-		"public/js/bulk_transaction_processing.js"
-	],
-	"js/item-dashboard.min.js": [
-		"stock/dashboard/item_dashboard.html",
-		"stock/dashboard/item_dashboard_list.html",
-		"stock/dashboard/item_dashboard.js",
-		"stock/page/warehouse_capacity_summary/warehouse_capacity_summary.html",
-		"stock/page/warehouse_capacity_summary/warehouse_capacity_summary_header.html"
-	],
-	"js/point-of-sale.min.js": [
-		"selling/page/point_of_sale/pos_item_selector.js",
-		"selling/page/point_of_sale/pos_item_cart.js",
-		"selling/page/point_of_sale/pos_item_details.js",
-		"selling/page/point_of_sale/pos_number_pad.js",
-		"selling/page/point_of_sale/pos_payment.js",
-		"selling/page/point_of_sale/pos_past_order_list.js",
-		"selling/page/point_of_sale/pos_past_order_summary.js",
-		"selling/page/point_of_sale/pos_controller.js"
-	],
-	"js/bank-reconciliation-tool.min.js": [
-		"public/js/bank_reconciliation_tool/data_table_manager.js",
-		"public/js/bank_reconciliation_tool/number_card.js",
-		"public/js/bank_reconciliation_tool/dialog_manager.js"
-	],
-	"js/e-commerce.min.js": [
-		"e_commerce/product_ui/views.js",
-		"e_commerce/product_ui/grid.js",
-		"e_commerce/product_ui/list.js",
-		"e_commerce/product_ui/search.js"
-	]
-}
diff --git a/erpnext/public/js/bank_reconciliation_tool/dialog_manager.js b/erpnext/public/js/bank_reconciliation_tool/dialog_manager.js
index cbb64ca..52fa8ab 100644
--- a/erpnext/public/js/bank_reconciliation_tool/dialog_manager.js
+++ b/erpnext/public/js/bank_reconciliation_tool/dialog_manager.js
@@ -104,6 +104,9 @@
 				name: __("Document Name"),
 				editable: false,
 				width: 1,
+				format: (value, row) => {
+					return frappe.form.formatters.Link(value, {options: row[2].content});
+				},
 			},
 			{
 				name: __("Reference Date"),
@@ -132,7 +135,7 @@
 	format_row(row) {
 		return [
 			row[1], // Document Type
-			frappe.form.formatters.Link(row[2], {options: row[1]}), // Document Name
+			row[2], // Document Name
 			row[5] || row[8], // Reference Date
 			format_currency(row[3], row[9]), // Remaining
 			row[4], // Reference Number
diff --git a/erpnext/public/js/controllers/stock_controller.js b/erpnext/public/js/controllers/stock_controller.js
index 720423b..e9c409e 100644
--- a/erpnext/public/js/controllers/stock_controller.js
+++ b/erpnext/public/js/controllers/stock_controller.js
@@ -57,7 +57,8 @@
 					from_date: me.frm.doc.posting_date,
 					to_date: moment(me.frm.doc.modified).format('YYYY-MM-DD'),
 					company: me.frm.doc.company,
-					show_cancelled_entries: me.frm.doc.docstatus === 2
+					show_cancelled_entries: me.frm.doc.docstatus === 2,
+					ignore_prepared_report: true
 				};
 				frappe.set_route("query-report", "Stock Ledger");
 			}, __("View"));
@@ -75,7 +76,8 @@
 					to_date: moment(me.frm.doc.modified).format('YYYY-MM-DD'),
 					company: me.frm.doc.company,
 					group_by: "Group by Voucher (Consolidated)",
-					show_cancelled_entries: me.frm.doc.docstatus === 2
+					show_cancelled_entries: me.frm.doc.docstatus === 2,
+					ignore_prepared_report: true
 				};
 				frappe.set_route("query-report", "General Ledger");
 			}, __("View"));
diff --git a/erpnext/public/js/erpnext.bundle.js b/erpnext/public/js/erpnext.bundle.js
index d7bea7b..966a9e1 100644
--- a/erpnext/public/js/erpnext.bundle.js
+++ b/erpnext/public/js/erpnext.bundle.js
@@ -28,5 +28,6 @@
 import "./utils/landed_taxes_and_charges_common.js";
 import "./utils/sales_common.js";
 import "./controllers/buying.js";
+import "./utils/demo.js";
 
 // import { sum } from 'frappe/public/utils/util.js'
diff --git a/erpnext/public/js/setup_wizard.js b/erpnext/public/js/setup_wizard.js
index 934fd1f..ba200ef 100644
--- a/erpnext/public/js/setup_wizard.js
+++ b/erpnext/public/js/setup_wizard.js
@@ -40,6 +40,12 @@
 			{ fieldname: 'fy_start_date', label: __('Financial Year Begins On'), fieldtype: 'Date', reqd: 1 },
 			// end date should be hidden (auto calculated)
 			{ fieldname: 'fy_end_date', label: __('End Date'), fieldtype: 'Date', reqd: 1, hidden: 1 },
+			{ fieldtype: "Section Break" },
+			{
+				fieldname: 'setup_demo',
+				label: __('Generate Demo Data for Exploration'),
+				fieldtype: 'Check',
+				description: 'If checked, we will create demo data for you to explore the system. This demo data can be erased later.'},
 		],
 
 		onload: function (slide) {
diff --git a/erpnext/public/js/utils/demo.js b/erpnext/public/js/utils/demo.js
new file mode 100644
index 0000000..b59c476
--- /dev/null
+++ b/erpnext/public/js/utils/demo.js
@@ -0,0 +1,91 @@
+$(document).on("toolbar_setup", function () {
+	if (frappe.boot.sysdefaults.demo_company) {
+		render_clear_demo_button();
+	}
+
+	// for first load after setup.
+	frappe.realtime.on("demo_data_complete", () => {
+		render_clear_demo_button();
+	});
+});
+
+function render_clear_demo_button() {
+	let wait_for_onboaring_tours = setInterval(() => {
+		if ($("#driver-page-overlay").length) {
+			return;
+		}
+		setup_clear_demo_button();
+		clearInterval(wait_for_onboaring_tours);
+	}, 2000);
+}
+
+function setup_clear_demo_button() {
+	let message_string = __(
+		"Demo data is present on the system, erase data before starting real usage."
+	);
+	let $floatingBar = $(`
+		<div class="flex justify-content-center" style="width: 100%;">
+			<div class="flex justify-content-center flex-col shadow rounded p-2"
+					style="
+						background-color: #e0f2fe;
+						position: fixed;
+						bottom: 20px;
+						z-index: 1;">
+			<p style="margin: auto 0; padding-left: 10px; margin-right: 20px; font-size: 15px;">
+					${message_string}
+				</p>
+				<button id="clear-demo" type="button"
+					class="
+						px-4
+						py-2
+						border
+						border-transparent
+						text-white
+					"
+					style="
+						margin: auto 0;
+						height: fit-content;
+						background-color: #007bff;
+						border-radius: 5px;
+						margin-right: 10px
+					"
+				>
+					Clear Demo Data
+				</button>
+
+	 			<a type="button" id="dismiss-demo-banner" class="text-muted" style="align-self: center">
+					<svg class="icon" style="">
+						<use class="" href="#icon-close"></use>
+					</svg>
+				</a>
+			</div>
+		</div>
+	`);
+
+	$("footer").append($floatingBar);
+
+	$("#clear-demo").on("click", function () {
+		frappe.confirm(
+			__("Are you sure you want to clear all demo data?"),
+			() => {
+				frappe.call({
+					method: "erpnext.setup.demo.clear_demo_data",
+					freeze: true,
+					freeze_message: __("Clearing Demo Data..."),
+					callback: function (r) {
+						frappe.ui.toolbar.clear_cache();
+						frappe.show_alert({
+							message: __("Demo data cleared"),
+							indicator: "green",
+						});
+						$("footer").remove($floatingBar);
+					},
+				});
+			}
+		);
+	});
+
+	$("#dismiss-demo-banner").on("click", function () {
+		$floatingBar.remove();
+	});
+}
diff --git a/erpnext/public/scss/point-of-sale.scss b/erpnext/public/scss/point-of-sale.scss
index c9d001c..ba64b59 100644
--- a/erpnext/public/scss/point-of-sale.scss
+++ b/erpnext/public/scss/point-of-sale.scss
@@ -53,7 +53,7 @@
 
 	.seperator {
 		margin-left: var(--margin-sm);
-		margin-right: var(--margin-sm);
+		margin-right: var(--margin-md);
 		border-bottom: 1px solid var(--gray-300);
 	}
 
@@ -381,6 +381,7 @@
 						align-items: center;
 						padding: var(--padding-sm);
 						border-radius: var(--border-radius-md);
+						margin-right: var(--margin-sm);
 
 						&:hover {
 							background-color: var(--control-bg);
@@ -858,13 +859,10 @@
 
 			> .fields-section {
 				flex: 1;
-				position: absolute;
 				display: flex;
 				flex-direction: column;
 				width: 50%;
 				height: 100%;
-				top: 0;
-				left: 0;
 				padding-bottom: var(--margin-md);
 
 				.invoice-fields {
@@ -1152,3 +1150,62 @@
 		}
 	}
 }
+
+@media screen and (max-width: 620px) {
+	.point-of-sale-app {
+		grid-template-columns: repeat(1, minmax(0, 1fr));
+
+		> .items-selector {
+			grid-column: span 6 / span 1  !important;
+			> .items-container {
+			grid-template-columns: repeat(2, minmax(0, 1fr)) !important;
+			}
+		}
+
+		> .item-details-container, .customer-cart-container {
+			grid-column: span 6 / span 1;
+		}
+
+		> .payment-container {
+			overflow: scroll;
+			> .fields-numpad-container {
+				flex-direction: column-reverse;
+				> .number-pad {
+					display: none;
+				}
+				> .fields-section {
+					width: 100%;
+				}
+			}
+		}
+
+		> .past-order-summary{
+			> .invoice-summary-wrapper {
+				width: 100%;
+			}
+		}
+
+		.numpad-totals {
+			> span {
+				padding: 0 5px;
+				font-size: var(--text-sm);
+			}
+		}
+
+		.col > * {
+			font-size: var(--text-sm) !important;
+		}
+
+		.control-input-wrapper {
+			padding-left: 0.15rem;
+		}
+
+		.pay-amount {
+			margin-left: 0.2rem;
+		}
+
+		.past-order-list {
+			grid-column: span 6 / span 1;
+		}
+	}
+}
diff --git a/erpnext/regional/doctype/lower_deduction_certificate/lower_deduction_certificate.py b/erpnext/regional/doctype/lower_deduction_certificate/lower_deduction_certificate.py
index cc223e9..6ae04c1 100644
--- a/erpnext/regional/doctype/lower_deduction_certificate/lower_deduction_certificate.py
+++ b/erpnext/regional/doctype/lower_deduction_certificate/lower_deduction_certificate.py
@@ -34,6 +34,7 @@
 				"supplier": self.supplier,
 				"tax_withholding_category": self.tax_withholding_category,
 				"name": ("!=", self.name),
+				"company": self.company,
 			},
 			["name", "valid_from", "valid_upto"],
 			as_dict=True,
diff --git a/erpnext/selling/doctype/sales_order/sales_order.py b/erpnext/selling/doctype/sales_order/sales_order.py
index ca669f6..cc141ff 100755
--- a/erpnext/selling/doctype/sales_order/sales_order.py
+++ b/erpnext/selling/doctype/sales_order/sales_order.py
@@ -108,18 +108,26 @@
 				and customer = %s",
 				(self.po_no, self.name, self.customer),
 			)
-			if (
-				so
-				and so[0][0]
-				and not cint(
+			if so and so[0][0]:
+				if cint(
 					frappe.db.get_single_value("Selling Settings", "allow_against_multiple_purchase_orders")
-				)
-			):
-				frappe.msgprint(
-					_("Warning: Sales Order {0} already exists against Customer's Purchase Order {1}").format(
-						so[0][0], self.po_no
+				):
+					frappe.msgprint(
+						_("Warning: Sales Order {0} already exists against Customer's Purchase Order {1}").format(
+							frappe.bold(so[0][0]), frappe.bold(self.po_no)
+						)
 					)
-				)
+				else:
+					frappe.throw(
+						_(
+							"Sales Order {0} already exists against Customer's Purchase Order {1}. To allow multiple Sales Orders, Enable {2} in {3}"
+						).format(
+							frappe.bold(so[0][0]),
+							frappe.bold(self.po_no),
+							frappe.bold(_("'Allow Multiple Sales Orders Against a Customer's Purchase Order'")),
+							get_link_to_form("Selling Settings", "Selling Settings"),
+						)
+					)
 
 	def validate_for_items(self):
 		for d in self.get("items"):
diff --git a/erpnext/selling/doctype/sales_order/test_sales_order.py b/erpnext/selling/doctype/sales_order/test_sales_order.py
index c85a4fb..954393f 100644
--- a/erpnext/selling/doctype/sales_order/test_sales_order.py
+++ b/erpnext/selling/doctype/sales_order/test_sales_order.py
@@ -2055,7 +2055,7 @@
 	so.company = args.company or "_Test Company"
 	so.customer = args.customer or "_Test Customer"
 	so.currency = args.currency or "INR"
-	so.po_no = args.po_no or "12345"
+	so.po_no = args.po_no or ""
 	if args.selling_price_list:
 		so.selling_price_list = args.selling_price_list
 
diff --git a/erpnext/selling/doctype/selling_settings/selling_settings.json b/erpnext/selling/doctype/selling_settings/selling_settings.json
index 045227f..6855012 100644
--- a/erpnext/selling/doctype/selling_settings/selling_settings.json
+++ b/erpnext/selling/doctype/selling_settings/selling_settings.json
@@ -20,6 +20,7 @@
   "editable_price_list_rate",
   "validate_selling_price",
   "editable_bundle_item_rates",
+  "allow_negative_rates_for_items",
   "sales_transactions_settings_section",
   "so_required",
   "dn_required",
@@ -85,7 +86,7 @@
    "fieldname": "sales_update_frequency",
    "fieldtype": "Select",
    "label": "Sales Update Frequency in Company and Project",
-   "options": "Each Transaction\nDaily\nMonthly",
+   "options": "Monthly\nEach Transaction\nDaily",
    "reqd": 1
   },
   {
@@ -193,6 +194,12 @@
    "fieldname": "dont_reserve_sales_order_qty_on_sales_return",
    "fieldtype": "Check",
    "label": "Don't Reserve Sales Order Qty on Sales Return"
+  },
+  {
+   "default": "0",
+   "fieldname": "allow_negative_rates_for_items",
+   "fieldtype": "Check",
+   "label": "Allow Negative rates for Items"
   }
  ],
  "icon": "fa fa-cog",
@@ -200,7 +207,7 @@
  "index_web_pages_for_search": 1,
  "issingle": 1,
  "links": [],
- "modified": "2023-02-04 12:37:53.380857",
+ "modified": "2023-08-14 20:33:05.693667",
  "modified_by": "Administrator",
  "module": "Selling",
  "name": "Selling Settings",
@@ -229,4 +236,4 @@
  "sort_order": "DESC",
  "states": [],
  "track_changes": 1
-}
+}
\ No newline at end of file
diff --git a/erpnext/selling/page/point_of_sale/pos_item_cart.js b/erpnext/selling/page/point_of_sale/pos_item_cart.js
index 46490c4..193048f 100644
--- a/erpnext/selling/page/point_of_sale/pos_item_cart.js
+++ b/erpnext/selling/page/point_of_sale/pos_item_cart.js
@@ -286,7 +286,7 @@
 			this.item_is_selected = false;
 			this.$cart_container.find('.cart-item-wrapper').css("background-color", "");
 		} else {
-			$cart_item.css("background-color", "var(--gray-50)");
+			$cart_item.css("background-color", "var(--control-bg)");
 			this.item_is_selected = true;
 			this.$cart_container.find('.cart-item-wrapper').not(item).css("background-color", "");
 		}
diff --git a/erpnext/setup/demo.py b/erpnext/setup/demo.py
new file mode 100644
index 0000000..926283f
--- /dev/null
+++ b/erpnext/setup/demo.py
@@ -0,0 +1,210 @@
+# Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and Contributors
+# License: GNU General Public License v3. See license.txt
+
+import json
+import os
+from random import randint
+
+import frappe
+from frappe import _
+from frappe.utils import add_days, getdate
+
+from erpnext.accounts.doctype.payment_entry.payment_entry import get_payment_entry
+from erpnext.accounts.utils import get_fiscal_year
+from erpnext.buying.doctype.purchase_order.purchase_order import make_purchase_invoice
+from erpnext.selling.doctype.sales_order.sales_order import make_sales_invoice
+from erpnext.setup.setup_wizard.operations.install_fixtures import create_bank_account
+
+
+def setup_demo_data():
+	from frappe.utils.telemetry import capture
+
+	capture("demo_data_creation_started", "erpnext")
+	try:
+		company = create_demo_company()
+		process_masters()
+		make_transactions(company)
+		frappe.cache.delete_keys("bootinfo")
+		frappe.publish_realtime("demo_data_complete")
+	except Exception:
+		frappe.log_error("Failed to create demo data")
+		capture("demo_data_creation_failed", "erpnext", properties={"exception": frappe.get_traceback()})
+		raise
+	capture("demo_data_creation_completed", "erpnext")
+
+
+@frappe.whitelist()
+def clear_demo_data():
+	from frappe.utils.telemetry import capture
+
+	frappe.only_for("System Manager")
+
+	capture("demo_data_erased", "erpnext")
+	try:
+		company = frappe.db.get_single_value("Global Defaults", "demo_company")
+		create_transaction_deletion_record(company)
+		clear_masters()
+		delete_company(company)
+		default_company = frappe.db.get_single_value("Global Defaults", "default_company")
+		frappe.db.set_default("company", default_company)
+	except Exception:
+		frappe.db.rollback()
+		frappe.log_error("Failed to erase demo data")
+		frappe.throw(
+			_("Failed to erase demo data, please delete the demo company manually."),
+			title=_("Could Not Delete Demo Data"),
+		)
+
+
+def create_demo_company():
+	company = frappe.db.get_all("Company")[0].name
+	company_doc = frappe.get_doc("Company", company)
+
+	# Make a dummy company
+	new_company = frappe.new_doc("Company")
+	new_company.company_name = company_doc.company_name + " (Demo)"
+	new_company.abbr = company_doc.abbr + "D"
+	new_company.enable_perpetual_inventory = 1
+	new_company.default_currency = company_doc.default_currency
+	new_company.country = company_doc.country
+	new_company.chart_of_accounts_based_on = "Standard Template"
+	new_company.chart_of_accounts = company_doc.chart_of_accounts
+	new_company.insert()
+
+	# Set Demo Company as default to
+	frappe.db.set_single_value("Global Defaults", "demo_company", new_company.name)
+	frappe.db.set_default("company", new_company.name)
+
+	bank_account = create_bank_account({"company_name": new_company.name})
+	frappe.db.set_value("Company", new_company.name, "default_bank_account", bank_account.name)
+
+	return new_company.name
+
+
+def process_masters():
+	for doctype in frappe.get_hooks("demo_master_doctypes"):
+		data = read_data_file_using_hooks(doctype)
+		if data:
+			for item in json.loads(data):
+				create_demo_record(item)
+
+
+def create_demo_record(doctype):
+	frappe.get_doc(doctype).insert(ignore_permissions=True)
+
+
+def make_transactions(company):
+	frappe.db.set_single_value("Stock Settings", "allow_negative_stock", 1)
+	start_date = get_fiscal_year(date=getdate())[1]
+
+	for doctype in frappe.get_hooks("demo_transaction_doctypes"):
+		data = read_data_file_using_hooks(doctype)
+		if data:
+			for item in json.loads(data):
+				create_transaction(item, company, start_date)
+
+	convert_order_to_invoices()
+	frappe.db.set_single_value("Stock Settings", "allow_negative_stock", 0)
+
+
+def create_transaction(doctype, company, start_date):
+	document_type = doctype.get("doctype")
+	warehouse = get_warehouse(company)
+
+	if document_type == "Purchase Order":
+		posting_date = get_random_date(start_date, 1, 30)
+	else:
+		posting_date = get_random_date(start_date, 31, 364)
+
+	doctype.update(
+		{
+			"company": company,
+			"set_posting_time": 1,
+			"transaction_date": posting_date,
+			"schedule_date": posting_date,
+			"delivery_date": posting_date,
+			"set_warehouse": warehouse,
+		}
+	)
+
+	doc = frappe.get_doc(doctype)
+	doc.save(ignore_permissions=True)
+	doc.submit()
+
+
+def convert_order_to_invoices():
+	for document in ["Purchase Order", "Sales Order"]:
+		# Keep some orders intentionally unbilled/unpaid
+		for i, order in enumerate(
+			frappe.db.get_all(
+				document, filters={"docstatus": 1}, fields=["name", "transaction_date"], limit=6
+			)
+		):
+
+			if document == "Purchase Order":
+				invoice = make_purchase_invoice(order.name)
+			elif document == "Sales Order":
+				invoice = make_sales_invoice(order.name)
+
+			invoice.set_posting_time = 1
+			invoice.posting_date = order.transaction_date
+			invoice.due_date = order.transaction_date
+			invoice.update_stock = 1
+			invoice.submit()
+
+			if i % 2 != 0:
+				payment = get_payment_entry(invoice.doctype, invoice.name)
+				payment.reference_no = invoice.name
+				payment.submit()
+
+
+def get_random_date(start_date, start_range, end_range):
+	return add_days(start_date, randint(start_range, end_range))
+
+
+def create_transaction_deletion_record(company):
+	transaction_deletion_record = frappe.new_doc("Transaction Deletion Record")
+	transaction_deletion_record.company = company
+	transaction_deletion_record.save(ignore_permissions=True)
+	transaction_deletion_record.submit()
+
+
+def clear_masters():
+	for doctype in frappe.get_hooks("demo_master_doctypes")[::-1]:
+		data = read_data_file_using_hooks(doctype)
+		if data:
+			for item in json.loads(data):
+				clear_demo_record(item)
+
+
+def clear_demo_record(document):
+	document_type = document.get("doctype")
+	del document["doctype"]
+
+	valid_columns = frappe.get_meta(document_type).get_valid_columns()
+
+	filters = document
+	for key in list(filters):
+		if key not in valid_columns:
+			filters.pop(key, None)
+
+	doc = frappe.get_doc(document_type, filters)
+	doc.delete(ignore_permissions=True)
+
+
+def delete_company(company):
+	frappe.db.set_single_value("Global Defaults", "demo_company", "")
+	frappe.delete_doc("Company", company, ignore_permissions=True)
+
+
+def read_data_file_using_hooks(doctype):
+	path = os.path.join(os.path.dirname(__file__), "demo_data")
+	with open(os.path.join(path, doctype + ".json"), "r") as f:
+		data = f.read()
+
+	return data
+
+
+def get_warehouse(company):
+	warehouses = frappe.db.get_all("Warehouse", {"company": company, "is_group": 0})
+	return warehouses[randint(0, 3)].name
diff --git a/erpnext/setup/demo_data/customer.json b/erpnext/setup/demo_data/customer.json
new file mode 100644
index 0000000..1b47906
--- /dev/null
+++ b/erpnext/setup/demo_data/customer.json
@@ -0,0 +1,20 @@
+[
+    {
+        "doctype": "Customer",
+        "customer_group": "Demo Customer Group",
+        "territory": "All Territories",
+        "customer_name": "Grant Plastics Ltd."
+    },
+    {
+        "doctype": "Customer",
+        "customer_group": "Demo Customer Group",
+        "territory": "All Territories",
+        "customer_name": "West View Software Ltd."
+    },
+    {
+        "doctype": "Customer",
+        "customer_group": "Demo Customer Group",
+        "territory": "All Territories",
+        "customer_name": "Palmer Productions Ltd."
+    }
+]
\ No newline at end of file
diff --git a/erpnext/setup/demo_data/customer_group.json b/erpnext/setup/demo_data/customer_group.json
new file mode 100644
index 0000000..7543335
--- /dev/null
+++ b/erpnext/setup/demo_data/customer_group.json
@@ -0,0 +1,6 @@
+[
+    {
+        "doctype": "Customer Group",
+        "customer_group_name": "Demo Customer Group"
+    }
+]
\ No newline at end of file
diff --git a/erpnext/setup/demo_data/item.json b/erpnext/setup/demo_data/item.json
new file mode 100644
index 0000000..330e114
--- /dev/null
+++ b/erpnext/setup/demo_data/item.json
@@ -0,0 +1,82 @@
+[
+    {
+        "doctype": "Item",
+        "item_group": "Demo Item Group",
+        "item_code": "SKU001",
+        "item_name": "T-shirt",
+        "gst_hsn_code": "999512",
+        "image": "https://images.pexels.com/photos/1484808/pexels-photo-1484808.jpeg"
+    },
+    {
+        "doctype": "Item",
+        "item_group": "Demo Item Group",
+        "item_code": "SKU002",
+        "item_name": "Laptop",
+        "gst_hsn_code": "999512",
+        "image": "https://images.pexels.com/photos/3999538/pexels-photo-3999538.jpeg"
+    },
+    {
+        "doctype": "Item",
+        "item_group": "Demo Item Group",
+        "item_code": "SKU003",
+        "item_name": "Book",
+        "gst_hsn_code": "999512",
+        "image": "https://images.pexels.com/photos/2422178/pexels-photo-2422178.jpeg"
+    },
+    {
+        "doctype": "Item",
+        "item_group": "Demo Item Group",
+        "item_code": "SKU004",
+        "item_name": "Smartphone",
+        "gst_hsn_code": "999512",
+        "image": "https://images.pexels.com/photos/1647976/pexels-photo-1647976.jpeg"
+    },
+    {
+        "doctype": "Item",
+        "item_group": "Demo Item Group",
+        "item_code": "SKU005",
+        "item_name": "Sneakers",
+        "gst_hsn_code": "999512",
+        "image": "https://images.pexels.com/photos/1598505/pexels-photo-1598505.jpeg"
+    },
+    {
+        "doctype": "Item",
+        "item_group": "Demo Item Group",
+        "item_code": "SKU006",
+        "item_name": "Coffee Mug",
+        "gst_hsn_code": "999512",
+        "image": "https://images.pexels.com/photos/585753/pexels-photo-585753.jpeg"
+    },
+    {
+        "doctype": "Item",
+        "item_group": "Demo Item Group",
+        "item_code": "SKU007",
+        "item_name": "Television",
+        "gst_hsn_code": "999512",
+        "image": "https://images.pexels.com/photos/8059376/pexels-photo-8059376.jpeg"
+    },
+    {
+        "doctype": "Item",
+        "item_group": "Demo Item Group",
+        "item_code": "SKU008",
+        "item_name": "Backpack",
+        "gst_hsn_code": "999512",
+        "image": "https://images.pexels.com/photos/3731256/pexels-photo-3731256.jpeg"
+    },
+    {
+        "doctype": "Item",
+        "item_group": "Demo Item Group",
+        "item_code": "SKU009",
+        "item_name": "Headphones",
+        "gst_hsn_code": "999512",
+        "image": "https://images.pexels.com/photos/3587478/pexels-photo-3587478.jpeg"
+    },
+    {
+        "doctype": "Item",
+        "item_group": "Demo Item Group",
+        "item_code": "SKU010",
+        "item_name": "Camera",
+        "gst_hsn_code": "999512",
+        "image": "https://images.pexels.com/photos/51383/photo-camera-subject-photographer-51383.jpeg"
+    }
+]
diff --git a/erpnext/setup/demo_data/item_group.json b/erpnext/setup/demo_data/item_group.json
new file mode 100644
index 0000000..f96944d
--- /dev/null
+++ b/erpnext/setup/demo_data/item_group.json
@@ -0,0 +1,6 @@
+[
+    {
+        "doctype": "Item Group",
+        "item_group_name": "Demo Item Group"
+    }
+]
diff --git a/erpnext/setup/demo_data/journal_entry.json b/erpnext/setup/demo_data/journal_entry.json
new file mode 100644
index 0000000..b751c7c
--- /dev/null
+++ b/erpnext/setup/demo_data/journal_entry.json
@@ -0,0 +1,25 @@
+[
+    {
+        "cheque_date": "2023-03-14",
+        "cheque_no": "33",
+        "doctype": "Journal Entry",
+        "accounts": [
+         {
+          "party_type": "Customer",
+          "party": "ABC Enterprises",
+          "credit_in_account_currency": 40000.0,
+          "debit_in_account_currency": 0.0,
+          "doctype": "Journal Entry Account",
+          "parentfield": "accounts",
+         },
+         {
+          "credit_in_account_currency": 0.0,
+          "debit_in_account_currency": 40000.0,
+          "doctype": "Journal Entry Account",
+          "parentfield": "accounts",
+         }
+        ],
+        "user_remark": "test",
+        "voucher_type": "Bank Entry"
+    }
+]
\ No newline at end of file
diff --git a/erpnext/setup/demo_data/payment_entry.json b/erpnext/setup/demo_data/payment_entry.json
new file mode 100644
index 0000000..c0767c3
--- /dev/null
+++ b/erpnext/setup/demo_data/payment_entry.json
@@ -0,0 +1,57 @@
+[
+    {
+        "doctype": "Payment Entry",
+        "payment_type": "Receive",
+        "party_type": "Customer",
+        "party": "ABC Enterprises",
+        "paid_amount": 67000,
+        "received_amount": 67000,
+        "reference_no": "#ref0001",
+        "source_exchange_rate": 1,
+        "target_exchange_rate": 1
+    },
+    {
+        "doctype": "Payment Entry",
+        "payment_type": "Receive",
+        "party_type": "Customer",
+        "party": "XYZ Corporation",
+        "paid_amount": 500000,
+        "received_amount": 500000,
+        "reference_no": "#ref0001",
+        "source_exchange_rate": 1,
+        "target_exchange_rate": 1
+    },
+    {
+        "doctype": "Payment Entry",
+        "payment_type": "Receive",
+        "party_type": "Customer",
+        "party": "KJPR Pvt. Ltd.",
+        "paid_amount": 300000,
+        "received_amount": 30000,
+        "reference_no": "#ref0001",
+        "source_exchange_rate": 1,
+        "target_exchange_rate": 1
+    },
+    {
+        "doctype": "Payment Entry",
+        "payment_type": "Pay",
+        "party_type": "Supplier",
+        "party": "DQ Industries",
+        "paid_amount": 85000,
+        "received_amount": 85000,
+        "reference_no": "#ref0005",
+        "source_exchange_rate": 1,
+        "target_exchange_rate": 1
+    },
+    {
+        "doctype": "Payment Entry",
+        "payment_type": "Pay",
+        "party_type": "Supplier",
+        "party": "KC Corp.",
+        "paid_amount": 100000,
+        "received_amount": 100000,
+        "reference_no": "#ref0006",
+        "source_exchange_rate": 1,
+        "target_exchange_rate": 1
+    }
+]
\ No newline at end of file
diff --git a/erpnext/setup/demo_data/purchase_order.json b/erpnext/setup/demo_data/purchase_order.json
new file mode 100644
index 0000000..318a865
--- /dev/null
+++ b/erpnext/setup/demo_data/purchase_order.json
@@ -0,0 +1,172 @@
+[
+    {
+        "conversion_rate": 1.0,
+        "supplier": "Zuckerman Security Ltd.",
+        "doctype": "Purchase Order",
+        "update_stock": 1,
+        "disable_rounded_total": 1,
+        "items": [
+            {
+                "doctype": "Purchase Order Item",
+                "item_code": "SKU001",
+                "parentfield": "items",
+                "qty": 100.0,
+                "rate": 400.0,
+                "conversion_factor": 1
+            }
+        ]
+    },
+    {
+        "conversion_rate": 1.0,
+        "supplier": "MA Inc.",
+        "doctype": "Purchase Order",
+        "update_stock": 1,
+        "disable_rounded_total": 1,
+        "items": [
+            {
+                "doctype": "Purchase Order Item",
+                "item_code": "SKU002",
+                "parentfield": "items",
+                "qty": 50.0,
+                "rate": 300.0,
+                "conversion_factor": 1
+            }
+        ]
+    },
+    {
+        "conversion_rate": 1.0,
+        "supplier": "Summit Traders Ltd.",
+        "doctype": "Purchase Order",
+        "update_stock": 1,
+        "disable_rounded_total": 1,
+        "items": [
+            {
+                "doctype": "Purchase Order Item",
+                "item_code": "SKU003",
+                "parentfield": "items",
+                "qty": 200.0,
+                "rate": 523.0,
+                "conversion_factor": 1
+            }
+        ]
+    },
+    {
+        "conversion_rate": 1.0,
+        "supplier": "Zuckerman Security Ltd.",
+        "doctype": "Purchase Order",
+        "update_stock": 1,
+        "disable_rounded_total": 1,
+        "items": [
+            {
+                "doctype": "Purchase Order Item",
+                "item_code": "SKU004",
+                "parentfield": "items",
+                "qty": 60.0,
+                "rate": 725.0,
+                "conversion_factor": 1
+            }
+        ]
+    },
+    {
+        "conversion_rate": 1.0,
+        "supplier": "MA Inc.",
+        "doctype": "Purchase Order",
+        "update_stock": 1,
+        "disable_rounded_total": 1,
+        "items": [
+            {
+                "doctype": "Purchase Order Item",
+                "item_code": "SKU005",
+                "parentfield": "items",
+                "qty": 182.0,
+                "rate": 222.0,
+                "conversion_factor": 1
+            }
+        ]
+    },
+    {
+        "conversion_rate": 1.0,
+        "supplier": "Summit Traders Ltd.",
+        "doctype": "Purchase Order",
+        "update_stock": 1,
+        "disable_rounded_total": 1,
+        "items": [
+            {
+                "doctype": "Purchase Order Item",
+                "item_code": "SKU006",
+                "parentfield": "items",
+                "qty": 250.0,
+                "rate": 420.0,
+                "conversion_factor": 1
+            }
+        ]
+    },
+    {
+        "conversion_rate": 1.0,
+        "supplier": "Zuckerman Security Ltd.",
+        "doctype": "Purchase Order",
+        "update_stock": 1,
+        "disable_rounded_total": 1,
+        "items": [
+            {
+                "doctype": "Purchase Order Item",
+                "item_code": "SKU007",
+                "parentfield": "items",
+                "qty": 190.0,
+                "rate": 375.0,
+                "conversion_factor": 1
+            }
+        ]
+    },
+    {
+        "conversion_rate": 1.0,
+        "supplier": "MA Inc.",
+        "doctype": "Purchase Order",
+        "update_stock": 1,
+        "disable_rounded_total": 1,
+        "items": [
+            {
+                "doctype": "Purchase Order Item",
+                "item_code": "SKU008",
+                "parentfield": "items",
+                "qty": 121.0,
+                "rate": 333.0,
+                "conversion_factor": 1
+            }
+        ]
+    },
+    {
+        "conversion_rate": 1.0,
+        "supplier": "Summit Traders Ltd.",
+        "doctype": "Purchase Order",
+        "update_stock": 1,
+        "disable_rounded_total": 1,
+        "items": [
+            {
+                "doctype": "Purchase Order Item",
+                "item_code": "SKU009",
+                "parentfield": "items",
+                "qty": 76.0,
+                "rate": 700.0,
+                "conversion_factor": 1
+            }
+        ]
+    },
+    {
+        "conversion_rate": 1.0,
+        "supplier": "Zuckerman Security Ltd.",
+        "doctype": "Purchase Order",
+        "update_stock": 1,
+        "disable_rounded_total": 1,
+        "items": [
+            {
+                "doctype": "Purchase Order Item",
+                "item_code": "SKU010",
+                "parentfield": "items",
+                "qty": 78.0,
+                "rate": 500.0,
+                "conversion_factor": 1
+            }
+        ]
+    }
+]
\ No newline at end of file
diff --git a/erpnext/setup/demo_data/sales_order.json b/erpnext/setup/demo_data/sales_order.json
new file mode 100644
index 0000000..29bffc3
--- /dev/null
+++ b/erpnext/setup/demo_data/sales_order.json
@@ -0,0 +1,127 @@
+[
+    {
+        "conversion_rate": 1.0,
+        "customer": "Grant Plastics Ltd.",
+        "doctype": "Sales Order",
+        "update_stock": 1,
+        "disable_rounded_total": 1,
+        "items": [
+            {
+                "doctype": "Sales Order Item",
+                "item_code": "SKU004",
+                "parentfield": "items",
+                "qty": 20.0,
+                "rate": 1000.0,
+                "conversion_factor": 1
+            }
+        ]
+    },
+    {
+        "conversion_rate": 1.0,
+        "customer": "West View Software Ltd.",
+        "doctype": "Sales Order",
+        "update_stock": 1,
+        "disable_rounded_total": 1,
+        "items": [
+            {
+                "doctype": "Sales Order Item",
+                "item_code": "SKU001",
+                "parentfield": "items",
+                "qty": 25.0,
+                "rate": 800.0,
+                "conversion_factor": 1
+            },
+            {
+                "doctype": "Sales Order Item",
+                "item_code": "SKU002",
+                "parentfield": "items",
+                "qty": 15.0,
+                "rate": 800.0,
+                "conversion_factor": 1
+            }
+        ]
+    },
+    {
+        "conversion_rate": 1.0,
+        "customer": "West View Software Ltd.",
+        "doctype": "Sales Order",
+        "update_stock": 1,
+        "disable_rounded_total": 1,
+        "items": [
+            {
+                "doctype": "Sales Order Item",
+                "item_code": "SKU003",
+                "parentfield": "items",
+                "qty": 100,
+                "rate": 500.0,
+                "conversion_factor": 1
+            },
+            {
+                "doctype": "Sales Order Item",
+                "item_code": "SKU006",
+                "parentfield": "items",
+                "qty": 100,
+                "rate": 890.0,
+                "conversion_factor": 1
+            },
+            {
+                "doctype": "Sales Order Item",
+                "item_code": "SKU007",
+                "parentfield": "items",
+                "qty": 100,
+                "rate": 900.0,
+                "conversion_factor": 1
+            }
+        ]
+    },
+    {
+        "conversion_rate": 1.0,
+        "customer": "Palmer Productions Ltd.",
+        "doctype": "Sales Order",
+        "update_stock": 1,
+        "disable_rounded_total": 1,
+        "items": [
+            {
+                "doctype": "Sales Order Item",
+                "item_code": "SKU005",
+                "parentfield": "items",
+                "qty": 150.0,
+                "rate": 100.0,
+                "conversion_factor": 1
+            }
+        ]
+    },
+    {
+        "conversion_rate": 1.0,
+        "customer": "Grant Plastics Ltd.",
+        "doctype": "Sales Order",
+        "update_stock": 1,
+        "disable_rounded_total": 1,
+        "items": [
+            {
+                "doctype": "Sales Order Item",
+                "item_code": "SKU008",
+                "parentfield": "items",
+                "qty": 20.0,
+                "rate": 500.0,
+                "conversion_factor": 1
+            },
+            {
+                "doctype": "Sales Order Item",
+                "item_code": "SKU009",
+                "parentfield": "items",
+                "qty": 40.0,
+                "rate": 300.0,
+                "conversion_factor": 1
+            },
+            {
+                "doctype": "Sales Order Item",
+                "item_code": "SKU010",
+                "parentfield": "items",
+                "qty": 50.0,
+                "rate": 900.0,
+                "conversion_factor": 1
+            }
+        ]
+    }
+]
\ No newline at end of file
diff --git a/erpnext/setup/demo_data/supplier.json b/erpnext/setup/demo_data/supplier.json
new file mode 100644
index 0000000..01a4e89
--- /dev/null
+++ b/erpnext/setup/demo_data/supplier.json
@@ -0,0 +1,17 @@
+[
+    {
+        "doctype": "Supplier",
+        "supplier_group": "Demo Supplier Group",
+        "supplier_name": "Zuckerman Security Ltd."
+    },
+    {
+        "doctype": "Supplier",
+        "supplier_group": "Demo Supplier Group",
+        "supplier_name": "MA Inc."
+    },
+    {
+        "doctype": "Supplier",
+        "supplier_group": "Demo Supplier Group",
+        "supplier_name": "Summit Traders Ltd."
+    }
+]
\ No newline at end of file
diff --git a/erpnext/setup/demo_data/supplier_group.json b/erpnext/setup/demo_data/supplier_group.json
new file mode 100644
index 0000000..17070bf
--- /dev/null
+++ b/erpnext/setup/demo_data/supplier_group.json
@@ -0,0 +1,6 @@
+[
+    {
+        "doctype": "Supplier Group",
+        "supplier_group_name": "Demo Supplier Group"
+    }
+]
\ No newline at end of file
diff --git a/erpnext/setup/doctype/company/company.js b/erpnext/setup/doctype/company/company.js
index f4682c1..fa207ec 100644
--- a/erpnext/setup/doctype/company/company.js
+++ b/erpnext/setup/doctype/company/company.js
@@ -18,6 +18,7 @@
 		});
 	},
 	setup: function(frm) {
+		frm.__rename_queue = "long";
 		erpnext.company.setup_queries(frm);
 
 		frm.set_query("parent_company", function() {
diff --git a/erpnext/setup/doctype/company/test_company.py b/erpnext/setup/doctype/company/test_company.py
index fd2fe30..babd7dd 100644
--- a/erpnext/setup/doctype/company/test_company.py
+++ b/erpnext/setup/doctype/company/test_company.py
@@ -195,6 +195,22 @@
 		child_company.save()
 		self.test_basic_tree()
 
+	def test_demo_data(self):
+		from erpnext.setup.demo import clear_demo_data, setup_demo_data
+
+		setup_demo_data()
+		company_name = frappe.db.get_value("Company", {"name": ("like", "%(Demo)")})
+		self.assertTrue(company_name)
+
+		for transaction in frappe.get_hooks("demo_transaction_doctypes"):
+			self.assertTrue(frappe.db.exists(frappe.unscrub(transaction), {"company": company_name}))
+
+		clear_demo_data()
+		company_name = frappe.db.get_value("Company", {"name": ("like", "%(Demo)")})
+		self.assertFalse(company_name)
+		for transaction in frappe.get_hooks("demo_transaction_doctypes"):
+			self.assertFalse(frappe.db.exists(frappe.unscrub(transaction), {"company": company_name}))
+
 
 def create_company_communication(doctype, docname):
 	comm = frappe.get_doc(
diff --git a/erpnext/setup/doctype/global_defaults/global_defaults.json b/erpnext/setup/doctype/global_defaults/global_defaults.json
index 823d2ba..bd80e1d 100644
--- a/erpnext/setup/doctype/global_defaults/global_defaults.json
+++ b/erpnext/setup/doctype/global_defaults/global_defaults.json
@@ -12,7 +12,8 @@
   "default_currency",
   "hide_currency_symbol",
   "disable_rounded_total",
-  "disable_in_words"
+  "disable_in_words",
+  "demo_company"
  ],
  "fields": [
   {
@@ -71,6 +72,14 @@
    "fieldtype": "Check",
    "in_list_view": 1,
    "label": "Disable In Words"
+  },
+  {
+   "fieldname": "demo_company",
+   "fieldtype": "Link",
+   "hidden": 1,
+   "label": "Demo Company",
+   "options": "Company",
+   "read_only": 1
   }
  ],
  "icon": "fa fa-cog",
diff --git a/erpnext/setup/doctype/holiday_list/holiday_list.py b/erpnext/setup/doctype/holiday_list/holiday_list.py
index 2ef4e65..526bc2b 100644
--- a/erpnext/setup/doctype/holiday_list/holiday_list.py
+++ b/erpnext/setup/doctype/holiday_list/holiday_list.py
@@ -6,12 +6,9 @@
 from datetime import date
 
 import frappe
-from babel import Locale
 from frappe import _, throw
 from frappe.model.document import Document
 from frappe.utils import formatdate, getdate, today
-from holidays import country_holidays
-from holidays.utils import list_supported_countries
 
 
 class OverlapError(frappe.ValidationError):
@@ -40,6 +37,8 @@
 
 	@frappe.whitelist()
 	def get_supported_countries(self):
+		from holidays.utils import list_supported_countries
+
 		subdivisions_by_country = list_supported_countries()
 		countries = [
 			{"value": country, "label": local_country_name(country)}
@@ -52,6 +51,8 @@
 
 	@frappe.whitelist()
 	def get_local_holidays(self):
+		from holidays import country_holidays
+
 		if not self.country:
 			throw(_("Please select a country"))
 
@@ -169,4 +170,6 @@
 
 def local_country_name(country_code: str) -> str:
 	"""Return the localized country name for the given country code."""
-	return Locale.parse(frappe.local.lang).territories.get(country_code, country_code)
+	from babel import Locale
+
+	return Locale.parse(frappe.local.lang, sep="-").territories.get(country_code, country_code)
diff --git a/erpnext/setup/doctype/holiday_list/test_holiday_list.py b/erpnext/setup/doctype/holiday_list/test_holiday_list.py
index 23b08fd..7eeb27d 100644
--- a/erpnext/setup/doctype/holiday_list/test_holiday_list.py
+++ b/erpnext/setup/doctype/holiday_list/test_holiday_list.py
@@ -8,6 +8,8 @@
 import frappe
 from frappe.utils import getdate
 
+from erpnext.setup.doctype.holiday_list.holiday_list import local_country_name
+
 
 class TestHolidayList(unittest.TestCase):
 	def test_holiday_list(self):
@@ -58,6 +60,16 @@
 		self.assertIn(date(2023, 4, 10), holidays)
 		self.assertNotIn(date(2023, 5, 1), holidays)
 
+	def test_localized_country_names(self):
+		lang = frappe.local.lang
+		frappe.local.lang = "en-gb"
+		self.assertEqual(local_country_name("IN"), "India")
+		self.assertEqual(local_country_name("DE"), "Germany")
+
+		frappe.local.lang = "de"
+		self.assertEqual(local_country_name("DE"), "Deutschland")
+		frappe.local.lang = lang
+
 
 def make_holiday_list(
 	name, from_date=getdate() - timedelta(days=10), to_date=getdate(), holiday_dates=None
diff --git a/erpnext/setup/onboarding_step/create_an_item/create_an_item.json b/erpnext/setup/onboarding_step/create_an_item/create_an_item.json
index 4115196..66c5bfb 100644
--- a/erpnext/setup/onboarding_step/create_an_item/create_an_item.json
+++ b/erpnext/setup/onboarding_step/create_an_item/create_an_item.json
@@ -2,7 +2,7 @@
  "action": "Create Entry",
  "action_label": "Create a new Item",
  "creation": "2021-05-17 13:47:18.515052",
- "description": "# Create an Item\n\nItem is a product, of a or service offered by your company, or something you buy as a part of your supplies or raw materials.\n\nItems are integral to everything you do in ERPNext - from billing, purchasing to managing inventory. Everything you buy or sell, whether it is a physical product or a service is an Item. Items can be stock, non-stock, variants, serialized, batched, assets etc.\n",
+ "description": "# Create an Item\n\nItem is a product or a service offered by your company, or something you buy as a part of your supplies or raw materials.\n\nItems are integral to everything you do in ERPNext - from billing, purchasing to managing inventory. Everything you buy or sell, whether it is a physical product or a service is an Item. Items can be stock, non-stock, variants, serialized, batched, assets, etc.\n",
  "docstatus": 0,
  "doctype": "Onboarding Step",
  "form_tour": "Item General",
@@ -20,4 +20,4 @@
  "show_full_form": 0,
  "title": "Create an Item",
  "validate_action": 1
-}
\ No newline at end of file
+}
diff --git a/erpnext/setup/setup_wizard/operations/install_fixtures.py b/erpnext/setup/setup_wizard/operations/install_fixtures.py
index 535c87d..ae6881b 100644
--- a/erpnext/setup/setup_wizard/operations/install_fixtures.py
+++ b/erpnext/setup/setup_wizard/operations/install_fixtures.py
@@ -490,7 +490,7 @@
 
 def create_bank_account(args):
 	if not args.get("bank_account"):
-		return
+		args["bank_account"] = _("Bank Account")
 
 	company_name = args.get("company_name")
 	bank_account_group = frappe.db.get_value(
diff --git a/erpnext/setup/setup_wizard/setup_wizard.py b/erpnext/setup/setup_wizard/setup_wizard.py
index 65b268e..2da107e 100644
--- a/erpnext/setup/setup_wizard/setup_wizard.py
+++ b/erpnext/setup/setup_wizard/setup_wizard.py
@@ -5,7 +5,8 @@
 import frappe
 from frappe import _
 
-from .operations import install_fixtures as fixtures
+from erpnext.setup.demo import setup_demo_data
+from erpnext.setup.setup_wizard.operations import install_fixtures as fixtures
 
 
 def get_setup_stages(args=None):
@@ -37,6 +38,11 @@
 				],
 			},
 			{
+				"status": _("Setting up demo data"),
+				"fail_msg": _("Failed to setup demo data"),
+				"tasks": [{"fn": setup_demo, "args": args, "fail_msg": _("Failed to setup demo data")}],
+			},
+			{
 				"status": _("Wrapping up"),
 				"fail_msg": _("Failed to login"),
 				"tasks": [{"fn": fin, "args": args, "fail_msg": _("Failed to login")}],
@@ -63,6 +69,11 @@
 	login_as_first_user(args)
 
 
+def setup_demo(args):
+	if args.get("setup_demo"):
+		frappe.enqueue(setup_demo_data, enqueue_after_commit=True, at_front=True)
+
+
 def login_as_first_user(args):
 	if args.get("email") and hasattr(frappe.local, "login_manager"):
 		frappe.local.login_manager.login_as(args.get("email"))
diff --git a/erpnext/startup/boot.py b/erpnext/startup/boot.py
index db1cc49..bdbf8b4 100644
--- a/erpnext/startup/boot.py
+++ b/erpnext/startup/boot.py
@@ -61,6 +61,8 @@
 		)
 		bootinfo.party_account_types = frappe._dict(party_account_types)
 
+		bootinfo.sysdefaults.demo_company = frappe.db.get_single_value("Global Defaults", "demo_company")
+
 
 def update_page_info(bootinfo):
 	bootinfo.page_info.update(
diff --git a/erpnext/stock/doctype/delivery_note/test_delivery_note.py b/erpnext/stock/doctype/delivery_note/test_delivery_note.py
index 0ef3027..48b8ab7 100644
--- a/erpnext/stock/doctype/delivery_note/test_delivery_note.py
+++ b/erpnext/stock/doctype/delivery_note/test_delivery_note.py
@@ -703,7 +703,7 @@
 
 	def test_dn_billing_status_case1(self):
 		# SO -> DN -> SI
-		so = make_sales_order()
+		so = make_sales_order(po_no="12345")
 		dn = create_dn_against_so(so.name, delivered_qty=2)
 
 		self.assertEqual(dn.status, "To Bill")
@@ -730,7 +730,7 @@
 			make_sales_invoice,
 		)
 
-		so = make_sales_order()
+		so = make_sales_order(po_no="12345")
 
 		si = make_sales_invoice(so.name)
 		si.get("items")[0].qty = 5
@@ -774,7 +774,7 @@
 
 		frappe.db.set_single_value("Stock Settings", "allow_negative_stock", 1)
 
-		so = make_sales_order()
+		so = make_sales_order(po_no="12345")
 
 		dn1 = make_delivery_note(so.name)
 		dn1.get("items")[0].qty = 2
@@ -820,7 +820,7 @@
 		from erpnext.accounts.doctype.sales_invoice.sales_invoice import make_delivery_note
 		from erpnext.selling.doctype.sales_order.sales_order import make_sales_invoice
 
-		so = make_sales_order()
+		so = make_sales_order(po_no="12345")
 
 		si = make_sales_invoice(so.name)
 		si.submit()
@@ -1227,6 +1227,7 @@
 		self.assertEqual(get_reserved_qty(item, warehouse), 0 if dont_reserve_qty else qty_to_reserve)
 
 	def tearDown(self):
+		frappe.db.rollback()
 		frappe.db.set_single_value("Selling Settings", "dont_reserve_sales_order_qty_on_sales_return", 0)
 
 
diff --git a/erpnext/stock/doctype/material_request/material_request.py b/erpnext/stock/doctype/material_request/material_request.py
index 00b1b20..1139c4b 100644
--- a/erpnext/stock/doctype/material_request/material_request.py
+++ b/erpnext/stock/doctype/material_request/material_request.py
@@ -225,7 +225,8 @@
 					d.ordered_qty = flt(mr_items_ordered_qty.get(d.name))
 
 					if mr_qty_allowance:
-						allowed_qty = d.qty + (d.qty * (mr_qty_allowance / 100))
+						allowed_qty = flt((d.qty + (d.qty * (mr_qty_allowance / 100))), d.precision("ordered_qty"))
+
 						if d.ordered_qty and d.ordered_qty > allowed_qty:
 							frappe.throw(
 								_(
diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.py b/erpnext/stock/doctype/stock_entry/stock_entry.py
index 0059a3f..a2cae7f 100644
--- a/erpnext/stock/doctype/stock_entry/stock_entry.py
+++ b/erpnext/stock/doctype/stock_entry/stock_entry.py
@@ -187,7 +187,7 @@
 			return False
 
 		# If line items are more than 100 or record is older than 6 months
-		if len(self.items) > 100 or month_diff(nowdate(), self.posting_date) > 6:
+		if len(self.items) > 50 or month_diff(nowdate(), self.posting_date) > 6:
 			return True
 
 		return False
diff --git a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py
index 6ea27ed..f009bd4 100644
--- a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py
+++ b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py
@@ -696,7 +696,7 @@
 			)
 
 		if sl_entries:
-			self.make_sl_entries(sl_entries)
+			self.make_sl_entries(sl_entries, allow_negative_stock=True)
 
 	def recalculate_qty_for_serial_and_batch_bundle(self, row):
 		doc = frappe.get_doc("Serial and Batch Bundle", row.current_serial_and_batch_bundle)
diff --git a/erpnext/stock/report/item_shortage_report/item_shortage_report.py b/erpnext/stock/report/item_shortage_report/item_shortage_report.py
index 9fafe91..4bd9a10 100644
--- a/erpnext/stock/report/item_shortage_report/item_shortage_report.py
+++ b/erpnext/stock/report/item_shortage_report/item_shortage_report.py
@@ -40,7 +40,12 @@
 			item.item_name,
 			item.description,
 		)
-		.where((bin.projected_qty < 0) & (wh.name == bin.warehouse) & (bin.item_code == item.name))
+		.where(
+			(item.disabled == 0)
+			& (bin.projected_qty < 0)
+			& (wh.name == bin.warehouse)
+			& (bin.item_code == item.name)
+		)
 		.orderby(bin.projected_qty)
 	)
 
diff --git a/erpnext/subcontracting/doctype/subcontracting_receipt/test_subcontracting_receipt.py b/erpnext/subcontracting/doctype/subcontracting_receipt/test_subcontracting_receipt.py
index 4663209..887cba5 100644
--- a/erpnext/subcontracting/doctype/subcontracting_receipt/test_subcontracting_receipt.py
+++ b/erpnext/subcontracting/doctype/subcontracting_receipt/test_subcontracting_receipt.py
@@ -506,6 +506,67 @@
 		self.assertNotEqual(scr.supplied_items[0].rate, prev_cost)
 		self.assertEqual(scr.supplied_items[0].rate, sr.items[0].valuation_rate)
 
+	def test_subcontracting_receipt_raw_material_rate(self):
+		from erpnext.manufacturing.doctype.production_plan.test_production_plan import make_bom
+
+		# Step - 1: Set Backflush Based On as "BOM"
+		set_backflush_based_on("BOM")
+
+		# Step - 2: Create FG and RM Items
+		fg_item = make_item(properties={"is_stock_item": 1, "is_sub_contracted_item": 1}).name
+		rm_item1 = make_item(properties={"is_stock_item": 1}).name
+		rm_item2 = make_item(properties={"is_stock_item": 1}).name
+
+		# Step - 3: Create BOM for FG Item
+		bom = make_bom(item=fg_item, raw_materials=[rm_item1, rm_item2])
+		for rm_item in bom.items:
+			self.assertEqual(rm_item.rate, 0)
+			self.assertEqual(rm_item.amount, 0)
+		bom = bom.name
+
+		# Step - 4: Create PO and SCO
+		service_items = [
+			{
+				"warehouse": "_Test Warehouse - _TC",
+				"item_code": "Subcontracted Service Item 1",
+				"qty": 100,
+				"rate": 100,
+				"fg_item": fg_item,
+				"fg_item_qty": 100,
+			},
+		]
+		sco = get_subcontracting_order(service_items=service_items)
+		for rm_item in sco.supplied_items:
+			self.assertEqual(rm_item.rate, 0)
+			self.assertEqual(rm_item.amount, 0)
+
+		# Step - 5: Inward Raw Materials
+		rm_items = get_rm_items(sco.supplied_items)
+		for rm_item in rm_items:
+			rm_item["rate"] = 100
+		itemwise_details = make_stock_in_entry(rm_items=rm_items)
+
+		# Step - 6: Transfer RM's to Subcontractor
+		se = make_stock_transfer_entry(
+			sco_no=sco.name,
+			rm_items=rm_items,
+			itemwise_details=copy.deepcopy(itemwise_details),
+		)
+		for item in se.items:
+			self.assertEqual(item.qty, 100)
+			self.assertEqual(item.basic_rate, 100)
+			self.assertEqual(item.amount, item.qty * item.basic_rate)
+
+		# Step - 7: Create Subcontracting Receipt
+		scr = make_subcontracting_receipt(sco.name)
+		scr.save()
+		scr.submit()
+		scr.load_from_db()
+		for rm_item in scr.supplied_items:
+			self.assertEqual(rm_item.consumed_qty, 100)
+			self.assertEqual(rm_item.rate, 100)
+			self.assertEqual(rm_item.amount, rm_item.consumed_qty * rm_item.rate)
+
 
 def make_return_subcontracting_receipt(**args):
 	args = frappe._dict(args)
diff --git a/erpnext/templates/emails/request_for_quotation.html b/erpnext/templates/emails/request_for_quotation.html
deleted file mode 100644
index 5b073e6..0000000
--- a/erpnext/templates/emails/request_for_quotation.html
+++ /dev/null
@@ -1,29 +0,0 @@
-<h4>{{_("Request for Quotation")}}</h4>
-<p>{{ supplier_salutation if supplier_salutation else ''}} {{ supplier_name }},</p>
-<p>{{ message }}</p>
-<p>{{_("The Request for Quotation can be accessed by clicking on the following button")}}:</p>
-<br>
-<a
-	href="{{ rfq_link }}"
-	class="btn btn-default btn-sm"
-	target="_blank">
-	{{ _("Submit your Quotation") }}
-</a>
-<br>
-<br>
-{% if update_password_link %}
-<br>
-<p>{{_("Please click on the following button to set your new password")}}:</p>
-<a
-	href="{{ update_password_link }}"
-	class="btn btn-default btn-xs"
-	target="_blank">
-	{{_("Set Password") }}
-</a>
-<br>
-<br>
-{% endif %}
-<p>
-	{{_("Regards")}},<br>
-	{{ user_fullname }}
-</p>
diff --git a/erpnext/templates/includes/order/order_taxes.html b/erpnext/templates/includes/order/order_taxes.html
index 0060ab3..d7b9620 100644
--- a/erpnext/templates/includes/order/order_taxes.html
+++ b/erpnext/templates/includes/order/order_taxes.html
@@ -19,7 +19,7 @@
 					{{ d.description }}
 				</div>
 				<div class="item-grand-total col-4 text-right pr-0">
-					{{ doc.get_formatted("net_total") }}
+					{{ d.get_formatted("base_tax_amount") }}
 				</div>
 			</div>
 		</div>