Merge pull request #39681 from ruthra-kumar/fix_gl_logic_in_advance_as_liability

fix: incorrect advance paid in Sales/Purchase Order
diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py
index 77efe78..7970a3e 100644
--- a/erpnext/accounts/doctype/payment_entry/payment_entry.py
+++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py
@@ -1271,7 +1271,13 @@
 				references = [x for x in self.get("references") if x.name == entry.name]
 
 			for ref in references:
-				if ref.reference_doctype in ("Sales Invoice", "Purchase Invoice", "Journal Entry"):
+				if ref.reference_doctype in (
+					"Sales Invoice",
+					"Purchase Invoice",
+					"Journal Entry",
+					"Sales Order",
+					"Purchase Order",
+				):
 					self.add_advance_gl_for_reference(gl_entries, ref)
 
 	def add_advance_gl_for_reference(self, gl_entries, invoice):
@@ -1285,14 +1291,15 @@
 			"voucher_detail_no": invoice.name,
 		}
 
-		posting_date = frappe.db.get_value(
-			invoice.reference_doctype, invoice.reference_name, "posting_date"
-		)
+		date_field = "posting_date"
+		if invoice.reference_doctype in ["Sales Order", "Purchase Order"]:
+			date_field = "transaction_date"
+		posting_date = frappe.db.get_value(invoice.reference_doctype, invoice.reference_name, date_field)
 
 		if getdate(posting_date) < getdate(self.posting_date):
 			posting_date = self.posting_date
 
-		dr_or_cr = "credit" if invoice.reference_doctype == "Sales Invoice" else "debit"
+		dr_or_cr = "credit" if invoice.reference_doctype in ["Sales Invoice", "Sales Order"] else "debit"
 		args_dict["account"] = invoice.account
 		args_dict[dr_or_cr] = invoice.allocated_amount
 		args_dict[dr_or_cr + "_in_account_currency"] = invoice.allocated_amount
@@ -2197,6 +2204,11 @@
 		else:
 			outstanding_amount = flt(total_amount) - flt(ref_doc.get("advance_paid"))
 
+		if reference_doctype in ["Sales Order", "Purchase Order"]:
+			party_type = "Customer" if reference_doctype == "Sales Order" else "Supplier"
+			party_field = "customer" if reference_doctype == "Sales Order" else "supplier"
+			party = ref_doc.get(party_field)
+			account = get_party_account(party_type, party, ref_doc.company)
 	else:
 		# Get the exchange rate based on the posting date of the ref doc.
 		exchange_rate = get_exchange_rate(party_account_currency, company_currency, ref_doc.posting_date)
diff --git a/erpnext/accounts/doctype/payment_entry/test_payment_entry.py b/erpnext/accounts/doctype/payment_entry/test_payment_entry.py
index 8a03dd7..5a014b8 100644
--- a/erpnext/accounts/doctype/payment_entry/test_payment_entry.py
+++ b/erpnext/accounts/doctype/payment_entry/test_payment_entry.py
@@ -1070,6 +1070,8 @@
 		self.assertRaises(frappe.ValidationError, pe_draft.submit)
 
 	def test_details_update_on_reference_table(self):
+		from erpnext.accounts.party import get_party_account
+
 		so = make_sales_order(
 			customer="_Test Customer USD", currency="USD", qty=1, rate=100, do_not_submit=True
 		)
