From c8e2c9aa2535b35a9c2a803762ef26a55ef804e2 Mon Sep 17 00:00:00 2001 From: ljain112 Date: Thu, 28 Nov 2024 19:50:01 +0530 Subject: [PATCH] fix: handle multi currency in common party journal entry (cherry picked from commit e371f68d66700a1641bb36da533385682feac076) # Conflicts: # erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py --- .../sales_invoice/test_sales_invoice.py | 194 ++++++++++++++++++ erpnext/controllers/accounts_controller.py | 67 +++++- 2 files changed, 251 insertions(+), 10 deletions(-) diff --git a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py index 90bec018257..1d7359b811f 100644 --- a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py @@ -4135,6 +4135,200 @@ class TestSalesInvoice(FrappeTestCase): self.assertEqual(len(actual), 4) self.assertEqual(expected, actual) +<<<<<<< HEAD +======= + @IntegrationTestCase.change_settings("Accounts Settings", {"enable_common_party_accounting": True}) + def test_common_party_with_foreign_currency_jv(self): + from erpnext.accounts.doctype.account.test_account import create_account + from erpnext.accounts.doctype.opening_invoice_creation_tool.test_opening_invoice_creation_tool import ( + make_customer, + ) + from erpnext.accounts.doctype.party_link.party_link import create_party_link + from erpnext.buying.doctype.supplier.test_supplier import create_supplier + from erpnext.setup.utils import get_exchange_rate + + creditors = create_account( + account_name="Creditors USD", + parent_account="Accounts Payable - _TC", + company="_Test Company", + account_currency="USD", + account_type="Payable", + ) + debtors = create_account( + account_name="Debtors USD", + parent_account="Accounts Receivable - _TC", + company="_Test Company", + account_currency="USD", + account_type="Receivable", + ) + + # create a customer + customer = make_customer(customer="_Test Common Party USD") + cust_doc = frappe.get_doc("Customer", customer) + cust_doc.default_currency = "USD" + test_account_details = { + "company": "_Test Company", + "account": debtors, + } + cust_doc.append("accounts", test_account_details) + cust_doc.save() + + # create a supplier + supplier = create_supplier(supplier_name="_Test Common Party USD").name + supp_doc = frappe.get_doc("Supplier", supplier) + supp_doc.default_currency = "USD" + test_account_details = { + "company": "_Test Company", + "account": creditors, + } + supp_doc.append("accounts", test_account_details) + supp_doc.save() + + # create a party link between customer & supplier + create_party_link("Supplier", supplier, customer) + + # create a sales invoice + si = create_sales_invoice( + customer=customer, + currency="USD", + conversion_rate=get_exchange_rate("USD", "INR"), + debit_to=debtors, + do_not_save=1, + ) + si.party_account_currency = "USD" + si.save() + si.submit() + + # check outstanding of sales invoice + si.reload() + self.assertEqual(si.status, "Paid") + self.assertEqual(flt(si.outstanding_amount), 0.0) + + # check creation of journal entry + jv = frappe.get_all( + "Journal Entry Account", + { + "account": si.debit_to, + "party_type": "Customer", + "party": si.customer, + "reference_type": si.doctype, + "reference_name": si.name, + }, + pluck="credit_in_account_currency", + ) + self.assertTrue(jv) + self.assertEqual(jv[0], si.grand_total) + + @IntegrationTestCase.change_settings("Accounts Settings", {"enable_common_party_accounting": True}) + def test_common_party_with_different_currency_in_debtor_and_creditor(self): + from erpnext.accounts.doctype.account.test_account import create_account + from erpnext.accounts.doctype.opening_invoice_creation_tool.test_opening_invoice_creation_tool import ( + make_customer, + ) + from erpnext.accounts.doctype.party_link.party_link import create_party_link + from erpnext.buying.doctype.supplier.test_supplier import create_supplier + from erpnext.setup.utils import get_exchange_rate + + creditors = create_account( + account_name="Creditors INR", + parent_account="Accounts Payable - _TC", + company="_Test Company", + account_currency="INR", + account_type="Payable", + ) + debtors = create_account( + account_name="Debtors USD", + parent_account="Accounts Receivable - _TC", + company="_Test Company", + account_currency="USD", + account_type="Receivable", + ) + + # create a customer + customer = make_customer(customer="_Test Common Party USD") + cust_doc = frappe.get_doc("Customer", customer) + cust_doc.default_currency = "USD" + test_account_details = { + "company": "_Test Company", + "account": debtors, + } + cust_doc.append("accounts", test_account_details) + cust_doc.save() + + # create a supplier + supplier = create_supplier(supplier_name="_Test Common Party INR").name + supp_doc = frappe.get_doc("Supplier", supplier) + supp_doc.default_currency = "INR" + test_account_details = { + "company": "_Test Company", + "account": creditors, + } + supp_doc.append("accounts", test_account_details) + supp_doc.save() + + # create a party link between customer & supplier + create_party_link("Supplier", supplier, customer) + + # create a sales invoice + si = create_sales_invoice( + customer=customer, + currency="USD", + conversion_rate=get_exchange_rate("USD", "INR"), + debit_to=debtors, + do_not_save=1, + ) + si.party_account_currency = "USD" + si.save() + si.submit() + + # check outstanding of sales invoice + si.reload() + self.assertEqual(si.status, "Paid") + self.assertEqual(flt(si.outstanding_amount), 0.0) + + # check creation of journal entry + jv = frappe.get_all( + "Journal Entry Account", + { + "account": si.debit_to, + "party_type": "Customer", + "party": si.customer, + "reference_type": si.doctype, + "reference_name": si.name, + }, + pluck="credit_in_account_currency", + ) + self.assertTrue(jv) + self.assertEqual(jv[0], si.grand_total) + + def test_invoice_remarks(self): + si = frappe.copy_doc(self.globalTestRecords["Sales Invoice"][0]) + si.po_no = "Test PO" + si.po_date = nowdate() + si.save() + si.submit() + self.assertEqual(si.remarks, f"Against Customer Order Test PO dated {format_date(nowdate())}") + + def test_gl_voucher_subtype(self): + si = create_sales_invoice() + gl_entries = frappe.get_all( + "GL Entry", + filters={"voucher_type": "Sales Invoice", "voucher_no": si.name}, + pluck="voucher_subtype", + ) + + self.assertTrue(all([x == "Sales Invoice" for x in gl_entries])) + + si = create_sales_invoice(is_return=1, qty=-1) + gl_entries = frappe.get_all( + "GL Entry", + filters={"voucher_type": "Sales Invoice", "voucher_no": si.name}, + pluck="voucher_subtype", + ) + + self.assertTrue(all([x == "Credit Note" for x in gl_entries])) + +>>>>>>> e371f68d66 (fix: handle multi currency in common party journal entry) def set_advance_flag(company, flag, default_account): frappe.db.set_value( diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index b4b23dd5f4c..bc0cdde94de 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -2465,6 +2465,12 @@ class AccountsController(TransactionBase): secondary_account = get_party_account(secondary_party_type, secondary_party, self.company) primary_account_currency = get_account_currency(primary_account) secondary_account_currency = get_account_currency(secondary_account) + default_currency = erpnext.get_company_currency(self.company) + + # Determine if multi-currency journal entry is needed + multi_currency = ( + primary_account_currency != default_currency or secondary_account_currency != default_currency + ) jv = frappe.new_doc("Journal Entry") jv.voucher_type = "Journal Entry" @@ -2489,7 +2495,7 @@ class AccountsController(TransactionBase): advance_entry.cost_center = self.cost_center or erpnext.get_default_cost_center(self.company) advance_entry.is_advance = "Yes" - # update dimesions + # Update dimensions dimensions_dict = frappe._dict() active_dimensions = get_dimensions()[0] for dim in active_dimensions: @@ -2498,17 +2504,58 @@ class AccountsController(TransactionBase): reconcilation_entry.update(dimensions_dict) advance_entry.update(dimensions_dict) - if self.doctype == "Sales Invoice": - reconcilation_entry.credit_in_account_currency = self.outstanding_amount - advance_entry.debit_in_account_currency = self.outstanding_amount + # Calculate exchange rates if necessary + if multi_currency: + # Exchange rates for primary and secondary accounts + exc_rate_primary_to_default = ( + 1 + if primary_account_currency == default_currency + else get_exchange_rate(primary_account_currency, default_currency, self.posting_date) + ) + exc_rate_secondary_to_default = ( + 1 + if secondary_account_currency == default_currency + else get_exchange_rate(secondary_account_currency, default_currency, self.posting_date) + ) + exc_rate_secondary_to_primary = ( + 1 + if secondary_account_currency == primary_account_currency + else get_exchange_rate( + secondary_account_currency, primary_account_currency, self.posting_date + ) + ) + + # Convert outstanding amount from secondary to primary account currency, if needed + + os_in_default_currency = self.outstanding_amount * exc_rate_secondary_to_default + os_in_primary_currency = self.outstanding_amount * exc_rate_secondary_to_primary + + if self.doctype == "Sales Invoice": + # Calculate credit and debit values for reconciliation and advance entries + reconcilation_entry.credit_in_account_currency = self.outstanding_amount + reconcilation_entry.credit = os_in_default_currency + + advance_entry.debit_in_account_currency = os_in_primary_currency + advance_entry.debit = os_in_default_currency + else: + advance_entry.credit_in_account_currency = os_in_primary_currency + advance_entry.credit = os_in_default_currency + + reconcilation_entry.debit_in_account_currency = self.outstanding_amount + reconcilation_entry.debit = os_in_default_currency + + # Set exchange rates for entries + reconcilation_entry.exchange_rate = exc_rate_secondary_to_default + advance_entry.exchange_rate = exc_rate_primary_to_default else: - advance_entry.credit_in_account_currency = self.outstanding_amount - reconcilation_entry.debit_in_account_currency = self.outstanding_amount - - default_currency = erpnext.get_company_currency(self.company) - if primary_account_currency != default_currency or secondary_account_currency != default_currency: - jv.multi_currency = 1 + if self.doctype == "Sales Invoice": + reconcilation_entry.credit_in_account_currency = self.outstanding_amount + advance_entry.debit_in_account_currency = self.outstanding_amount + else: + advance_entry.credit_in_account_currency = self.outstanding_amount + reconcilation_entry.debit_in_account_currency = self.outstanding_amount + jv.multi_currency = multi_currency jv.append("accounts", reconcilation_entry) jv.append("accounts", advance_entry)