diff --git a/erpnext/accounts/doctype/exchange_rate_revaluation/test_exchange_rate_revaluation.py b/erpnext/accounts/doctype/exchange_rate_revaluation/test_exchange_rate_revaluation.py index 15d030b45d5..a132ff30cf4 100644 --- a/erpnext/accounts/doctype/exchange_rate_revaluation/test_exchange_rate_revaluation.py +++ b/erpnext/accounts/doctype/exchange_rate_revaluation/test_exchange_rate_revaluation.py @@ -130,7 +130,8 @@ class TestExchangeRateRevaluation(AccountsTestMixin, FrappeTestCase): err = err.save().submit() # Create JV for ERR - self.assertTrue(err.check_journal_and_reversal()) + ret = err.check_journal_and_reversal() + self.assertFalse(ret.get("journals_posted")) err_journals = err.make_jv_entries() je = frappe.get_doc("Journal Entry", err_journals.get("zero_balance_jv")) je = je.submit() @@ -213,7 +214,8 @@ class TestExchangeRateRevaluation(AccountsTestMixin, FrappeTestCase): err = err.save().submit() # Create JV for ERR - self.assertTrue(err.check_journal_and_reversal()) + ret = err.check_journal_and_reversal() + self.assertFalse(ret.get("journals_posted")) err_journals = err.make_jv_entries() je = frappe.get_doc("Journal Entry", err_journals.get("zero_balance_jv")) je = je.submit() @@ -287,3 +289,148 @@ class TestExchangeRateRevaluation(AccountsTestMixin, FrappeTestCase): for key, _val in expected_data.items(): self.assertEqual(expected_data.get(key), account_details.get(key)) +<<<<<<< HEAD +======= + + @ERPNextTestSuite.change_settings( + "Accounts Settings", + {"allow_multi_currency_invoices_against_single_party_account": 1, "allow_stale": 0}, + ) + def test_05_revaluation_journal_reversal(self): + """ + Test reversing of revaluation journals + """ + si = create_sales_invoice( + item=self.item, + company=self.company, + customer=self.customer, + debit_to=self.debtors_usd, + posting_date=today(), + parent_cost_center=self.cost_center, + cost_center=self.cost_center, + rate=100, + price_list_rate=100, + do_not_submit=1, + ) + si.currency = "USD" + si.conversion_rate = 80 + si.save().submit() + + err = frappe.new_doc("Exchange Rate Revaluation") + err.company = self.company + err.posting_date = today() + err.fetch_and_calculate_accounts_data() + self.assertEqual(len(err.accounts), 1) + err.save().submit() + + gain_loss_account = err.get_for_unrealized_gain_loss_account() + usd_account = err.accounts[0].account + old_balance = err.accounts[0].balance_in_base_currency + new_balance = err.accounts[0].new_balance_in_base_currency + total_gain_loss = err.total_gain_loss + + # Create JV for ERR + ret = err.check_journal_and_reversal() + self.assertFalse(ret.get("journals_posted")) + err_journals = err.make_jv_entries() + je = frappe.get_doc("Journal Entry", err_journals.get("revaluation_jv")) + je = je.submit() + + je.reload() + self.assertEqual(je.voucher_type, "Exchange Rate Revaluation") + self.assertEqual(len(je.accounts), 3) + expected = [ + (usd_account, new_balance, 0.0, 100.0, 0.0), + (usd_account, 0.0, old_balance, 0.0, 100.0), + (gain_loss_account, 0.0, total_gain_loss, 0.0, total_gain_loss), + ] + actual = [] + for acc in je.accounts: + actual.append( + ( + acc.account, + acc.debit, + acc.credit, + acc.debit_in_account_currency, + acc.credit_in_account_currency, + ) + ) + self.assertEqual(expected, actual) + + # Assert reversals are not posted + ret = err.check_journal_and_reversal() + self.assertTrue(ret.get("journals_posted")) + self.assertFalse(ret.get("reversals_posted")) + + err.make_reverse_journal() + ret = err.check_journal_and_reversal() + self.assertTrue(ret.get("journals_posted")) + self.assertTrue(ret.get("reversals_posted")) + + reverse_jv = frappe.db.get_all( + "Journal Entry", filters={"reversal_of": err_journals.get("revaluation_jv")}, pluck="name" + ) + self.assertIsNotNone(reverse_jv) + + +class TestExchangeRateRevaluationValidation(ERPNextTestSuite): + """Validation and gain/loss calculation paths, exercised on the document directly + so they don't need the multi-currency GL setup the integration tests above build.""" + + def setUp(self): + frappe.set_user("Administrator") + self.company = "_Test Company" + + def _revaluation_with_rows(self, rows, rounding_loss_allowance=0.05): + doc = frappe.new_doc("Exchange Rate Revaluation") + doc.company = self.company + doc.posting_date = today() + doc.rounding_loss_allowance = rounding_loss_allowance + for row in rows: + doc.append("accounts", row) + return doc + + def test_rounding_loss_allowance_must_be_between_0_and_1(self): + for bad in (-0.1, 1, 1.5): + doc = self._revaluation_with_rows([], rounding_loss_allowance=bad) + self.assertRaises(frappe.ValidationError, doc.validate) + # values inside [0, 1) are accepted, at the lower bound and mid-range + for good in (0.0, 0.5): + self._revaluation_with_rows([], rounding_loss_allowance=good).validate() + + def test_gain_loss_computed_and_split_by_zero_balance(self): + doc = self._revaluation_with_rows( + [ + # open (unbooked) row: base balance moved 1000 -> 1100, a 100 gain + {"zero_balance": 0, "balance_in_base_currency": 1000, "new_balance_in_base_currency": 1100}, + # already-settled (zero_balance) row carries a booked loss of 40 + {"zero_balance": 1, "gain_loss": -40}, + ] + ) + doc.validate() + + # gain_loss is derived only for open rows; the zero-balance row keeps its value + self.assertEqual(doc.accounts[0].gain_loss, 100) + self.assertEqual(doc.gain_loss_unbooked, 100) + self.assertEqual(doc.gain_loss_booked, -40) + self.assertEqual(doc.total_gain_loss, 60) + + def test_before_submit_drops_rows_without_gain_loss(self): + doc = self._revaluation_with_rows( + [ + {"zero_balance": 0, "balance_in_base_currency": 1000, "new_balance_in_base_currency": 1100}, + {"zero_balance": 0, "balance_in_base_currency": 500, "new_balance_in_base_currency": 500}, + ] + ) + doc.validate() # second row nets to a 0 gain_loss + doc.remove_accounts_without_gain_loss() + self.assertEqual(len(doc.accounts), 1) + self.assertEqual(doc.accounts[0].gain_loss, 100) + + def test_before_submit_requires_at_least_one_gain_loss_row(self): + doc = self._revaluation_with_rows( + [{"zero_balance": 0, "balance_in_base_currency": 500, "new_balance_in_base_currency": 500}] + ) + doc.validate() + self.assertRaises(frappe.ValidationError, doc.remove_accounts_without_gain_loss) +>>>>>>> 65775e59a1 (refactor(test): for reverse journals as well)