@@ -1084,6 +1086,7 @@
 
 		ref_details = get_reference_details(so.doctype, so.name, pe.paid_from_account_currency)
 		expected_response = {
+			"account": get_party_account("Customer", so.customer, so.company),
 			"total_amount": 5000.0,
 			"outstanding_amount": 5000.0,
 			"exchange_rate": 1.0,
diff --git a/erpnext/accounts/utils.py b/erpnext/accounts/utils.py
index d4cb57b..0755f2e 100644
--- a/erpnext/accounts/utils.py
+++ b/erpnext/accounts/utils.py
@@ -490,7 +490,9 @@
 
 		# For payments with `Advance` in separate account feature enabled, only new ledger entries are posted for each reference.
 		# No need to cancel/delete payment ledger entries
-		if not (voucher_type == "Payment Entry" and doc.book_advance_payments_in_separate_party_account):
+		if voucher_type == "Payment Entry" and doc.book_advance_payments_in_separate_party_account:
+			doc.make_advance_gl_entries(cancel=1)
+		else:
 			_delete_pl_entries(voucher_type, voucher_no)
 
 		for entry in entries:
@@ -501,14 +503,16 @@
 
 			# update ref in advance entry
 			if voucher_type == "Journal Entry":
-				referenced_row = update_reference_in_journal_entry(entry, doc, do_not_save=False)
+				referenced_row, update_advance_paid = update_reference_in_journal_entry(
+					entry, doc, do_not_save=False
+				)
 				# advance section in sales/purchase invoice and reconciliation tool,both pass on exchange gain/loss
 				# amount and account in args
 				# referenced_row is used to deduplicate gain/loss journal
 				entry.update({"referenced_row": referenced_row})
 				doc.make_exchange_gain_loss_journal([entry], dimensions_dict)
 			else:
-				referenced_row = update_reference_in_payment_entry(
+				referenced_row, update_advance_paid = update_reference_in_payment_entry(
 					entry,
 					doc,
 					do_not_save=True,
@@ -522,7 +526,8 @@
 
 		if voucher_type == "Payment Entry" and doc.book_advance_payments_in_separate_party_account:
 			# both ledgers must be posted to for `Advance` in separate account feature
-			doc.make_advance_gl_entries(referenced_row, update_outstanding="No")
+			# TODO: find a more efficient way post only for the new linked vouchers
+			doc.make_advance_gl_entries()
 		else:
 			gl_map = doc.build_gl_map()
 			create_payment_ledger_entry(gl_map, update_outstanding="No", cancel=0, adv_adj=1)
@@ -532,6 +537,10 @@
 			update_voucher_outstanding(
 				entry.against_voucher_type, entry.against_voucher, entry.account, entry.party_type, entry.party
 			)
+		# update advance paid in Advance Receivable/Payable doctypes
+		if update_advance_paid:
+			for t, n in update_advance_paid:
+				frappe.get_doc(t, n).set_total_advance_paid()
 
 		frappe.flags.ignore_party_validation = False
 
@@ -621,11 +630,12 @@
 	jv_detail = journal_entry.get("accounts", {"name": d["voucher_detail_no"]})[0]
 
 	# Update Advance Paid in SO/PO since they might be getting unlinked
+	update_advance_paid = []
 	advance_payment_doctypes = frappe.get_hooks(
 		"advance_payment_receivable_doctypes"
 	) + frappe.get_hooks("advance_payment_payable_doctypes")
 	if jv_detail.get("reference_type") in advance_payment_doctypes:
-		frappe.get_doc(jv_detail.reference_type, jv_detail.reference_name).set_total_advance_paid()
+		update_advance_paid.append((jv_detail.reference_type, jv_detail.reference_name))
 
 	if flt(d["unadjusted_amount"]) - flt(d["allocated_amount"]) != 0:
 		# adjust the unreconciled balance
@@ -674,7 +684,7 @@
 	if not do_not_save:
 		journal_entry.save(ignore_permissions=True)
 
-	return new_row.name
+	return new_row.name, update_advance_paid
 
 
 def update_reference_in_payment_entry(
@@ -693,6 +703,7 @@
 		"account": d.account,
 		"dimensions": d.dimensions,
 	}
+	update_advance_paid = []
 
 	if d.voucher_detail_no:
 		existing_row = payment_entry.get("references", {"name": d["voucher_detail_no"]})[0]
@@ -702,9 +713,7 @@
 			"advance_payment_receivable_doctypes"
 		) + frappe.get_hooks("advance_payment_payable_doctypes")
 		if existing_row.get("reference_doctype") in advance_payment_doctypes:
-			frappe.get_doc(
-				existing_row.reference_doctype, existing_row.reference_name
-			).set_total_advance_paid()
+			update_advance_paid.append((existing_row.reference_doctype, existing_row.reference_name))
 
 		if d.allocated_amount <= existing_row.allocated_amount:
 			existing_row.allocated_amount -= d.allocated_amount
@@ -734,7 +743,7 @@
 
 	if not do_not_save:
 		payment_entry.save(ignore_permissions=True)
-	return row
+	return row, update_advance_paid
 
 
 def cancel_exchange_gain_loss_journal(
diff --git a/erpnext/buying/doctype/purchase_order/test_purchase_order.py b/erpnext/buying/doctype/purchase_order/test_purchase_order.py
index d262783..c667ee8 100644
--- a/erpnext/buying/doctype/purchase_order/test_purchase_order.py
+++ b/erpnext/buying/doctype/purchase_order/test_purchase_order.py
@@ -762,11 +762,94 @@
 		pe_doc = frappe.get_doc("Payment Entry", pe.name)
 		pe_doc.cancel()
 
+	def create_account(self, account_name, company, currency, parent):
+		if not frappe.db.get_value(
+			"Account", filters={"account_name": account_name, "company": company}
+		):
+			account = frappe.get_doc(
+				{
+					"doctype": "Account",
+					"account_name": account_name,
+					"parent_account": parent,
+					"company": company,
+					"account_currency": currency,
+					"is_group": 0,
+					"account_type": "Payable",
+				}
+			).insert()
+		else:
+			account = frappe.db.get_value(
+				"Account",
+				filters={"account_name": account_name, "company": company},
+				fieldname="name",
+				pluck=True,
+			)
+
+		return account
+
+	def test_advance_payment_with_separate_party_account_enabled(self):
+		"""
+		Test "Advance Paid" on Purchase Order, when "Book Advance Payments in Separate Party Account" is enabled and
+		the payment entry linked to the Order is allocated to Purchase Invoice.
+		"""
+		supplier = "_Test Supplier"
+		company = "_Test Company"
+
+		# Setup default 'Advance Paid' account
+		account = self.create_account(
+			"Advance Paid", company, "INR", "Application of Funds (Assets) - _TC"
+		)
+		company_doc = frappe.get_doc("Company", company)
+		company_doc.book_advance_payments_in_separate_party_account = True
+		company_doc.default_advance_paid_account = account.name
+		company_doc.save()
+
+		po_doc = create_purchase_order(supplier=supplier)
+
+		from erpnext.accounts.doctype.payment_entry.test_payment_entry import get_payment_entry
+
+		pe = get_payment_entry("Purchase Order", po_doc.name)
+		pe.save().submit()
+
+		po_doc.reload()
+		self.assertEqual(po_doc.advance_paid, 5000)
+
+		from erpnext.buying.doctype.purchase_order.purchase_order import make_purchase_invoice
+
+		pi = make_purchase_invoice(po_doc.name)
+		pi.append(
+			"advances",
+			{
+				"reference_type": pe.doctype,
+				"reference_name": pe.name,
+				"reference_row": pe.references[0].name,
+				"advance_amount": 5000,
+				"allocated_amount": 5000,
+			},
+		)
+		pi.save().submit()
+		pe.reload()
+		po_doc.reload()
+		self.assertEqual(po_doc.advance_paid, 0)
+
+		company_doc.book_advance_payments_in_separate_party_account = False
+		company_doc.save()
+
 	@change_settings("Accounts Settings", {"unlink_advance_payment_on_cancelation_of_order": 1})
 	def test_advance_paid_upon_payment_entry_cancellation(self):
 		from erpnext.accounts.doctype.payment_entry.test_payment_entry import get_payment_entry
 
-		po_doc = create_purchase_order(supplier="_Test Supplier USD", currency="USD", do_not_submit=1)
+		supplier = "_Test Supplier USD"
+		company = "_Test Company"
+
+		# Setup default USD payable account for Supplier
+		account = self.create_account("Creditors USD", company, "USD", "Accounts Payable - _TC")
+		supplier_doc = frappe.get_doc("Supplier", supplier)
+		if not [x for x in supplier_doc.accounts if x.company == company]:
+			supplier_doc.append("accounts", {"company": company, "account": account.name})
+			supplier_doc.save()
+
+		po_doc = create_purchase_order(supplier=supplier, currency="USD", do_not_submit=1)
 		po_doc.conversion_rate = 80
 		po_doc.submit()
 
diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py
index a3db196..c543dfc 100644
--- a/erpnext/controllers/accounts_controller.py
+++ b/erpnext/controllers/accounts_controller.py
@@ -1864,7 +1864,7 @@
 				(ple.against_voucher_type == self.doctype)
 				& (ple.against_voucher_no == self.name)
 				& (ple.party == party)
-				& (ple.docstatus == 1)
+				& (ple.delinked == 0)
 				& (ple.company == self.company)
 			)
 			.run(as_dict=True)
@@ -1880,7 +1880,10 @@
 				advance_paid, precision=self.precision("advance_paid"), currency=advance.account_currency
 			)
 
-			frappe.db.set_value(self.doctype, self.name, "party_account_currency", advance.account_currency)
+			if advance.account_currency:
+				frappe.db.set_value(
+					self.doctype, self.name, "party_account_currency", advance.account_currency
+				)
 
 			if advance.account_currency == self.currency:
 				order_total = self.get("rounded_total") or self.grand_total