From b09eade3e4b41833e507410410f63b93d8d17de7 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Fri, 16 Dec 2022 21:58:22 +0530 Subject: [PATCH 1/2] fix: ERR journals reported in AR/AP Exchange Rate Revaluation on Receivable/Payable will included in AR/AP report --- .../accounts_receivable.py | 20 ++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/erpnext/accounts/report/accounts_receivable/accounts_receivable.py b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py index a195c57586..9d9684340c 100755 --- a/erpnext/accounts/report/accounts_receivable/accounts_receivable.py +++ b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py @@ -99,6 +99,9 @@ class ReceivablePayableReport(object): # Get return entries self.get_return_entries() + # Get Exchange Rate Revaluations + self.get_exchange_rate_revaluations() + self.data = [] for ple in self.ple_entries: @@ -251,7 +254,8 @@ class ReceivablePayableReport(object): row.invoice_grand_total = row.invoiced if (abs(row.outstanding) > 1.0 / 10**self.currency_precision) and ( - abs(row.outstanding_in_account_currency) > 1.0 / 10**self.currency_precision + (abs(row.outstanding_in_account_currency) > 1.0 / 10**self.currency_precision) + or (row.voucher_no in self.err_journals) ): # non-zero oustanding, we must consider this row @@ -1028,3 +1032,17 @@ class ReceivablePayableReport(object): "data": {"labels": self.ageing_column_labels, "datasets": rows}, "type": "percentage", } + + def get_exchange_rate_revaluations(self): + je = qb.DocType("Journal Entry") + results = ( + qb.from_(je) + .select(je.name) + .where( + (je.company == self.filters.company) + & (je.posting_date.lte(self.filters.report_date)) + & (je.voucher_type == "Exchange Rate Revaluation") + ) + .run() + ) + self.err_journals = [x[0] for x in results] if results else [] From 2ed86760d7696b0129a0d01cc4287cfe001f3a66 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Sun, 18 Dec 2022 12:39:54 +0530 Subject: [PATCH 2/2] test: err for party should be in AR/AP report --- .../test_accounts_receivable.py | 134 +++++++++++++++--- 1 file changed, 116 insertions(+), 18 deletions(-) diff --git a/erpnext/accounts/report/accounts_receivable/test_accounts_receivable.py b/erpnext/accounts/report/accounts_receivable/test_accounts_receivable.py index bac8beed2e..97a9c15fc7 100644 --- a/erpnext/accounts/report/accounts_receivable/test_accounts_receivable.py +++ b/erpnext/accounts/report/accounts_receivable/test_accounts_receivable.py @@ -1,9 +1,10 @@ import unittest import frappe -from frappe.tests.utils import FrappeTestCase -from frappe.utils import add_days, getdate, today +from frappe.tests.utils import FrappeTestCase, change_settings +from frappe.utils import add_days, flt, getdate, today +from erpnext import get_default_cost_center from erpnext.accounts.doctype.payment_entry.payment_entry import get_payment_entry from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice from erpnext.accounts.report.accounts_receivable.accounts_receivable import execute @@ -17,10 +18,37 @@ class TestAccountsReceivable(FrappeTestCase): frappe.db.sql("delete from `tabPayment Entry` where company='_Test Company 2'") frappe.db.sql("delete from `tabGL Entry` where company='_Test Company 2'") frappe.db.sql("delete from `tabPayment Ledger Entry` where company='_Test Company 2'") + frappe.db.sql("delete from `tabJournal Entry` where company='_Test Company 2'") + frappe.db.sql("delete from `tabExchange Rate Revaluation` where company='_Test Company 2'") + + self.create_usd_account() def tearDown(self): frappe.db.rollback() + def create_usd_account(self): + name = "Debtors USD" + exists = frappe.db.get_list( + "Account", filters={"company": "_Test Company 2", "account_name": "Debtors USD"} + ) + if exists: + self.debtors_usd = exists[0].name + else: + debtors = frappe.get_doc( + "Account", + frappe.db.get_list( + "Account", filters={"company": "_Test Company 2", "account_name": "Debtors"} + )[0].name, + ) + + debtors_usd = frappe.new_doc("Account") + debtors_usd.company = debtors.company + debtors_usd.account_name = "Debtors USD" + debtors_usd.account_currency = "USD" + debtors_usd.parent_account = debtors.parent_account + debtors_usd.account_type = debtors.account_type + self.debtors_usd = debtors_usd.save().name + def test_accounts_receivable(self): filters = { "company": "_Test Company 2", @@ -33,7 +61,7 @@ class TestAccountsReceivable(FrappeTestCase): } # check invoice grand total and invoiced column's value for 3 payment terms - name = make_sales_invoice() + name = make_sales_invoice().name report = execute(filters) expected_data = [[100, 30], [100, 50], [100, 20]] @@ -118,8 +146,74 @@ class TestAccountsReceivable(FrappeTestCase): ], ) + @change_settings( + "Accounts Settings", {"allow_multi_currency_invoices_against_single_party_account": 1} + ) + def test_exchange_revaluation_for_party(self): + """ + Exchange Revaluation for party on Receivable/Payable shoule be included + """ -def make_sales_invoice(): + company = "_Test Company 2" + customer = "_Test Customer 2" + + # Using Exchange Gain/Loss account for unrealized as well. + company_doc = frappe.get_doc("Company", company) + company_doc.unrealized_exchange_gain_loss_account = company_doc.exchange_gain_loss_account + company_doc.save() + + si = make_sales_invoice(no_payment_schedule=True, do_not_submit=True) + si.currency = "USD" + si.conversion_rate = 0.90 + si.debit_to = self.debtors_usd + si = si.save().submit() + + # Exchange Revaluation + err = frappe.new_doc("Exchange Rate Revaluation") + err.company = company + err.posting_date = today() + accounts = err.get_accounts_data() + err.extend("accounts", accounts) + err.accounts[0].new_exchange_rate = 0.95 + row = err.accounts[0] + row.new_balance_in_base_currency = flt( + row.new_exchange_rate * flt(row.balance_in_account_currency) + ) + row.gain_loss = row.new_balance_in_base_currency - flt(row.balance_in_base_currency) + err.set_total_gain_loss() + err = err.save().submit() + + # Submit JV for ERR + jv = frappe.get_doc(err.make_jv_entry()) + jv = jv.save() + for x in jv.accounts: + x.cost_center = get_default_cost_center(jv.company) + jv.submit() + + filters = { + "company": company, + "report_date": today(), + "range1": 30, + "range2": 60, + "range3": 90, + "range4": 120, + } + report = execute(filters) + + expected_data_for_err = [0, -5, 0, 5] + row = [x for x in report[1] if x.voucher_type == jv.doctype and x.voucher_no == jv.name][0] + self.assertEqual( + expected_data_for_err, + [ + row.invoiced, + row.paid, + row.credit_note, + row.outstanding, + ], + ) + + +def make_sales_invoice(no_payment_schedule=False, do_not_submit=False): frappe.set_user("Administrator") si = create_sales_invoice( @@ -134,22 +228,26 @@ def make_sales_invoice(): do_not_save=1, ) - si.append( - "payment_schedule", - dict(due_date=getdate(add_days(today(), 30)), invoice_portion=30.00, payment_amount=30), - ) - si.append( - "payment_schedule", - dict(due_date=getdate(add_days(today(), 60)), invoice_portion=50.00, payment_amount=50), - ) - si.append( - "payment_schedule", - dict(due_date=getdate(add_days(today(), 90)), invoice_portion=20.00, payment_amount=20), - ) + if not no_payment_schedule: + si.append( + "payment_schedule", + dict(due_date=getdate(add_days(today(), 30)), invoice_portion=30.00, payment_amount=30), + ) + si.append( + "payment_schedule", + dict(due_date=getdate(add_days(today(), 60)), invoice_portion=50.00, payment_amount=50), + ) + si.append( + "payment_schedule", + dict(due_date=getdate(add_days(today(), 90)), invoice_portion=20.00, payment_amount=20), + ) - si.submit() + si = si.save() - return si.name + if not do_not_submit: + si = si.submit() + + return si def make_payment(docname):