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