From a3a40febf370a1537b971891419786a0f67877a8 Mon Sep 17 00:00:00 2001 From: venkat102 Date: Fri, 6 Sep 2024 19:53:40 +0530 Subject: [PATCH 1/5] fix: check multi-currency on jv for common party accounting with foreign currency (cherry picked from commit 00938bfd4d1243b570e83f13f5774029ad0df3a1) --- erpnext/controllers/accounts_controller.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index 1b624d3dd1a..a8a790719bf 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -2298,6 +2298,8 @@ class AccountsController(TransactionBase): primary_account = get_party_account(primary_party_type, primary_party, self.company) 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) jv = frappe.new_doc("Journal Entry") jv.voucher_type = "Journal Entry" @@ -2338,6 +2340,10 @@ class AccountsController(TransactionBase): 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 + jv.append("accounts", reconcilation_entry) jv.append("accounts", advance_entry) From b6f352a02470bc7034b9e05e8ea142fbebeb97c8 Mon Sep 17 00:00:00 2001 From: venkat102 Date: Fri, 6 Sep 2024 20:01:35 +0530 Subject: [PATCH 2/5] test: add unit test for common party with foreign currency (cherry picked from commit 740a04a70437422dac2b824d058b264026228b79) # Conflicts: # erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py --- .../sales_invoice/test_sales_invoice.py | 196 +++++++++++++++++- 1 file changed, 195 insertions(+), 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 f5f905e33ec..9677660c5f9 100644 --- a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py @@ -44,7 +44,7 @@ class TestSalesInvoice(FrappeTestCase): from erpnext.stock.doctype.stock_ledger_entry.test_stock_ledger_entry import create_items create_items(["_Test Internal Transfer Item"], uoms=[{"uom": "Box", "conversion_factor": 10}]) - create_internal_parties() + # create_internal_parties() setup_accounts() frappe.db.set_single_value("Accounts Settings", "acc_frozen_upto", None) @@ -3679,6 +3679,7 @@ class TestSalesInvoice(FrappeTestCase): self.assertEqual(res[0][0], pos_return.return_against) +<<<<<<< HEAD def check_gl_entries(doc, voucher_no, expected_gle, posting_date): gl_entries = frappe.db.sql( """select account, debit, credit, posting_date @@ -3688,6 +3689,199 @@ def check_gl_entries(doc, voucher_no, expected_gle, posting_date): order by posting_date asc, account asc""", (voucher_no, posting_date), as_dict=1, +======= + def _create_opening_roundoff_account(self, company_name): + liability_root = frappe.db.get_all( + "Account", + filters={"company": company_name, "root_type": "Liability", "disabled": 0}, + order_by="lft", + limit=1, + )[0] + + # setup round off account + if acc := frappe.db.exists( + "Account", + { + "account_name": "Round Off for Opening", + "account_type": "Round Off for Opening", + "company": company_name, + }, + ): + frappe.db.set_value("Company", company_name, "round_off_for_opening", acc) + else: + acc = frappe.new_doc("Account") + acc.company = company_name + acc.parent_account = liability_root.name + acc.account_name = "Round Off for Opening" + acc.account_type = "Round Off for Opening" + acc.save() + frappe.db.set_value("Company", company_name, "round_off_for_opening", acc.name) + + def test_opening_invoice_with_rounding_adjustment(self): + si = create_sales_invoice(qty=1, rate=99.98, do_not_submit=True) + si.is_opening = "Yes" + si.items[0].income_account = "Temporary Opening - _TC" + si.save() + + self._create_opening_roundoff_account(si.company) + + si.reload() + si.submit() + res = frappe.db.get_all( + "GL Entry", + filters={"voucher_no": si.name, "is_opening": "Yes"}, + fields=["account", "debit", "credit", "is_opening"], + ) + self.assertEqual(len(res), 3) + + def _create_opening_invoice_with_inclusive_tax(self): + si = create_sales_invoice(qty=1, rate=90, do_not_submit=True) + si.is_opening = "Yes" + si.items[0].income_account = "Temporary Opening - _TC" + item_template = si.items[0].as_dict() + item_template.name = None + item_template.rate = 55 + si.append("items", item_template) + si.append( + "taxes", + { + "charge_type": "On Net Total", + "account_head": "_Test Account Service Tax - _TC", + "cost_center": "_Test Cost Center - _TC", + "description": "Testing...", + "rate": 5, + "included_in_print_rate": True, + }, + ) + # there will be 0.01 precision loss between Dr and Cr + # caused by 'included_in_print_tax' option + si.save() + return si + + def test_rounding_validation_for_opening_with_inclusive_tax(self): + si = self._create_opening_invoice_with_inclusive_tax() + # 'Round Off for Opening' not set in Company master + # Ledger level validation must be thrown + self.assertRaises(frappe.ValidationError, si.submit) + + def test_ledger_entries_on_opening_invoice_with_rounding_loss_by_inclusive_tax(self): + si = self._create_opening_invoice_with_inclusive_tax() + # 'Round Off for Opening' is set in Company master + self._create_opening_roundoff_account(si.company) + + si.submit() + actual = frappe.db.get_all( + "GL Entry", + filters={"voucher_no": si.name, "is_opening": "Yes", "is_cancelled": False}, + fields=["account", "debit", "credit", "is_opening"], + order_by="account,debit", + ) + expected = [ + {"account": "_Test Account Service Tax - _TC", "debit": 0.0, "credit": 6.9, "is_opening": "Yes"}, + {"account": "Debtors - _TC", "debit": 145.0, "credit": 0.0, "is_opening": "Yes"}, + {"account": "Round Off for Opening - _TC", "debit": 0.0, "credit": 0.01, "is_opening": "Yes"}, + {"account": "Temporary Opening - _TC", "debit": 0.0, "credit": 138.09, "is_opening": "Yes"}, + ] + self.assertEqual(len(actual), 4) + self.assertEqual(expected, actual) + + 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() + + # enable common party accounting + frappe.db.set_single_value("Accounts Settings", "enable_common_party_accounting", 1) + + # create a party link between customer & supplier + party_link = 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) + + party_link.delete() + frappe.db.set_single_value("Accounts Settings", "enable_common_party_accounting", 0) + + +def set_advance_flag(company, flag, default_account): + frappe.db.set_value( + "Company", + company, + { + "book_advance_payments_in_separate_party_account": flag, + "default_advance_received_account": default_account, + }, +>>>>>>> 740a04a704 (test: add unit test for common party with foreign currency) ) for i, gle in enumerate(gl_entries): From 4dd06b69a1eac3d0b490a8d78f156bdba684c052 Mon Sep 17 00:00:00 2001 From: venkat102 Date: Fri, 6 Sep 2024 20:03:56 +0530 Subject: [PATCH 3/5] fix: uncomment internal parties (cherry picked from commit 454e18ad5fef1ad81aab3efcca1d7886a0d80fbf) --- 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 9677660c5f9..9cf2bf77248 100644 --- a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py @@ -44,7 +44,7 @@ class TestSalesInvoice(FrappeTestCase): from erpnext.stock.doctype.stock_ledger_entry.test_stock_ledger_entry import create_items create_items(["_Test Internal Transfer Item"], uoms=[{"uom": "Box", "conversion_factor": 10}]) - # create_internal_parties() + create_internal_parties() setup_accounts() frappe.db.set_single_value("Accounts Settings", "acc_frozen_upto", None) From de0b8c07f6c4522ad8c1825943151a8c7a70e157 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Sat, 7 Sep 2024 11:39:06 +0530 Subject: [PATCH 4/5] refactor(test): use change_settings decorator (cherry picked from commit ee94fb37c81a28e89aa2175b933d231a5a6601f7) --- .../accounts/doctype/sales_invoice/test_sales_invoice.py | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py index 9cf2bf77248..e0318ebbf2a 100644 --- a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py @@ -3785,6 +3785,7 @@ def check_gl_entries(doc, voucher_no, expected_gle, posting_date): self.assertEqual(len(actual), 4) self.assertEqual(expected, actual) + @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 ( @@ -3831,11 +3832,8 @@ def check_gl_entries(doc, voucher_no, expected_gle, posting_date): supp_doc.append("accounts", test_account_details) supp_doc.save() - # enable common party accounting - frappe.db.set_single_value("Accounts Settings", "enable_common_party_accounting", 1) - # create a party link between customer & supplier - party_link = create_party_link("Supplier", supplier, customer) + create_party_link("Supplier", supplier, customer) # create a sales invoice si = create_sales_invoice( @@ -3869,9 +3867,6 @@ def check_gl_entries(doc, voucher_no, expected_gle, posting_date): self.assertTrue(jv) self.assertEqual(jv[0], si.grand_total) - party_link.delete() - frappe.db.set_single_value("Accounts Settings", "enable_common_party_accounting", 0) - def set_advance_flag(company, flag, default_account): frappe.db.set_value( From cf78f9702c64ff6fb87cc011af59b9fa721a11c7 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Mon, 9 Sep 2024 11:01:54 +0530 Subject: [PATCH 5/5] chore: resolve conflict --- .../sales_invoice/test_sales_invoice.py | 125 ++---------------- 1 file changed, 9 insertions(+), 116 deletions(-) diff --git a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py index e0318ebbf2a..9b303430455 100644 --- a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py @@ -3678,113 +3678,6 @@ class TestSalesInvoice(FrappeTestCase): self.assertEqual(len(res), 1) self.assertEqual(res[0][0], pos_return.return_against) - -<<<<<<< HEAD -def check_gl_entries(doc, voucher_no, expected_gle, posting_date): - gl_entries = frappe.db.sql( - """select account, debit, credit, posting_date - from `tabGL Entry` - where voucher_type='Sales Invoice' and voucher_no=%s and posting_date > %s - and is_cancelled = 0 - order by posting_date asc, account asc""", - (voucher_no, posting_date), - as_dict=1, -======= - def _create_opening_roundoff_account(self, company_name): - liability_root = frappe.db.get_all( - "Account", - filters={"company": company_name, "root_type": "Liability", "disabled": 0}, - order_by="lft", - limit=1, - )[0] - - # setup round off account - if acc := frappe.db.exists( - "Account", - { - "account_name": "Round Off for Opening", - "account_type": "Round Off for Opening", - "company": company_name, - }, - ): - frappe.db.set_value("Company", company_name, "round_off_for_opening", acc) - else: - acc = frappe.new_doc("Account") - acc.company = company_name - acc.parent_account = liability_root.name - acc.account_name = "Round Off for Opening" - acc.account_type = "Round Off for Opening" - acc.save() - frappe.db.set_value("Company", company_name, "round_off_for_opening", acc.name) - - def test_opening_invoice_with_rounding_adjustment(self): - si = create_sales_invoice(qty=1, rate=99.98, do_not_submit=True) - si.is_opening = "Yes" - si.items[0].income_account = "Temporary Opening - _TC" - si.save() - - self._create_opening_roundoff_account(si.company) - - si.reload() - si.submit() - res = frappe.db.get_all( - "GL Entry", - filters={"voucher_no": si.name, "is_opening": "Yes"}, - fields=["account", "debit", "credit", "is_opening"], - ) - self.assertEqual(len(res), 3) - - def _create_opening_invoice_with_inclusive_tax(self): - si = create_sales_invoice(qty=1, rate=90, do_not_submit=True) - si.is_opening = "Yes" - si.items[0].income_account = "Temporary Opening - _TC" - item_template = si.items[0].as_dict() - item_template.name = None - item_template.rate = 55 - si.append("items", item_template) - si.append( - "taxes", - { - "charge_type": "On Net Total", - "account_head": "_Test Account Service Tax - _TC", - "cost_center": "_Test Cost Center - _TC", - "description": "Testing...", - "rate": 5, - "included_in_print_rate": True, - }, - ) - # there will be 0.01 precision loss between Dr and Cr - # caused by 'included_in_print_tax' option - si.save() - return si - - def test_rounding_validation_for_opening_with_inclusive_tax(self): - si = self._create_opening_invoice_with_inclusive_tax() - # 'Round Off for Opening' not set in Company master - # Ledger level validation must be thrown - self.assertRaises(frappe.ValidationError, si.submit) - - def test_ledger_entries_on_opening_invoice_with_rounding_loss_by_inclusive_tax(self): - si = self._create_opening_invoice_with_inclusive_tax() - # 'Round Off for Opening' is set in Company master - self._create_opening_roundoff_account(si.company) - - si.submit() - actual = frappe.db.get_all( - "GL Entry", - filters={"voucher_no": si.name, "is_opening": "Yes", "is_cancelled": False}, - fields=["account", "debit", "credit", "is_opening"], - order_by="account,debit", - ) - expected = [ - {"account": "_Test Account Service Tax - _TC", "debit": 0.0, "credit": 6.9, "is_opening": "Yes"}, - {"account": "Debtors - _TC", "debit": 145.0, "credit": 0.0, "is_opening": "Yes"}, - {"account": "Round Off for Opening - _TC", "debit": 0.0, "credit": 0.01, "is_opening": "Yes"}, - {"account": "Temporary Opening - _TC", "debit": 0.0, "credit": 138.09, "is_opening": "Yes"}, - ] - self.assertEqual(len(actual), 4) - self.assertEqual(expected, actual) - @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 @@ -3868,15 +3761,15 @@ def check_gl_entries(doc, voucher_no, expected_gle, posting_date): self.assertEqual(jv[0], si.grand_total) -def set_advance_flag(company, flag, default_account): - frappe.db.set_value( - "Company", - company, - { - "book_advance_payments_in_separate_party_account": flag, - "default_advance_received_account": default_account, - }, ->>>>>>> 740a04a704 (test: add unit test for common party with foreign currency) +def check_gl_entries(doc, voucher_no, expected_gle, posting_date): + gl_entries = frappe.db.sql( + """select account, debit, credit, posting_date + from `tabGL Entry` + where voucher_type='Sales Invoice' and voucher_no=%s and posting_date > %s + and is_cancelled = 0 + order by posting_date asc, account asc""", + (voucher_no, posting_date), + as_dict=1, ) for i, gle in enumerate(gl_entries):