From b343334f694b79a92faabbba03984dca086ca8c0 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Wed, 16 Oct 2024 17:01:47 +0530 Subject: [PATCH 01/25] feat: advance payment ledger doctype (cherry picked from commit 2d6efd7cc83e97186e07457567b1439b14b1fb57) --- .../advance_payment_ledger_entry/__init__.py | 0 .../advance_payment_ledger_entry.js | 8 ++ .../advance_payment_ledger_entry.json | 89 +++++++++++++++++++ .../advance_payment_ledger_entry.py | 27 ++++++ .../test_advance_payment_ledger_entry.py | 29 ++++++ 5 files changed, 153 insertions(+) create mode 100644 erpnext/accounts/doctype/advance_payment_ledger_entry/__init__.py create mode 100644 erpnext/accounts/doctype/advance_payment_ledger_entry/advance_payment_ledger_entry.js create mode 100644 erpnext/accounts/doctype/advance_payment_ledger_entry/advance_payment_ledger_entry.json create mode 100644 erpnext/accounts/doctype/advance_payment_ledger_entry/advance_payment_ledger_entry.py create mode 100644 erpnext/accounts/doctype/advance_payment_ledger_entry/test_advance_payment_ledger_entry.py diff --git a/erpnext/accounts/doctype/advance_payment_ledger_entry/__init__.py b/erpnext/accounts/doctype/advance_payment_ledger_entry/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/erpnext/accounts/doctype/advance_payment_ledger_entry/advance_payment_ledger_entry.js b/erpnext/accounts/doctype/advance_payment_ledger_entry/advance_payment_ledger_entry.js new file mode 100644 index 00000000000..1a0dc1e7272 --- /dev/null +++ b/erpnext/accounts/doctype/advance_payment_ledger_entry/advance_payment_ledger_entry.js @@ -0,0 +1,8 @@ +// Copyright (c) 2024, Frappe Technologies Pvt. Ltd. and contributors +// For license information, please see license.txt + +// frappe.ui.form.on("Advance Payment Ledger Entry", { +// refresh(frm) { + +// }, +// }); diff --git a/erpnext/accounts/doctype/advance_payment_ledger_entry/advance_payment_ledger_entry.json b/erpnext/accounts/doctype/advance_payment_ledger_entry/advance_payment_ledger_entry.json new file mode 100644 index 00000000000..6f4792543d8 --- /dev/null +++ b/erpnext/accounts/doctype/advance_payment_ledger_entry/advance_payment_ledger_entry.json @@ -0,0 +1,89 @@ +{ + "actions": [], + "allow_rename": 1, + "creation": "2024-10-16 16:57:12.085072", + "doctype": "DocType", + "engine": "InnoDB", + "field_order": [ + "company", + "voucher_type", + "voucher_no", + "against_voucher_type", + "against_voucher_no", + "amount", + "currency", + "event" + ], + "fields": [ + { + "fieldname": "voucher_type", + "fieldtype": "Link", + "label": "Voucher Type", + "options": "DocType" + }, + { + "fieldname": "voucher_no", + "fieldtype": "Dynamic Link", + "label": "Voucher No", + "options": "voucher_type" + }, + { + "fieldname": "against_voucher_type", + "fieldtype": "Link", + "label": "Against Voucher Type", + "options": "DocType" + }, + { + "fieldname": "against_voucher_no", + "fieldtype": "Dynamic Link", + "label": "Against Voucher No", + "options": "against_voucher_type" + }, + { + "fieldname": "amount", + "fieldtype": "Currency", + "label": "Amount" + }, + { + "fieldname": "currency", + "fieldtype": "Link", + "label": "Currency", + "options": "Currency" + }, + { + "fieldname": "event", + "fieldtype": "Data", + "label": "Event" + }, + { + "fieldname": "company", + "fieldtype": "Link", + "label": "Company", + "options": "Company" + } + ], + "index_web_pages_for_search": 1, + "links": [], + "modified": "2024-10-16 17:08:09.334330", + "modified_by": "Administrator", + "module": "Accounts", + "name": "Advance Payment Ledger Entry", + "owner": "Administrator", + "permissions": [ + { + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "System Manager", + "share": 1, + "write": 1 + } + ], + "sort_field": "creation", + "sort_order": "DESC", + "states": [] +} \ No newline at end of file diff --git a/erpnext/accounts/doctype/advance_payment_ledger_entry/advance_payment_ledger_entry.py b/erpnext/accounts/doctype/advance_payment_ledger_entry/advance_payment_ledger_entry.py new file mode 100644 index 00000000000..0ec2d411761 --- /dev/null +++ b/erpnext/accounts/doctype/advance_payment_ledger_entry/advance_payment_ledger_entry.py @@ -0,0 +1,27 @@ +# Copyright (c) 2024, Frappe Technologies Pvt. Ltd. and contributors +# For license information, please see license.txt + +# import frappe +from frappe.model.document import Document + + +class AdvancePaymentLedgerEntry(Document): + # begin: auto-generated types + # This code is auto-generated. Do not modify anything in this block. + + from typing import TYPE_CHECKING + + if TYPE_CHECKING: + from frappe.types import DF + + against_voucher_no: DF.DynamicLink | None + against_voucher_type: DF.Link | None + amount: DF.Currency + company: DF.Link | None + currency: DF.Link | None + event: DF.Data | None + voucher_no: DF.DynamicLink | None + voucher_type: DF.Link | None + # end: auto-generated types + + pass diff --git a/erpnext/accounts/doctype/advance_payment_ledger_entry/test_advance_payment_ledger_entry.py b/erpnext/accounts/doctype/advance_payment_ledger_entry/test_advance_payment_ledger_entry.py new file mode 100644 index 00000000000..ef938062399 --- /dev/null +++ b/erpnext/accounts/doctype/advance_payment_ledger_entry/test_advance_payment_ledger_entry.py @@ -0,0 +1,29 @@ +# Copyright (c) 2024, Frappe Technologies Pvt. Ltd. and Contributors +# See license.txt + +# import frappe +from frappe.tests import IntegrationTestCase, UnitTestCase + +# On IntegrationTestCase, the doctype test records and all +# link-field test record depdendencies are recursively loaded +# Use these module variables to add/remove to/from that list +EXTRA_TEST_RECORD_DEPENDENCIES = [] # eg. ["User"] +IGNORE_TEST_RECORD_DEPENDENCIES = [] # eg. ["User"] + + +class TestAdvancePaymentLedgerEntry(UnitTestCase): + """ + Unit tests for AdvancePaymentLedgerEntry. + Use this class for testing individual functions and methods. + """ + + pass + + +class TestAdvancePaymentLedgerEntry(IntegrationTestCase): + """ + Integration tests for AdvancePaymentLedgerEntry. + Use this class for testing interactions between multiple components. + """ + + pass From 0d02f8b5d127b6f2dfcf8bf36a2bd6d1bb19aa0d Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Wed, 16 Oct 2024 17:11:50 +0530 Subject: [PATCH 02/25] refactor: make all fields readonly (cherry picked from commit f176a82198b894bfc289fcf8e506ce0d2c918573) --- .../advance_payment_ledger_entry.json | 26 ++++++++++++------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/erpnext/accounts/doctype/advance_payment_ledger_entry/advance_payment_ledger_entry.json b/erpnext/accounts/doctype/advance_payment_ledger_entry/advance_payment_ledger_entry.json index 6f4792543d8..1d0a5b42a31 100644 --- a/erpnext/accounts/doctype/advance_payment_ledger_entry/advance_payment_ledger_entry.json +++ b/erpnext/accounts/doctype/advance_payment_ledger_entry/advance_payment_ledger_entry.json @@ -19,52 +19,60 @@ "fieldname": "voucher_type", "fieldtype": "Link", "label": "Voucher Type", - "options": "DocType" + "options": "DocType", + "read_only": 1 }, { "fieldname": "voucher_no", "fieldtype": "Dynamic Link", "label": "Voucher No", - "options": "voucher_type" + "options": "voucher_type", + "read_only": 1 }, { "fieldname": "against_voucher_type", "fieldtype": "Link", "label": "Against Voucher Type", - "options": "DocType" + "options": "DocType", + "read_only": 1 }, { "fieldname": "against_voucher_no", "fieldtype": "Dynamic Link", "label": "Against Voucher No", - "options": "against_voucher_type" + "options": "against_voucher_type", + "read_only": 1 }, { "fieldname": "amount", "fieldtype": "Currency", - "label": "Amount" + "label": "Amount", + "read_only": 1 }, { "fieldname": "currency", "fieldtype": "Link", "label": "Currency", - "options": "Currency" + "options": "Currency", + "read_only": 1 }, { "fieldname": "event", "fieldtype": "Data", - "label": "Event" + "label": "Event", + "read_only": 1 }, { "fieldname": "company", "fieldtype": "Link", "label": "Company", - "options": "Company" + "options": "Company", + "read_only": 1 } ], "index_web_pages_for_search": 1, "links": [], - "modified": "2024-10-16 17:08:09.334330", + "modified": "2024-10-16 17:11:28.143979", "modified_by": "Administrator", "module": "Accounts", "name": "Advance Payment Ledger Entry", From bf0b74bcbdb396ac5d1438034b4beb11f796cf78 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Wed, 16 Oct 2024 17:16:31 +0530 Subject: [PATCH 03/25] refactor: create advance ledger entries on submit and cancel (cherry picked from commit 575ca5b90048ea96bbb59c4e0d968349a29211aa) --- .../doctype/payment_entry/payment_entry.py | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py index b645d92cb64..28ad4454596 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.py +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py @@ -110,6 +110,7 @@ class PaymentEntry(AccountsController): self.update_outstanding_amounts() self.update_payment_schedule() self.update_payment_requests() + self.make_advance_payment_ledger_entries() self.update_advance_paid() # advance_paid_status depends on the payment request amount self.set_status() @@ -197,6 +198,7 @@ class PaymentEntry(AccountsController): self.delink_advance_entry_references() self.update_payment_schedule(cancel=1) self.update_payment_requests(cancel=True) + self.make_advance_payment_ledger_entries() self.update_advance_paid() # advance_paid_status depends on the payment request amount self.set_status() @@ -1849,6 +1851,26 @@ class PaymentEntry(AccountsController): allocated_negative_outstanding, ) + def make_advance_payment_ledger_entries(self): + if self.docstatus == 1 or self.docstatus == 2: + advance_payment_doctypes = frappe.get_hooks( + "advance_payment_receivable_doctypes" + ) + frappe.get_hooks("advance_payment_payable_doctypes") + + advance_doctype_references = [ + x for x in self.references if x.reference_doctype in advance_payment_doctypes + ] + for x in advance_doctype_references: + doc = frappe.new_doc("Advance Payment Ledger Entry") + doc.company = self.company + doc.voucher_type = self.doctype + doc.voucher_no = self.name + doc.against_voucher_type = x.reference_doctype + doc.against_voucher_no = x.reference_name + doc.amount = x.allocated_amount if self.docstatus == 1 else -1 * x.allocated_amount + doc.event = "Submit" if self.docstatus == 1 else "Cancel" + doc.save() + @frappe.whitelist() def set_matched_payment_requests(self, matched_payment_requests): """ From 54f758c327d4e852ae10fc20570ddc59671efeae Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Wed, 16 Oct 2024 17:21:56 +0530 Subject: [PATCH 04/25] refactor: calculate advance from advance ledger (cherry picked from commit 2b2360bf7bedb771ca90d0a0a99f90eae693e179) # Conflicts: # erpnext/controllers/accounts_controller.py --- erpnext/controllers/accounts_controller.py | 25 ++++++++++++++++------ 1 file changed, 18 insertions(+), 7 deletions(-) diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index e6fb5d0d153..22d59f428f2 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -1935,22 +1935,33 @@ class AccountsController(TransactionBase): return stock_items +<<<<<<< HEAD def set_total_advance_paid(self): ple = frappe.qb.DocType("Payment Ledger Entry") party = self.customer if self.doctype == "Sales Order" else self.supplier +======= + def calculate_total_advance_from_ledger(self): + adv = frappe.qb.DocType("Advance Payment Ledger Entry") +>>>>>>> 2b2360bf7b (refactor: calculate advance from advance ledger) advance = ( - frappe.qb.from_(ple) - .select(ple.account_currency, Abs(Sum(ple.amount_in_account_currency)).as_("amount")) + frappe.qb.from_(adv) + .select(adv.currency, Abs(Sum(adv.amount)).as_("amount")) .where( - (ple.against_voucher_type == self.doctype) - & (ple.against_voucher_no == self.name) - & (ple.party == party) - & (ple.delinked == 0) - & (ple.company == self.company) + (adv.against_voucher_type == self.doctype) + & (adv.against_voucher_no == self.name) + & (adv.company == self.company) ) .run(as_dict=True) ) + return advance +<<<<<<< HEAD +======= + def set_total_advance_paid(self): + advance = self.calculate_total_advance_from_ledger() + advance_paid, order_total = None, None + +>>>>>>> 2b2360bf7b (refactor: calculate advance from advance ledger) if advance: advance = advance[0] From a6c26874c784b7ee5abea4916ee99f7344f15ab5 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Wed, 16 Oct 2024 17:26:20 +0530 Subject: [PATCH 05/25] refactor: remove advance payment ledgers on document deletion (cherry picked from commit 3c53b92f0561907ed71a41d82403ca77a088d3ed) --- erpnext/accounts/doctype/payment_entry/payment_entry.py | 1 + erpnext/controllers/accounts_controller.py | 5 +++++ 2 files changed, 6 insertions(+) diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py index 28ad4454596..abd6ea37e6d 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.py +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py @@ -191,6 +191,7 @@ class PaymentEntry(AccountsController): "Repost Accounting Ledger Items", "Unreconcile Payment", "Unreconcile Payment Entries", + "Advance Payment Ledger Entry", ) super().on_cancel() self.make_gl_entries(cancel=1) diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index 22d59f428f2..ffe28119041 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -345,9 +345,14 @@ class AccountsController(TransactionBase): repost_doc.flags.ignore_links = True repost_doc.save(ignore_permissions=True) + def _remove_advance_payment_ledger_entries(self): + adv = qb.DocType("Advance Payment Ledger Entry") + qb.from_(adv).delete().where(adv.voucher_type.eq(self.doctype) & adv.voucher_no.eq(self.name)).run() + def on_trash(self): from erpnext.accounts.utils import delete_exchange_gain_loss_journal + self._remove_advance_payment_ledger_entries() self._remove_references_in_repost_doctypes() self._remove_references_in_unreconcile() self.remove_serial_and_batch_bundle() From df25d33735ad79dca49c17a0daae8b0cc6fe39e7 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Thu, 17 Oct 2024 13:49:46 +0530 Subject: [PATCH 06/25] chore: remove duplicate test class (cherry picked from commit e2891a60d5a48205c544585032a0dae78975ac45) --- .../test_advance_payment_ledger_entry.py | 9 --------- 1 file changed, 9 deletions(-) diff --git a/erpnext/accounts/doctype/advance_payment_ledger_entry/test_advance_payment_ledger_entry.py b/erpnext/accounts/doctype/advance_payment_ledger_entry/test_advance_payment_ledger_entry.py index ef938062399..750a658102d 100644 --- a/erpnext/accounts/doctype/advance_payment_ledger_entry/test_advance_payment_ledger_entry.py +++ b/erpnext/accounts/doctype/advance_payment_ledger_entry/test_advance_payment_ledger_entry.py @@ -11,15 +11,6 @@ EXTRA_TEST_RECORD_DEPENDENCIES = [] # eg. ["User"] IGNORE_TEST_RECORD_DEPENDENCIES = [] # eg. ["User"] -class TestAdvancePaymentLedgerEntry(UnitTestCase): - """ - Unit tests for AdvancePaymentLedgerEntry. - Use this class for testing individual functions and methods. - """ - - pass - - class TestAdvancePaymentLedgerEntry(IntegrationTestCase): """ Integration tests for AdvancePaymentLedgerEntry. From a12df122a96d0e0313a475bec2b503707767b106 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Thu, 17 Oct 2024 13:56:24 +0530 Subject: [PATCH 07/25] refactor(test): advance_paid stays after reconciliation (cherry picked from commit c4197c3f31336ebca8ec2b81ee882ff2a43baccf) --- erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py index 7ac0d34e671..d05da0dbf19 100644 --- a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py @@ -1995,7 +1995,7 @@ class TestSalesInvoice(FrappeTestCase): # Check if SO is unlinked/replaced by SI in PE & if SO advance paid is 0 self.assertEqual(pe.references[0].reference_name, si.name) - self.assertEqual(sales_order.advance_paid, 0.0) + self.assertEqual(sales_order.advance_paid, 300.0) # check outstanding after advance allocation self.assertEqual( From d84a3c4f29df46156d94cef8ad195eaf59424e06 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Thu, 17 Oct 2024 14:31:18 +0530 Subject: [PATCH 08/25] fix: deleting SO/PO will remove its advance payment ledger entry (cherry picked from commit 14357bccba2c5a6e21091fc99239b91dcc7b647f) --- erpnext/controllers/accounts_controller.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index ffe28119041..23498ad6883 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -349,6 +349,14 @@ class AccountsController(TransactionBase): adv = qb.DocType("Advance Payment Ledger Entry") qb.from_(adv).delete().where(adv.voucher_type.eq(self.doctype) & adv.voucher_no.eq(self.name)).run() + advance_payment_doctypes = frappe.get_hooks("advance_payment_receivable_doctypes") + frappe.get_hooks( + "advance_payment_payable_doctypes" + ) + if self.doctype in advance_payment_doctypes: + qb.from_(adv).delete().where( + adv.against_voucher_type.eq(self.doctype) & adv.against_voucher_no.eq(self.name) + ).run() + def on_trash(self): from erpnext.accounts.utils import delete_exchange_gain_loss_journal From ffd426d43db54349cd4c969c66fee84e5ad54301 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Thu, 17 Oct 2024 16:51:16 +0530 Subject: [PATCH 09/25] refactor: link journal entry to advance payment ledger (cherry picked from commit fca5e952484af634f0f1268258dd0bf8c56bb73a) --- .../doctype/journal_entry/journal_entry.py | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/erpnext/accounts/doctype/journal_entry/journal_entry.py b/erpnext/accounts/doctype/journal_entry/journal_entry.py index 47626492e84..d11cc27ccb7 100644 --- a/erpnext/accounts/doctype/journal_entry/journal_entry.py +++ b/erpnext/accounts/doctype/journal_entry/journal_entry.py @@ -188,6 +188,7 @@ class JournalEntry(AccountsController): self.validate_cheque_info() self.check_credit_limit() self.make_gl_entries() + self.make_advance_payment_ledger_entries() self.update_advance_paid() self.update_asset_value() self.update_inter_company_jv() @@ -220,6 +221,7 @@ class JournalEntry(AccountsController): "Unreconcile Payment Entries", ) self.make_gl_entries(1) + self.make_advance_payment_ledger_entries() self.update_advance_paid() self.unlink_advance_entry_reference() self.unlink_asset_reference() @@ -231,6 +233,32 @@ class JournalEntry(AccountsController): def get_title(self): return self.pay_to_recd_from or self.accounts[0].account + def make_advance_payment_ledger_entries(self): + if self.docstatus == 1 or self.docstatus == 2: + advance_payment_doctypes = frappe.get_hooks( + "advance_payment_receivable_doctypes" + ) + frappe.get_hooks("advance_payment_payable_doctypes") + + advance_doctype_references = [ + x for x in self.accounts if x.reference_type in advance_payment_doctypes + ] + + for x in advance_doctype_references: + # Looking for payments + dr_or_cr = "credit" if x.account_type == "Receivable" else "debit" + + amount = x.get(dr_or_cr) + if amount > 0: + doc = frappe.new_doc("Advance Payment Ledger Entry") + doc.company = self.company + doc.voucher_type = self.doctype + doc.voucher_no = self.name + doc.against_voucher_type = x.reference_type + doc.against_voucher_no = x.reference_name + doc.amount = amount if self.docstatus == 1 else -1 * amount + doc.event = "Submit" if self.docstatus == 1 else "Cancel" + doc.save() + def update_advance_paid(self): advance_paid = frappe._dict() for d in self.get("accounts"): From cb36dcb3823a3d50907e3084819db33271a52345 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Fri, 18 Oct 2024 13:32:28 +0530 Subject: [PATCH 10/25] refactor(test): reconciliation shouldn't affect advance paid (cherry picked from commit 35a8a187283efe4a9ce325017278d1e3b0bbdfbc) --- .../unreconcile_payment/test_unreconcile_payment.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/erpnext/accounts/doctype/unreconcile_payment/test_unreconcile_payment.py b/erpnext/accounts/doctype/unreconcile_payment/test_unreconcile_payment.py index 13e5294aa78..3d222b22ff8 100644 --- a/erpnext/accounts/doctype/unreconcile_payment/test_unreconcile_payment.py +++ b/erpnext/accounts/doctype/unreconcile_payment/test_unreconcile_payment.py @@ -362,10 +362,14 @@ class TestUnreconcilePayment(AccountsTestMixin, FrappeTestCase): # Assert 'Advance Paid' so.reload() pe.reload() - self.assertEqual(so.advance_paid, 0) + self.assertEqual(so.advance_paid, 100) self.assertEqual(len(pe.references), 0) self.assertEqual(pe.unallocated_amount, 100) + pe.cancel() + so.reload() + self.assertEqual(so.advance_paid, 100) + def test_06_unreconcile_advance_from_payment_entry(self): self.enable_advance_as_liability() so1 = self.create_sales_order() @@ -411,7 +415,7 @@ class TestUnreconcilePayment(AccountsTestMixin, FrappeTestCase): so2.reload() pe.reload() self.assertEqual(so1.advance_paid, 150) - self.assertEqual(so2.advance_paid, 0) + self.assertEqual(so2.advance_paid, 110) self.assertEqual(len(pe.references), 1) self.assertEqual(pe.unallocated_amount, 110) @@ -459,6 +463,6 @@ class TestUnreconcilePayment(AccountsTestMixin, FrappeTestCase): # Assert 'Advance Paid' so.reload() - self.assertEqual(so.advance_paid, 0) + self.assertEqual(so.advance_paid, 1000) self.disable_advance_as_liability() From 085e0455d80a853dddb01fe34123f613b51b1693 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Fri, 18 Oct 2024 13:48:15 +0530 Subject: [PATCH 11/25] refactor: patch to migrating old SO / PO to advance ledger (cherry picked from commit b927f2f4a0744db8680dcd9d664ec4c9ab9453f0) --- .../create_advance_payment_ledger_records.py | 71 +++++++++++++++++++ 1 file changed, 71 insertions(+) create mode 100644 erpnext/patches/v15_0/create_advance_payment_ledger_records.py diff --git a/erpnext/patches/v15_0/create_advance_payment_ledger_records.py b/erpnext/patches/v15_0/create_advance_payment_ledger_records.py new file mode 100644 index 00000000000..64d8e3c0f3c --- /dev/null +++ b/erpnext/patches/v15_0/create_advance_payment_ledger_records.py @@ -0,0 +1,71 @@ +import frappe +from frappe import qb +from frappe.query_builder.custom import ConstantColumn + + +def get_advance_doctypes() -> list: + return frappe.get_hooks("advance_payment_receivable_doctypes") + frappe.get_hooks( + "advance_payment_payable_doctypes" + ) + + +def get_payments_with_so_po_reference() -> list: + advance_doctypes = get_advance_doctypes() + per = qb.DocType("Payment Entry Reference") + payments_with_reference = ( + qb.from_(per) + .select(per.parent) + .distinct() + .where(per.reference_doctype.isin(advance_doctypes) & per.docstatus.eq(1)) + .run() + ) + pe = qb.DocType("Payment Entry") + advance_payment_entries = ( + qb.from_(pe) + .select(ConstantColumn("Payment Entry").as_("doctype")) + .select(pe.name) + .where(pe.name.isin(payments_with_reference) & pe.docstatus.eq(1)) + .run(as_dict=True) + ) + + return advance_payment_entries + + +def get_journals_with_so_po_reference() -> list: + advance_doctypes = get_advance_doctypes() + jea = qb.DocType("Journal Entry Account") + journals_with_reference = ( + qb.from_(jea) + .select(jea.parent) + .distinct() + .where(jea.reference_type.isin(advance_doctypes) & jea.docstatus.eq(1)) + .run() + ) + je = qb.DocType("Journal Entry") + advance_payment_entries = ( + qb.from_(je) + .select(ConstantColumn("Journal Entry").as_("doctype")) + .select(je.name) + .where(je.name.isin(journals_with_reference) & je.docstatus.eq(1)) + .run(as_dict=True) + ) + + return advance_payment_entries + + +def make_advance_ledger_entries(vouchers: list): + for x in vouchers: + frappe.get_doc(x.doctype, x.name).make_advance_payment_ledger_entries() + + +def execute(): + """ + Description: + Create Advance Payment Ledger Entry for all Payments made against Sales / Purchase Orders + """ + frappe.db.truncate("Advance Payment Ledger Entry") + payment_entries = get_payments_with_so_po_reference() + make_advance_ledger_entries(payment_entries) + + journals = get_journals_with_so_po_reference() + make_advance_ledger_entries(journals) From c6bfdcf50319486ab7348420667b5d4060e666c8 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Fri, 18 Oct 2024 16:22:38 +0530 Subject: [PATCH 12/25] chore: update patchex.txt (cherry picked from commit 8ab7194b1d6112af4ddb8034fb34dd9d49cf623f) --- erpnext/patches.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/erpnext/patches.txt b/erpnext/patches.txt index f48dc00fc8e..64c74f9d645 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -359,6 +359,7 @@ erpnext.patches.v15_0.allow_on_submit_dimensions_for_repostable_doctypes erpnext.patches.v14_0.update_flag_for_return_invoices #2024-03-22 erpnext.patches.v15_0.create_accounting_dimensions_in_payment_request erpnext.patches.v14_0.update_pos_return_ledger_entries #2024-08-16 +erpnext.patches.v15_0.create_advance_payment_ledger_records # below migration patch should always run last erpnext.patches.v14_0.migrate_gl_to_payment_ledger erpnext.stock.doctype.delivery_note.patches.drop_unused_return_against_index # 2023-12-20 From 063cef576cbebb8fb69b980a14d39b42caada964 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Fri, 18 Oct 2024 16:25:58 +0530 Subject: [PATCH 13/25] chore: update ignore_linked_doctypes for Journal Entry (cherry picked from commit 767ae6a372969ecbd03e7b66af3d9ede4202e9b0) --- erpnext/accounts/doctype/journal_entry/journal_entry.py | 1 + 1 file changed, 1 insertion(+) diff --git a/erpnext/accounts/doctype/journal_entry/journal_entry.py b/erpnext/accounts/doctype/journal_entry/journal_entry.py index d11cc27ccb7..bd25b13e999 100644 --- a/erpnext/accounts/doctype/journal_entry/journal_entry.py +++ b/erpnext/accounts/doctype/journal_entry/journal_entry.py @@ -219,6 +219,7 @@ class JournalEntry(AccountsController): "Repost Accounting Ledger Items", "Unreconcile Payment", "Unreconcile Payment Entries", + "Advance Payment Ledger Entry", ) self.make_gl_entries(1) self.make_advance_payment_ledger_entries() From 164d7cc896138b1c7bbfbdf00efe39fdb71c9dbb Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Tue, 22 Oct 2024 16:07:14 +0530 Subject: [PATCH 14/25] refactor: handle 'no data' situation in patch (cherry picked from commit 8e3bf7dc09dfdbe51043179dd32c04ab1f61f23e) --- .../create_advance_payment_ledger_records.py | 38 ++++++++++--------- 1 file changed, 21 insertions(+), 17 deletions(-) diff --git a/erpnext/patches/v15_0/create_advance_payment_ledger_records.py b/erpnext/patches/v15_0/create_advance_payment_ledger_records.py index 64d8e3c0f3c..13b4d95c760 100644 --- a/erpnext/patches/v15_0/create_advance_payment_ledger_records.py +++ b/erpnext/patches/v15_0/create_advance_payment_ledger_records.py @@ -10,6 +10,7 @@ def get_advance_doctypes() -> list: def get_payments_with_so_po_reference() -> list: + advance_payment_entries = [] advance_doctypes = get_advance_doctypes() per = qb.DocType("Payment Entry Reference") payments_with_reference = ( @@ -19,19 +20,21 @@ def get_payments_with_so_po_reference() -> list: .where(per.reference_doctype.isin(advance_doctypes) & per.docstatus.eq(1)) .run() ) - pe = qb.DocType("Payment Entry") - advance_payment_entries = ( - qb.from_(pe) - .select(ConstantColumn("Payment Entry").as_("doctype")) - .select(pe.name) - .where(pe.name.isin(payments_with_reference) & pe.docstatus.eq(1)) - .run(as_dict=True) - ) + if payments_with_reference: + pe = qb.DocType("Payment Entry") + advance_payment_entries = ( + qb.from_(pe) + .select(ConstantColumn("Payment Entry").as_("doctype")) + .select(pe.name) + .where(pe.name.isin(payments_with_reference) & pe.docstatus.eq(1)) + .run(as_dict=True) + ) return advance_payment_entries def get_journals_with_so_po_reference() -> list: + advance_journal_entries = [] advance_doctypes = get_advance_doctypes() jea = qb.DocType("Journal Entry Account") journals_with_reference = ( @@ -41,16 +44,17 @@ def get_journals_with_so_po_reference() -> list: .where(jea.reference_type.isin(advance_doctypes) & jea.docstatus.eq(1)) .run() ) - je = qb.DocType("Journal Entry") - advance_payment_entries = ( - qb.from_(je) - .select(ConstantColumn("Journal Entry").as_("doctype")) - .select(je.name) - .where(je.name.isin(journals_with_reference) & je.docstatus.eq(1)) - .run(as_dict=True) - ) + if journals_with_reference: + je = qb.DocType("Journal Entry") + advance_journal_entries = ( + qb.from_(je) + .select(ConstantColumn("Journal Entry").as_("doctype")) + .select(je.name) + .where(je.name.isin(journals_with_reference) & je.docstatus.eq(1)) + .run(as_dict=True) + ) - return advance_payment_entries + return advance_journal_entries def make_advance_ledger_entries(vouchers: list): From 68a95c7dbc943933e65ee0d08d30fdf6a2cbd592 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Tue, 22 Oct 2024 17:04:35 +0530 Subject: [PATCH 15/25] refactor: move creation logic to controller (cherry picked from commit ad88bde448d96cf50ece9a65fb3110b8bd89257f) --- .../doctype/journal_entry/journal_entry.py | 26 ---------- .../doctype/payment_entry/payment_entry.py | 20 -------- erpnext/controllers/accounts_controller.py | 50 +++++++++++++++++++ 3 files changed, 50 insertions(+), 46 deletions(-) diff --git a/erpnext/accounts/doctype/journal_entry/journal_entry.py b/erpnext/accounts/doctype/journal_entry/journal_entry.py index bd25b13e999..fb5c563d790 100644 --- a/erpnext/accounts/doctype/journal_entry/journal_entry.py +++ b/erpnext/accounts/doctype/journal_entry/journal_entry.py @@ -234,32 +234,6 @@ class JournalEntry(AccountsController): def get_title(self): return self.pay_to_recd_from or self.accounts[0].account - def make_advance_payment_ledger_entries(self): - if self.docstatus == 1 or self.docstatus == 2: - advance_payment_doctypes = frappe.get_hooks( - "advance_payment_receivable_doctypes" - ) + frappe.get_hooks("advance_payment_payable_doctypes") - - advance_doctype_references = [ - x for x in self.accounts if x.reference_type in advance_payment_doctypes - ] - - for x in advance_doctype_references: - # Looking for payments - dr_or_cr = "credit" if x.account_type == "Receivable" else "debit" - - amount = x.get(dr_or_cr) - if amount > 0: - doc = frappe.new_doc("Advance Payment Ledger Entry") - doc.company = self.company - doc.voucher_type = self.doctype - doc.voucher_no = self.name - doc.against_voucher_type = x.reference_type - doc.against_voucher_no = x.reference_name - doc.amount = amount if self.docstatus == 1 else -1 * amount - doc.event = "Submit" if self.docstatus == 1 else "Cancel" - doc.save() - def update_advance_paid(self): advance_paid = frappe._dict() for d in self.get("accounts"): diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py index abd6ea37e6d..8832b87eec7 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.py +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py @@ -1852,26 +1852,6 @@ class PaymentEntry(AccountsController): allocated_negative_outstanding, ) - def make_advance_payment_ledger_entries(self): - if self.docstatus == 1 or self.docstatus == 2: - advance_payment_doctypes = frappe.get_hooks( - "advance_payment_receivable_doctypes" - ) + frappe.get_hooks("advance_payment_payable_doctypes") - - advance_doctype_references = [ - x for x in self.references if x.reference_doctype in advance_payment_doctypes - ] - for x in advance_doctype_references: - doc = frappe.new_doc("Advance Payment Ledger Entry") - doc.company = self.company - doc.voucher_type = self.doctype - doc.voucher_no = self.name - doc.against_voucher_type = x.reference_doctype - doc.against_voucher_no = x.reference_name - doc.amount = x.allocated_amount if self.docstatus == 1 else -1 * x.allocated_amount - doc.event = "Submit" if self.docstatus == 1 else "Cancel" - doc.save() - @frappe.whitelist() def set_matched_payment_requests(self, matched_payment_requests): """ diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index 23498ad6883..a7a36b7e643 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -2570,6 +2570,56 @@ class AccountsController(TransactionBase): repost_ledger.insert() repost_ledger.submit() + def get_advance_payment_doctypes(self) -> list: + return frappe.get_hooks("advance_payment_receivable_doctypes") + frappe.get_hooks( + "advance_payment_payable_doctypes" + ) + + def make_advance_payment_ledger_for_journal(self): + advance_payment_doctypes = self.get_advance_payment_doctypes() + advance_doctype_references = [ + x for x in self.accounts if x.reference_type in advance_payment_doctypes + ] + + for x in advance_doctype_references: + # Looking for payments + dr_or_cr = "credit" if x.account_type == "Receivable" else "debit" + + amount = x.get(dr_or_cr) + if amount > 0: + doc = frappe.new_doc("Advance Payment Ledger Entry") + doc.company = self.company + doc.voucher_type = self.doctype + doc.voucher_no = self.name + doc.against_voucher_type = x.reference_type + doc.against_voucher_no = x.reference_name + doc.amount = amount if self.docstatus == 1 else -1 * amount + doc.event = "Submit" if self.docstatus == 1 else "Cancel" + doc.save() + + def make_advance_payment_ledger_for_payment(self): + advance_payment_doctypes = self.get_advance_payment_doctypes() + advance_doctype_references = [ + x for x in self.references if x.reference_doctype in advance_payment_doctypes + ] + for x in advance_doctype_references: + doc = frappe.new_doc("Advance Payment Ledger Entry") + doc.company = self.company + doc.voucher_type = self.doctype + doc.voucher_no = self.name + doc.against_voucher_type = x.reference_doctype + doc.against_voucher_no = x.reference_name + doc.amount = x.allocated_amount if self.docstatus == 1 else -1 * x.allocated_amount + doc.event = "Submit" if self.docstatus == 1 else "Cancel" + doc.save() + + def make_advance_payment_ledger_entries(self): + if self.docstatus != 0: + if self.doctype == "Journal Entry": + self.make_advance_payment_ledger_for_journal() + elif self.doctype == "Payment Entry": + self.make_advance_payment_ledger_for_payment() + @frappe.whitelist() def get_tax_rate(account_head): From 07a394a1c509b0539041f5fc88f1d000ff749951 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Wed, 30 Oct 2024 10:16:47 +0530 Subject: [PATCH 16/25] refactor: handle currency on advance payment ledger (cherry picked from commit ae6a81cd07e5f78a4b6107028a1322ec82978ae9) --- erpnext/controllers/accounts_controller.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index a7a36b7e643..6870c06bf5d 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -1958,7 +1958,7 @@ class AccountsController(TransactionBase): >>>>>>> 2b2360bf7b (refactor: calculate advance from advance ledger) advance = ( frappe.qb.from_(adv) - .select(adv.currency, Abs(Sum(adv.amount)).as_("amount")) + .select(adv.currency.as_("account_currency"), Abs(Sum(adv.amount)).as_("amount")) .where( (adv.against_voucher_type == self.doctype) & (adv.against_voucher_no == self.name) @@ -2602,6 +2602,11 @@ class AccountsController(TransactionBase): advance_doctype_references = [ x for x in self.references if x.reference_doctype in advance_payment_doctypes ] + currency = ( + self.paid_from_account_currency + if self.payment_type == "Receive" + else self.paid_to_account_currency + ) for x in advance_doctype_references: doc = frappe.new_doc("Advance Payment Ledger Entry") doc.company = self.company @@ -2610,6 +2615,7 @@ class AccountsController(TransactionBase): doc.against_voucher_type = x.reference_doctype doc.against_voucher_no = x.reference_name doc.amount = x.allocated_amount if self.docstatus == 1 else -1 * x.allocated_amount + doc.currency = currency doc.event = "Submit" if self.docstatus == 1 else "Cancel" doc.save() From d830ce1d88f7377627f047458f8c07b11b6b3725 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Wed, 30 Oct 2024 12:21:41 +0530 Subject: [PATCH 17/25] test: USD Sales Order with advance payment (cherry picked from commit 6c731561f3afb7208a7d8489f25c6d2008d6fd71) --- .../doctype/sales_order/test_sales_order.py | 58 +++++++++++++++++++ 1 file changed, 58 insertions(+) diff --git a/erpnext/selling/doctype/sales_order/test_sales_order.py b/erpnext/selling/doctype/sales_order/test_sales_order.py index 244a6b1ddad..c2c587899d4 100644 --- a/erpnext/selling/doctype/sales_order/test_sales_order.py +++ b/erpnext/selling/doctype/sales_order/test_sales_order.py @@ -1327,6 +1327,64 @@ class TestSalesOrder(AccountsTestMixin, FrappeTestCase): so.reload() self.assertEqual(so.advance_paid, 0) + def create_foreign_currency_usd_account(self): + account_name = "Debtors USD" + if not frappe.db.get_value( + "Account", filters={"account_name": account_name, "company": "_Test Company"} + ): + acc = frappe.new_doc("Account") + acc.account_name = account_name + acc.parent_account = "Accounts Receivable - _TC" + acc.company = "_Test Company" + acc.account_currency = "USD" + acc.account_type = "Receivable" + acc.insert() + else: + name = frappe.db.get_value( + "Account", + filters={"account_name": account_name, "company": "_Test Company"}, + fieldname="name", + pluck=True, + ) + acc = frappe.get_doc("Account", name) + self.debtors_usd = acc.name + + def test_advance_paid_and_currency_with_payment(self): + from erpnext.accounts.doctype.payment_entry.test_payment_entry import get_payment_entry + + self.create_customer("_Test USD Customer", "USD") + self.create_foreign_currency_usd_account() + + so = make_sales_order(customer=self.customer, currency="USD", qty=1, rate=100, do_not_submit=True) + so.conversion_rate = 80 + so.submit() + + pe_exchange_rate = 85 + pe = get_payment_entry("Sales Order", so.name, bank_account="_Test Bank - _TC") + pe.reference_no = "1" + pe.reference_date = nowdate() + pe.paid_from = self.debtors_usd + pe.paid_from_account_currency = "USD" + pe.source_exchange_rate = pe_exchange_rate + pe.paid_amount = so.grand_total + pe.received_amount = pe_exchange_rate * pe.paid_amount + pe.references[0].outstanding_amount = 100 + pe.references[0].total_amount = 100 + pe.references[0].allocated_amount = 100 + pe.save().submit() + + so.reload() + self.assertEqual(so.advance_paid, 100) + self.assertEqual(so.party_account_currency, "USD") + + # cancel advance payment + pe.reload() + pe.cancel() + + so.reload() + self.assertEqual(so.advance_paid, 0) + self.assertEqual(so.party_account_currency, "USD") + def test_cancel_sales_order_after_cancel_payment_entry(self): from erpnext.accounts.doctype.payment_entry.test_payment_entry import get_payment_entry From c8be4f3f781119516128d113365da736a149ebc1 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Wed, 30 Oct 2024 12:58:29 +0530 Subject: [PATCH 18/25] refactor: use dr / cr account currency field for journals (cherry picked from commit 9c1a4e284c0cbc0f943920a5501a6503ce4c5fde) --- erpnext/controllers/accounts_controller.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index 6870c06bf5d..03910516dea 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -2583,7 +2583,11 @@ class AccountsController(TransactionBase): for x in advance_doctype_references: # Looking for payments - dr_or_cr = "credit" if x.account_type == "Receivable" else "debit" + dr_or_cr = ( + "credit_in_account_currency" + if x.account_type == "Receivable" + else "debit_in_account_currency" + ) amount = x.get(dr_or_cr) if amount > 0: @@ -2595,6 +2599,7 @@ class AccountsController(TransactionBase): doc.against_voucher_no = x.reference_name doc.amount = amount if self.docstatus == 1 else -1 * amount doc.event = "Submit" if self.docstatus == 1 else "Cancel" + doc.currency = x.account_currency doc.save() def make_advance_payment_ledger_for_payment(self): From 7f9ae4e0447a2572a9909b88753e9311365ef464 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Wed, 30 Oct 2024 13:07:39 +0530 Subject: [PATCH 19/25] test: advance and currency from Journal (cherry picked from commit 18250825126be744b6bf4db3ce6bf23670d0a12a) --- .../doctype/sales_order/test_sales_order.py | 51 +++++++++++++++++++ 1 file changed, 51 insertions(+) diff --git a/erpnext/selling/doctype/sales_order/test_sales_order.py b/erpnext/selling/doctype/sales_order/test_sales_order.py index c2c587899d4..640669efbb7 100644 --- a/erpnext/selling/doctype/sales_order/test_sales_order.py +++ b/erpnext/selling/doctype/sales_order/test_sales_order.py @@ -1385,6 +1385,57 @@ class TestSalesOrder(AccountsTestMixin, FrappeTestCase): self.assertEqual(so.advance_paid, 0) self.assertEqual(so.party_account_currency, "USD") + def test_advance_paid_and_currency_with_journal(self): + from erpnext.accounts.doctype.payment_entry.test_payment_entry import get_payment_entry + + self.create_customer("_Test USD Customer", "USD") + self.create_foreign_currency_usd_account() + + so = make_sales_order(customer=self.customer, currency="USD", qty=1, rate=100, do_not_submit=True) + so.conversion_rate = 80 + so.submit() + + je_exchange_rate = 85 + je = frappe.get_doc( + { + "doctype": "Journal Entry", + "company": "_Test Company", + "voucher_type": "Journal Entry", + "posting_date": so.transaction_date, + "multi_currency": True, + "accounts": [ + { + "account": self.debtors_usd, + "party_type": "Customer", + "party": so.customer, + "credit": 8500, + "credit_in_account_currency": 100, + "is_advance": "Yes", + "reference_type": so.doctype, + "reference_name": so.name, + "exchange_rate": je_exchange_rate, + }, + { + "account": "_Test Bank - _TC", + "debit": 8500, + "debit_in_account_currency": 8500, + }, + ], + } + ) + je.save().submit() + so.reload() + self.assertEqual(so.advance_paid, 100) + self.assertEqual(so.party_account_currency, "USD") + + # cancel advance payment + je.reload() + je.cancel() + + so.reload() + self.assertEqual(so.advance_paid, 0) + self.assertEqual(so.party_account_currency, "USD") + def test_cancel_sales_order_after_cancel_payment_entry(self): from erpnext.accounts.doctype.payment_entry.test_payment_entry import get_payment_entry From 16c1fc75b5683cb75ab482ce63c07ab07e1cfb06 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Thu, 31 Oct 2024 10:08:41 +0530 Subject: [PATCH 20/25] chore: move tests to advance payment ledger doctype (cherry picked from commit 14cef3d4c4056bc4840c5a840c43b26fd803b388) --- .../test_advance_payment_ledger_entry.py | 116 +++++++++++++++++- .../doctype/sales_order/test_sales_order.py | 109 ---------------- 2 files changed, 113 insertions(+), 112 deletions(-) diff --git a/erpnext/accounts/doctype/advance_payment_ledger_entry/test_advance_payment_ledger_entry.py b/erpnext/accounts/doctype/advance_payment_ledger_entry/test_advance_payment_ledger_entry.py index 750a658102d..c953291ac6f 100644 --- a/erpnext/accounts/doctype/advance_payment_ledger_entry/test_advance_payment_ledger_entry.py +++ b/erpnext/accounts/doctype/advance_payment_ledger_entry/test_advance_payment_ledger_entry.py @@ -1,8 +1,13 @@ # Copyright (c) 2024, Frappe Technologies Pvt. Ltd. and Contributors # See license.txt -# import frappe +import frappe from frappe.tests import IntegrationTestCase, UnitTestCase +from frappe.utils import nowdate, today + +from erpnext.accounts.doctype.payment_entry.test_payment_entry import get_payment_entry +from erpnext.accounts.test.accounts_mixin import AccountsTestMixin +from erpnext.selling.doctype.sales_order.test_sales_order import make_sales_order # On IntegrationTestCase, the doctype test records and all # link-field test record depdendencies are recursively loaded @@ -11,10 +16,115 @@ EXTRA_TEST_RECORD_DEPENDENCIES = [] # eg. ["User"] IGNORE_TEST_RECORD_DEPENDENCIES = [] # eg. ["User"] -class TestAdvancePaymentLedgerEntry(IntegrationTestCase): +class TestAdvancePaymentLedgerEntry(AccountsTestMixin, IntegrationTestCase): """ Integration tests for AdvancePaymentLedgerEntry. Use this class for testing interactions between multiple components. """ - pass + def setUp(self): + self.create_company() + self.create_usd_receivable_account() + self.create_usd_payable_account() + self.create_item() + self.clear_old_entries() + + def tearDown(self): + frappe.db.rollback() + + def create_sales_order(self, qty=1, rate=100, currency="INR", do_not_submit=False): + """ + Helper method + """ + so = make_sales_order( + company=self.company, + customer=self.customer, + currency=currency, + item=self.item, + qty=qty, + rate=rate, + transaction_date=today(), + do_not_submit=do_not_submit, + ) + return so + + def test_so_advance_paid_and_currency_with_payment(self): + self.create_customer("_Test USD Customer", "USD") + + so = self.create_sales_order(currency="USD", do_not_submit=True) + so.conversion_rate = 80 + so.submit() + + pe_exchange_rate = 85 + pe = get_payment_entry(so.doctype, so.name, bank_account=self.cash) + pe.reference_no = "1" + pe.reference_date = nowdate() + pe.paid_from = self.debtors_usd + pe.paid_from_account_currency = "USD" + pe.source_exchange_rate = pe_exchange_rate + pe.paid_amount = so.grand_total + pe.received_amount = pe_exchange_rate * pe.paid_amount + pe.references[0].outstanding_amount = 100 + pe.references[0].total_amount = 100 + pe.references[0].allocated_amount = 100 + pe.save().submit() + + so.reload() + self.assertEqual(so.advance_paid, 100) + self.assertEqual(so.party_account_currency, "USD") + + # cancel advance payment + pe.reload() + pe.cancel() + + so.reload() + self.assertEqual(so.advance_paid, 0) + self.assertEqual(so.party_account_currency, "USD") + + def test_so_advance_paid_and_currency_with_journal(self): + self.create_customer("_Test USD Customer", "USD") + + so = self.create_sales_order(currency="USD", do_not_submit=True) + so.conversion_rate = 80 + so.submit() + + je_exchange_rate = 85 + je = frappe.get_doc( + { + "doctype": "Journal Entry", + "company": self.company, + "voucher_type": "Journal Entry", + "posting_date": so.transaction_date, + "multi_currency": True, + "accounts": [ + { + "account": self.debtors_usd, + "party_type": "Customer", + "party": so.customer, + "credit": 8500, + "credit_in_account_currency": 100, + "is_advance": "Yes", + "reference_type": so.doctype, + "reference_name": so.name, + "exchange_rate": je_exchange_rate, + }, + { + "account": self.cash, + "debit": 8500, + "debit_in_account_currency": 8500, + }, + ], + } + ) + je.save().submit() + so.reload() + self.assertEqual(so.advance_paid, 100) + self.assertEqual(so.party_account_currency, "USD") + + # cancel advance payment + je.reload() + je.cancel() + + so.reload() + self.assertEqual(so.advance_paid, 0) + self.assertEqual(so.party_account_currency, "USD") diff --git a/erpnext/selling/doctype/sales_order/test_sales_order.py b/erpnext/selling/doctype/sales_order/test_sales_order.py index 640669efbb7..244a6b1ddad 100644 --- a/erpnext/selling/doctype/sales_order/test_sales_order.py +++ b/erpnext/selling/doctype/sales_order/test_sales_order.py @@ -1327,115 +1327,6 @@ class TestSalesOrder(AccountsTestMixin, FrappeTestCase): so.reload() self.assertEqual(so.advance_paid, 0) - def create_foreign_currency_usd_account(self): - account_name = "Debtors USD" - if not frappe.db.get_value( - "Account", filters={"account_name": account_name, "company": "_Test Company"} - ): - acc = frappe.new_doc("Account") - acc.account_name = account_name - acc.parent_account = "Accounts Receivable - _TC" - acc.company = "_Test Company" - acc.account_currency = "USD" - acc.account_type = "Receivable" - acc.insert() - else: - name = frappe.db.get_value( - "Account", - filters={"account_name": account_name, "company": "_Test Company"}, - fieldname="name", - pluck=True, - ) - acc = frappe.get_doc("Account", name) - self.debtors_usd = acc.name - - def test_advance_paid_and_currency_with_payment(self): - from erpnext.accounts.doctype.payment_entry.test_payment_entry import get_payment_entry - - self.create_customer("_Test USD Customer", "USD") - self.create_foreign_currency_usd_account() - - so = make_sales_order(customer=self.customer, currency="USD", qty=1, rate=100, do_not_submit=True) - so.conversion_rate = 80 - so.submit() - - pe_exchange_rate = 85 - pe = get_payment_entry("Sales Order", so.name, bank_account="_Test Bank - _TC") - pe.reference_no = "1" - pe.reference_date = nowdate() - pe.paid_from = self.debtors_usd - pe.paid_from_account_currency = "USD" - pe.source_exchange_rate = pe_exchange_rate - pe.paid_amount = so.grand_total - pe.received_amount = pe_exchange_rate * pe.paid_amount - pe.references[0].outstanding_amount = 100 - pe.references[0].total_amount = 100 - pe.references[0].allocated_amount = 100 - pe.save().submit() - - so.reload() - self.assertEqual(so.advance_paid, 100) - self.assertEqual(so.party_account_currency, "USD") - - # cancel advance payment - pe.reload() - pe.cancel() - - so.reload() - self.assertEqual(so.advance_paid, 0) - self.assertEqual(so.party_account_currency, "USD") - - def test_advance_paid_and_currency_with_journal(self): - from erpnext.accounts.doctype.payment_entry.test_payment_entry import get_payment_entry - - self.create_customer("_Test USD Customer", "USD") - self.create_foreign_currency_usd_account() - - so = make_sales_order(customer=self.customer, currency="USD", qty=1, rate=100, do_not_submit=True) - so.conversion_rate = 80 - so.submit() - - je_exchange_rate = 85 - je = frappe.get_doc( - { - "doctype": "Journal Entry", - "company": "_Test Company", - "voucher_type": "Journal Entry", - "posting_date": so.transaction_date, - "multi_currency": True, - "accounts": [ - { - "account": self.debtors_usd, - "party_type": "Customer", - "party": so.customer, - "credit": 8500, - "credit_in_account_currency": 100, - "is_advance": "Yes", - "reference_type": so.doctype, - "reference_name": so.name, - "exchange_rate": je_exchange_rate, - }, - { - "account": "_Test Bank - _TC", - "debit": 8500, - "debit_in_account_currency": 8500, - }, - ], - } - ) - je.save().submit() - so.reload() - self.assertEqual(so.advance_paid, 100) - self.assertEqual(so.party_account_currency, "USD") - - # cancel advance payment - je.reload() - je.cancel() - - so.reload() - self.assertEqual(so.advance_paid, 0) - self.assertEqual(so.party_account_currency, "USD") - def test_cancel_sales_order_after_cancel_payment_entry(self): from erpnext.accounts.doctype.payment_entry.test_payment_entry import get_payment_entry From 91a276d4edbbd34ac40e63770fd9627791c76761 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Thu, 31 Oct 2024 10:24:20 +0530 Subject: [PATCH 21/25] test: PO 'Advance Paid' and curreny when using payment (cherry picked from commit ca85c75e3950d2dd1d7b5cba88ea61809b0e2f2a) --- .../test_advance_payment_ledger_entry.py | 50 +++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/erpnext/accounts/doctype/advance_payment_ledger_entry/test_advance_payment_ledger_entry.py b/erpnext/accounts/doctype/advance_payment_ledger_entry/test_advance_payment_ledger_entry.py index c953291ac6f..4fe206a351b 100644 --- a/erpnext/accounts/doctype/advance_payment_ledger_entry/test_advance_payment_ledger_entry.py +++ b/erpnext/accounts/doctype/advance_payment_ledger_entry/test_advance_payment_ledger_entry.py @@ -7,6 +7,7 @@ from frappe.utils import nowdate, today from erpnext.accounts.doctype.payment_entry.test_payment_entry import get_payment_entry from erpnext.accounts.test.accounts_mixin import AccountsTestMixin +from erpnext.buying.doctype.purchase_order.test_purchase_order import create_purchase_order from erpnext.selling.doctype.sales_order.test_sales_order import make_sales_order # On IntegrationTestCase, the doctype test records and all @@ -48,6 +49,22 @@ class TestAdvancePaymentLedgerEntry(AccountsTestMixin, IntegrationTestCase): ) return so + def create_purchase_order(self, qty=1, rate=100, currency="INR", do_not_submit=False): + """ + Helper method + """ + po = create_purchase_order( + company=self.company, + customer=self.supplier, + currency=currency, + item=self.item, + qty=qty, + rate=rate, + transaction_date=today(), + do_not_submit=do_not_submit, + ) + return po + def test_so_advance_paid_and_currency_with_payment(self): self.create_customer("_Test USD Customer", "USD") @@ -128,3 +145,36 @@ class TestAdvancePaymentLedgerEntry(AccountsTestMixin, IntegrationTestCase): so.reload() self.assertEqual(so.advance_paid, 0) self.assertEqual(so.party_account_currency, "USD") + + def test_po_advance_paid_and_currency_with_payment(self): + self.create_supplier("_Test USD Supplier", "USD") + + po = self.create_purchase_order(currency="USD", do_not_submit=True) + po.conversion_rate = 80 + po.submit() + + pe_exchange_rate = 85 + pe = get_payment_entry(po.doctype, po.name, bank_account=self.cash) + pe.reference_no = "1" + pe.reference_date = nowdate() + pe.paid_to = self.creditors_usd + pe.paid_to_account_currency = "USD" + pe.target_exchange_rate = pe_exchange_rate + pe.received_amount = po.grand_total + pe.paid_amount = pe_exchange_rate * pe.received_amount + pe.references[0].outstanding_amount = 100 + pe.references[0].total_amount = 100 + pe.references[0].allocated_amount = 100 + pe.save().submit() + + po.reload() + self.assertEqual(po.advance_paid, 100) + self.assertEqual(po.party_account_currency, "USD") + + # cancel advance payment + pe.reload() + pe.cancel() + + po.reload() + self.assertEqual(po.advance_paid, 0) + self.assertEqual(po.party_account_currency, "USD") From d0a655d5ae15732781f3e1ca824a4374b3295ad9 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Thu, 31 Oct 2024 10:27:20 +0530 Subject: [PATCH 22/25] test: PO advance and currency from journal (cherry picked from commit cf7b8f1b41c4ff9e86b6cb2eef01e5bd1d49d95e) --- .../test_advance_payment_ledger_entry.py | 48 +++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/erpnext/accounts/doctype/advance_payment_ledger_entry/test_advance_payment_ledger_entry.py b/erpnext/accounts/doctype/advance_payment_ledger_entry/test_advance_payment_ledger_entry.py index 4fe206a351b..f9ac59bfa80 100644 --- a/erpnext/accounts/doctype/advance_payment_ledger_entry/test_advance_payment_ledger_entry.py +++ b/erpnext/accounts/doctype/advance_payment_ledger_entry/test_advance_payment_ledger_entry.py @@ -178,3 +178,51 @@ class TestAdvancePaymentLedgerEntry(AccountsTestMixin, IntegrationTestCase): po.reload() self.assertEqual(po.advance_paid, 0) self.assertEqual(po.party_account_currency, "USD") + + def test_po_advance_paid_and_currency_with_journal(self): + self.create_supplier("_Test USD Supplier", "USD") + + po = self.create_purchase_order(currency="USD", do_not_submit=True) + po.conversion_rate = 80 + po.submit() + + je_exchange_rate = 85 + je = frappe.get_doc( + { + "doctype": "Journal Entry", + "company": self.company, + "voucher_type": "Journal Entry", + "posting_date": po.transaction_date, + "multi_currency": True, + "accounts": [ + { + "account": self.creditors_usd, + "party_type": "Supplier", + "party": po.supplier, + "debit": 8500, + "debit_in_account_currency": 100, + "is_advance": "Yes", + "reference_type": po.doctype, + "reference_name": po.name, + "exchange_rate": je_exchange_rate, + }, + { + "account": self.cash, + "credit": 8500, + "credit_in_account_currency": 8500, + }, + ], + } + ) + je.save().submit() + po.reload() + self.assertEqual(po.advance_paid, 100) + self.assertEqual(po.party_account_currency, "USD") + + # cancel advance payment + je.reload() + je.cancel() + + po.reload() + self.assertEqual(po.advance_paid, 0) + self.assertEqual(po.party_account_currency, "USD") From ba09ddcc3a0d02fc8cb318dd381e11e07afcccf5 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Fri, 1 Nov 2024 14:10:45 +0530 Subject: [PATCH 23/25] chore: resolve conflict --- erpnext/controllers/accounts_controller.py | 9 --------- 1 file changed, 9 deletions(-) diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index 03910516dea..d6016ef7d1d 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -1948,14 +1948,8 @@ class AccountsController(TransactionBase): return stock_items -<<<<<<< HEAD - def set_total_advance_paid(self): - ple = frappe.qb.DocType("Payment Ledger Entry") - party = self.customer if self.doctype == "Sales Order" else self.supplier -======= def calculate_total_advance_from_ledger(self): adv = frappe.qb.DocType("Advance Payment Ledger Entry") ->>>>>>> 2b2360bf7b (refactor: calculate advance from advance ledger) advance = ( frappe.qb.from_(adv) .select(adv.currency.as_("account_currency"), Abs(Sum(adv.amount)).as_("amount")) @@ -1968,13 +1962,10 @@ class AccountsController(TransactionBase): ) return advance -<<<<<<< HEAD -======= def set_total_advance_paid(self): advance = self.calculate_total_advance_from_ledger() advance_paid, order_total = None, None ->>>>>>> 2b2360bf7b (refactor: calculate advance from advance ledger) if advance: advance = advance[0] From 426010e21a3a02e2f2711a44f9b3e30bb209e6e2 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Fri, 1 Nov 2024 14:14:03 +0530 Subject: [PATCH 24/25] refactor: fetch correct hook variable --- erpnext/controllers/accounts_controller.py | 9 +++------ .../v15_0/create_advance_payment_ledger_records.py | 4 +--- 2 files changed, 4 insertions(+), 9 deletions(-) diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index d6016ef7d1d..1ae6b5256a5 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -349,9 +349,8 @@ class AccountsController(TransactionBase): adv = qb.DocType("Advance Payment Ledger Entry") qb.from_(adv).delete().where(adv.voucher_type.eq(self.doctype) & adv.voucher_no.eq(self.name)).run() - advance_payment_doctypes = frappe.get_hooks("advance_payment_receivable_doctypes") + frappe.get_hooks( - "advance_payment_payable_doctypes" - ) + advance_payment_doctypes = frappe.get_hooks("advance_payment_doctypes") + if self.doctype in advance_payment_doctypes: qb.from_(adv).delete().where( adv.against_voucher_type.eq(self.doctype) & adv.against_voucher_no.eq(self.name) @@ -2562,9 +2561,7 @@ class AccountsController(TransactionBase): repost_ledger.submit() def get_advance_payment_doctypes(self) -> list: - return frappe.get_hooks("advance_payment_receivable_doctypes") + frappe.get_hooks( - "advance_payment_payable_doctypes" - ) + return frappe.get_hooks("advance_payment_doctypes") def make_advance_payment_ledger_for_journal(self): advance_payment_doctypes = self.get_advance_payment_doctypes() diff --git a/erpnext/patches/v15_0/create_advance_payment_ledger_records.py b/erpnext/patches/v15_0/create_advance_payment_ledger_records.py index 13b4d95c760..8d247885cab 100644 --- a/erpnext/patches/v15_0/create_advance_payment_ledger_records.py +++ b/erpnext/patches/v15_0/create_advance_payment_ledger_records.py @@ -4,9 +4,7 @@ from frappe.query_builder.custom import ConstantColumn def get_advance_doctypes() -> list: - return frappe.get_hooks("advance_payment_receivable_doctypes") + frappe.get_hooks( - "advance_payment_payable_doctypes" - ) + return frappe.get_hooks("advance_payment_doctypes") def get_payments_with_so_po_reference() -> list: From 9bfcad31fd45d1585830d059bd8126853a1eef12 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Fri, 1 Nov 2024 20:08:07 +0530 Subject: [PATCH 25/25] refactor: replace non-existant IntegrationTestCase --- .../test_advance_payment_ledger_entry.py | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/erpnext/accounts/doctype/advance_payment_ledger_entry/test_advance_payment_ledger_entry.py b/erpnext/accounts/doctype/advance_payment_ledger_entry/test_advance_payment_ledger_entry.py index f9ac59bfa80..2f578aed172 100644 --- a/erpnext/accounts/doctype/advance_payment_ledger_entry/test_advance_payment_ledger_entry.py +++ b/erpnext/accounts/doctype/advance_payment_ledger_entry/test_advance_payment_ledger_entry.py @@ -2,7 +2,7 @@ # See license.txt import frappe -from frappe.tests import IntegrationTestCase, UnitTestCase +from frappe.tests.utils import FrappeTestCase from frappe.utils import nowdate, today from erpnext.accounts.doctype.payment_entry.test_payment_entry import get_payment_entry @@ -10,14 +10,8 @@ from erpnext.accounts.test.accounts_mixin import AccountsTestMixin from erpnext.buying.doctype.purchase_order.test_purchase_order import create_purchase_order from erpnext.selling.doctype.sales_order.test_sales_order import make_sales_order -# On IntegrationTestCase, the doctype test records and all -# link-field test record depdendencies are recursively loaded -# Use these module variables to add/remove to/from that list -EXTRA_TEST_RECORD_DEPENDENCIES = [] # eg. ["User"] -IGNORE_TEST_RECORD_DEPENDENCIES = [] # eg. ["User"] - -class TestAdvancePaymentLedgerEntry(AccountsTestMixin, IntegrationTestCase): +class TestAdvancePaymentLedgerEntry(AccountsTestMixin, FrappeTestCase): """ Integration tests for AdvancePaymentLedgerEntry. Use this class for testing interactions between multiple components.