Merge pull request #39681 from ruthra-kumar/fix_gl_logic_in_advance_as_liability

fix: incorrect advance paid in Sales/Purchase Order
This commit is contained in:
ruthra kumar
2024-03-10 16:38:43 +05:30
committed by GitHub
5 changed files with 128 additions and 18 deletions

View File

@@ -1271,7 +1271,13 @@ class PaymentEntry(AccountsController):
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 @@ class PaymentEntry(AccountsController):
"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 @@ def get_reference_details(reference_doctype, reference_name, party_account_curre
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)

View File

@@ -1070,6 +1070,8 @@ class TestPaymentEntry(FrappeTestCase):
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 @@ class TestPaymentEntry(FrappeTestCase):
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,

View File

@@ -490,7 +490,9 @@ def reconcile_against_document(
# 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 @@ def reconcile_against_document(
# 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 @@ def reconcile_against_document(
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 @@ def reconcile_against_document(
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 @@ def update_reference_in_journal_entry(d, journal_entry, do_not_save=False):
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 @@ def update_reference_in_journal_entry(d, journal_entry, do_not_save=False):
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 @@ def update_reference_in_payment_entry(
"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 @@ def update_reference_in_payment_entry(
"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 @@ def update_reference_in_payment_entry(
if not do_not_save:
payment_entry.save(ignore_permissions=True)
return row
return row, update_advance_paid
def cancel_exchange_gain_loss_journal(

View File

@@ -762,11 +762,94 @@ class TestPurchaseOrder(FrappeTestCase):
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()

View File

@@ -1864,7 +1864,7 @@ class AccountsController(TransactionBase):
(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 @@ class AccountsController(TransactionBase):
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