diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py index 77efe78368b..7970a3e0678 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.py +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py @@ -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) diff --git a/erpnext/accounts/doctype/payment_entry/test_payment_entry.py b/erpnext/accounts/doctype/payment_entry/test_payment_entry.py index 8a03dd7278a..5a014b85c99 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 @@ 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, diff --git a/erpnext/accounts/utils.py b/erpnext/accounts/utils.py index d4cb57b2143..0755f2e9e84 100644 --- a/erpnext/accounts/utils.py +++ b/erpnext/accounts/utils.py @@ -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( diff --git a/erpnext/buying/doctype/purchase_order/test_purchase_order.py b/erpnext/buying/doctype/purchase_order/test_purchase_order.py index d262783ae9a..c667ee821bd 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 @@ 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() diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index a3db19672d8..c543dfc2cd0 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -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