From c8e2c9aa2535b35a9c2a803762ef26a55ef804e2 Mon Sep 17 00:00:00 2001 From: ljain112 Date: Thu, 28 Nov 2024 19:50:01 +0530 Subject: [PATCH 1/2] 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) From 4c5570ae7d5d04fd3254f3c68e08877530a21997 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Mon, 2 Dec 2024 13:46:39 +0530 Subject: [PATCH 2/2] chore: resolve conflict --- .../sales_invoice/test_sales_invoice.py | 114 +----------------- 1 file changed, 1 insertion(+), 113 deletions(-) diff --git a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py index 1d7359b811f..d2b8882743a 100644 --- a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py @@ -4135,91 +4135,7 @@ 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}) + @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 ( @@ -4301,34 +4217,6 @@ class TestSalesInvoice(FrappeTestCase): 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(