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:
@@ -1271,7 +1271,13 @@ class PaymentEntry(AccountsController):
|
|||||||
references = [x for x in self.get("references") if x.name == entry.name]
|
references = [x for x in self.get("references") if x.name == entry.name]
|
||||||
|
|
||||||
for ref in references:
|
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)
|
self.add_advance_gl_for_reference(gl_entries, ref)
|
||||||
|
|
||||||
def add_advance_gl_for_reference(self, gl_entries, invoice):
|
def add_advance_gl_for_reference(self, gl_entries, invoice):
|
||||||
@@ -1285,14 +1291,15 @@ class PaymentEntry(AccountsController):
|
|||||||
"voucher_detail_no": invoice.name,
|
"voucher_detail_no": invoice.name,
|
||||||
}
|
}
|
||||||
|
|
||||||
posting_date = frappe.db.get_value(
|
date_field = "posting_date"
|
||||||
invoice.reference_doctype, invoice.reference_name, "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):
|
if getdate(posting_date) < getdate(self.posting_date):
|
||||||
posting_date = 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["account"] = invoice.account
|
||||||
args_dict[dr_or_cr] = invoice.allocated_amount
|
args_dict[dr_or_cr] = invoice.allocated_amount
|
||||||
args_dict[dr_or_cr + "_in_account_currency"] = 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:
|
else:
|
||||||
outstanding_amount = flt(total_amount) - flt(ref_doc.get("advance_paid"))
|
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:
|
else:
|
||||||
# Get the exchange rate based on the posting date of the ref doc.
|
# 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)
|
exchange_rate = get_exchange_rate(party_account_currency, company_currency, ref_doc.posting_date)
|
||||||
|
|||||||
@@ -1070,6 +1070,8 @@ class TestPaymentEntry(FrappeTestCase):
|
|||||||
self.assertRaises(frappe.ValidationError, pe_draft.submit)
|
self.assertRaises(frappe.ValidationError, pe_draft.submit)
|
||||||
|
|
||||||
def test_details_update_on_reference_table(self):
|
def test_details_update_on_reference_table(self):
|
||||||
|
from erpnext.accounts.party import get_party_account
|
||||||
|
|
||||||
so = make_sales_order(
|
so = make_sales_order(
|
||||||
customer="_Test Customer USD", currency="USD", qty=1, rate=100, do_not_submit=True
|
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)
|
ref_details = get_reference_details(so.doctype, so.name, pe.paid_from_account_currency)
|
||||||
expected_response = {
|
expected_response = {
|
||||||
|
"account": get_party_account("Customer", so.customer, so.company),
|
||||||
"total_amount": 5000.0,
|
"total_amount": 5000.0,
|
||||||
"outstanding_amount": 5000.0,
|
"outstanding_amount": 5000.0,
|
||||||
"exchange_rate": 1.0,
|
"exchange_rate": 1.0,
|
||||||
|
|||||||
@@ -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.
|
# 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
|
# 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)
|
_delete_pl_entries(voucher_type, voucher_no)
|
||||||
|
|
||||||
for entry in entries:
|
for entry in entries:
|
||||||
@@ -501,14 +503,16 @@ def reconcile_against_document(
|
|||||||
|
|
||||||
# update ref in advance entry
|
# update ref in advance entry
|
||||||
if voucher_type == "Journal 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
|
# advance section in sales/purchase invoice and reconciliation tool,both pass on exchange gain/loss
|
||||||
# amount and account in args
|
# amount and account in args
|
||||||
# referenced_row is used to deduplicate gain/loss journal
|
# referenced_row is used to deduplicate gain/loss journal
|
||||||
entry.update({"referenced_row": referenced_row})
|
entry.update({"referenced_row": referenced_row})
|
||||||
doc.make_exchange_gain_loss_journal([entry], dimensions_dict)
|
doc.make_exchange_gain_loss_journal([entry], dimensions_dict)
|
||||||
else:
|
else:
|
||||||
referenced_row = update_reference_in_payment_entry(
|
referenced_row, update_advance_paid = update_reference_in_payment_entry(
|
||||||
entry,
|
entry,
|
||||||
doc,
|
doc,
|
||||||
do_not_save=True,
|
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:
|
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
|
# 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:
|
else:
|
||||||
gl_map = doc.build_gl_map()
|
gl_map = doc.build_gl_map()
|
||||||
create_payment_ledger_entry(gl_map, update_outstanding="No", cancel=0, adv_adj=1)
|
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(
|
update_voucher_outstanding(
|
||||||
entry.against_voucher_type, entry.against_voucher, entry.account, entry.party_type, entry.party
|
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
|
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]
|
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 in SO/PO since they might be getting unlinked
|
||||||
|
update_advance_paid = []
|
||||||
advance_payment_doctypes = frappe.get_hooks(
|
advance_payment_doctypes = frappe.get_hooks(
|
||||||
"advance_payment_receivable_doctypes"
|
"advance_payment_receivable_doctypes"
|
||||||
) + frappe.get_hooks("advance_payment_payable_doctypes")
|
) + frappe.get_hooks("advance_payment_payable_doctypes")
|
||||||
if jv_detail.get("reference_type") in advance_payment_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:
|
if flt(d["unadjusted_amount"]) - flt(d["allocated_amount"]) != 0:
|
||||||
# adjust the unreconciled balance
|
# 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:
|
if not do_not_save:
|
||||||
journal_entry.save(ignore_permissions=True)
|
journal_entry.save(ignore_permissions=True)
|
||||||
|
|
||||||
return new_row.name
|
return new_row.name, update_advance_paid
|
||||||
|
|
||||||
|
|
||||||
def update_reference_in_payment_entry(
|
def update_reference_in_payment_entry(
|
||||||
@@ -693,6 +703,7 @@ def update_reference_in_payment_entry(
|
|||||||
"account": d.account,
|
"account": d.account,
|
||||||
"dimensions": d.dimensions,
|
"dimensions": d.dimensions,
|
||||||
}
|
}
|
||||||
|
update_advance_paid = []
|
||||||
|
|
||||||
if d.voucher_detail_no:
|
if d.voucher_detail_no:
|
||||||
existing_row = payment_entry.get("references", {"name": d["voucher_detail_no"]})[0]
|
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"
|
"advance_payment_receivable_doctypes"
|
||||||
) + frappe.get_hooks("advance_payment_payable_doctypes")
|
) + frappe.get_hooks("advance_payment_payable_doctypes")
|
||||||
if existing_row.get("reference_doctype") in advance_payment_doctypes:
|
if existing_row.get("reference_doctype") in advance_payment_doctypes:
|
||||||
frappe.get_doc(
|
update_advance_paid.append((existing_row.reference_doctype, existing_row.reference_name))
|
||||||
existing_row.reference_doctype, existing_row.reference_name
|
|
||||||
).set_total_advance_paid()
|
|
||||||
|
|
||||||
if d.allocated_amount <= existing_row.allocated_amount:
|
if d.allocated_amount <= existing_row.allocated_amount:
|
||||||
existing_row.allocated_amount -= d.allocated_amount
|
existing_row.allocated_amount -= d.allocated_amount
|
||||||
@@ -734,7 +743,7 @@ def update_reference_in_payment_entry(
|
|||||||
|
|
||||||
if not do_not_save:
|
if not do_not_save:
|
||||||
payment_entry.save(ignore_permissions=True)
|
payment_entry.save(ignore_permissions=True)
|
||||||
return row
|
return row, update_advance_paid
|
||||||
|
|
||||||
|
|
||||||
def cancel_exchange_gain_loss_journal(
|
def cancel_exchange_gain_loss_journal(
|
||||||
|
|||||||
@@ -762,11 +762,94 @@ class TestPurchaseOrder(FrappeTestCase):
|
|||||||
pe_doc = frappe.get_doc("Payment Entry", pe.name)
|
pe_doc = frappe.get_doc("Payment Entry", pe.name)
|
||||||
pe_doc.cancel()
|
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})
|
@change_settings("Accounts Settings", {"unlink_advance_payment_on_cancelation_of_order": 1})
|
||||||
def test_advance_paid_upon_payment_entry_cancellation(self):
|
def test_advance_paid_upon_payment_entry_cancellation(self):
|
||||||
from erpnext.accounts.doctype.payment_entry.test_payment_entry import get_payment_entry
|
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.conversion_rate = 80
|
||||||
po_doc.submit()
|
po_doc.submit()
|
||||||
|
|
||||||
|
|||||||
@@ -1864,7 +1864,7 @@ class AccountsController(TransactionBase):
|
|||||||
(ple.against_voucher_type == self.doctype)
|
(ple.against_voucher_type == self.doctype)
|
||||||
& (ple.against_voucher_no == self.name)
|
& (ple.against_voucher_no == self.name)
|
||||||
& (ple.party == party)
|
& (ple.party == party)
|
||||||
& (ple.docstatus == 1)
|
& (ple.delinked == 0)
|
||||||
& (ple.company == self.company)
|
& (ple.company == self.company)
|
||||||
)
|
)
|
||||||
.run(as_dict=True)
|
.run(as_dict=True)
|
||||||
@@ -1880,7 +1880,10 @@ class AccountsController(TransactionBase):
|
|||||||
advance_paid, precision=self.precision("advance_paid"), currency=advance.account_currency
|
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:
|
if advance.account_currency == self.currency:
|
||||||
order_total = self.get("rounded_total") or self.grand_total
|
order_total = self.get("rounded_total") or self.grand_total
|
||||||
|
|||||||
Reference in New Issue
Block a user