import base64 from .common import TestBankRecWidgetCommon from odoo import Command, fields from odoo.exceptions import ValidationError from odoo.tests import tagged from odoo.tests.common import Like @tagged('post_install', '-at_install') class TestAccountBankStatement(TestBankRecWidgetCommon): @classmethod def setUpClass(cls): super().setUpClass() cls.company_data_2 = cls.setup_other_company() cls.account_revenue_1 = cls.company_data['default_account_revenue'] cls.account_revenue_1.reconcile = True cls.early_payment_term = cls.env['account.payment.term'].create({ 'name': "Early_payment_term", 'company_id': cls.company_data['company'].id, 'discount_percentage': 10, 'discount_days': 10, 'early_discount': True, 'line_ids': [ Command.create({ 'value': 'percent', 'value_amount': 100, 'nb_days': 20, }), ], }) cls.default_tax = cls.env['account.tax'].create({ 'name': 'default_tax', 'amount_type': 'fixed', 'amount': 10.0, }) cls.default_tax_account = cls.env['account.account'].create({ 'name': 'account with default tax', 'code': '101010', 'tax_ids': [Command.set(cls.default_tax.ids)], }) def _create_and_post_payment(self, amount=100, memo=None, post=True, **kwargs): payment = self.env['account.payment'].create({ 'payment_type': 'inbound', 'payment_method_id': self.env.ref('account.account_payment_method_manual_in').id, 'partner_type': 'customer', 'partner_id': self.partner_a.id, 'amount': amount, 'journal_id': self.company_data['default_journal_bank'].id, 'memo': memo, **kwargs, }) if post: payment.action_post() return payment def test_set_line_bank_statement_line_multiple_move_lines(self): """Test setting multiple move lines on a statement line""" statement_line = self._create_st_line(amount=150, update_create_date=False) self.assertEqual(len(statement_line.move_id.line_ids), 2) move_line_1 = self._create_invoice_line('out_invoice', invoice_line_ids=[{'price_unit': 100.0}]) move_line_2 = self._create_invoice_line('out_invoice', invoice_line_ids=[{'price_unit': 50.0}]) statement_line.set_line_bank_statement_line([move_line_1.id, move_line_2.id]) self.assertEqual(len(statement_line.move_id.line_ids), 3) self.assertNotEqual(statement_line.move_id.line_ids[-1].account_id, statement_line.journal_id.suspense_account_id) def test_internal_transfer_branch_reconcile_model(self): """ Test that an internal transfer between a branch and its parent company can be completed without raising User Errors, even when the branch line was processed by a branch-only reconciliation model. """ company = self.company_data['company'] branch = self.env['res.company'].create({ 'name': "Branch Co", 'parent_id': company.id, 'account_journal_suspense_account_id': company.account_journal_suspense_account_id.id, }) self.env.user.company_ids = [Command.link(branch.id)] branch_bank_journal = self.env['account.journal'].with_company(branch).create({ 'name': "Bank Branch", 'type': 'bank', 'code': 'BNKBR', 'company_id': branch.id, }) reconcile_model = self.env['account.reconcile.model'].with_company(branch).create({ 'name': "Internal transfer", 'company_id': branch.id, 'line_ids': [Command.create({ 'account_id': company.transfer_account_id.id, 'amount_type': 'percentage', 'amount_string': '100', 'label': "Internal transfer", })], }) # Branch statement line reconciled through the branch reconcile model. branch_st_line = self._create_st_line( amount=-100.0, journal_id=branch_bank_journal.id, update_create_date=False, ) reconcile_model.with_company(branch).trigger_reconciliation_model(branch_st_line.ids) transfer_line = branch_st_line.move_id.line_ids.filtered( lambda line: line.account_id == company.transfer_account_id and not line.reconciled ) self.assertTrue(branch_st_line.is_reconciled) self.assertEqual(transfer_line.reconcile_model_id, reconcile_model) # Parent company statement line reconciled against the branch transfer line (internal transfer). company_st_line = self._create_st_line(amount=100.0, update_create_date=False) company_st_line.set_line_bank_statement_line(transfer_line.ids) self.assertTrue(company_st_line.is_reconciled) new_transfer_line = company_st_line.move_id.line_ids.filtered( lambda line: line.account_id == company.transfer_account_id ) self.assertFalse(new_transfer_line.reconcile_model_id) def test_set_line_bank_statement_line_with_open_balance(self): """Test that an open balance entry is created when the move lines don't fully cover the statement balance.""" statement_line = self._create_st_line(amount=200, update_create_date=False) self.assertEqual(len(statement_line.move_id.line_ids), 2) # Create a move line with only 150 move_line = self._create_invoice_line('out_invoice', invoice_line_ids=[{'price_unit': 150.0}]) statement_line.set_line_bank_statement_line(move_line.id) # Expecting 3 lines: statement line (200) + move line (150) + suspense line self.assertRecordValues( statement_line.move_id.line_ids, [ { 'account_id': statement_line.journal_id.default_account_id.id, 'debit': 200.0, 'credit': 0.0, }, { 'account_id': move_line.account_id.id, 'debit': 0.0, 'credit': 150.0, }, { 'account_id': statement_line.journal_id.suspense_account_id.id, 'debit': 0.0, 'credit': 50.0, }, ] ) def test_set_line_bank_statement_line_excess_payment(self): """Test handling when move lines exceed the statement balance.""" statement_line = self._create_st_line(amount=200, update_create_date=False) self.assertEqual(len(statement_line.move_id.line_ids), 2) # Create a move line that exceeds the statement amount move_line = self._create_invoice_line('out_invoice', invoice_line_ids=[{'price_unit': 250.0}]) statement_line.set_line_bank_statement_line(move_line.id) # Expecting 2 lines: original + move line self.assertRecordValues( statement_line.move_id.line_ids, [ { "account_id": statement_line.journal_id.default_account_id.id, "debit": 200.0, "credit": 0.0, }, { "account_id": move_line.account_id.id, "debit": 0.0, "credit": 200.0, }, ], ) # Ensure no suspense account was created (since there's an overpayment) suspense_line = statement_line.move_id.line_ids.filtered( lambda line: line.account_id == statement_line.journal_id.suspense_account_id ) self.assertEqual(len(suspense_line), 0) def test_set_line_bank_statement_line_excess_payment_negative(self): """Test handling when move lines exceed the statement negative balance.""" statement_line = self._create_st_line(amount=-200, update_create_date=False) self.assertEqual(len(statement_line.move_id.line_ids), 2) # Create a move line that exceeds the statement amount move_line = self._create_invoice_line('in_invoice', invoice_line_ids=[{'price_unit': 250.0}]) statement_line.set_line_bank_statement_line(move_line.id) # Expecting 2 lines: original + move line self.assertRecordValues( statement_line.move_id.line_ids, [ { "account_id": statement_line.journal_id.default_account_id.id, "debit": 0.0, "credit": 200.0, }, { "account_id": move_line.account_id.id, "debit": 200.0, "credit": 0.0, }, ], ) # Ensure no suspense account was created (since there's an overpayment) suspense_line = statement_line.move_id.line_ids.filtered( lambda line: line.account_id == statement_line.journal_id.suspense_account_id ) self.assertEqual(len(suspense_line), 0) def test_delete_reconciled_line_with_suspense(self): """Test removing a move line when a suspense account exists""" statement_line = self._create_st_line(amount=200, update_create_date=False) move_line_1 = self._create_invoice_line('out_invoice', invoice_line_ids=[{'price_unit': 150.0}]) move_line_2 = self._create_invoice_line('out_invoice', invoice_line_ids=[{'price_unit': 50.0}]) statement_line.set_line_bank_statement_line([move_line_1.id, move_line_2.id]) self.assertRecordValues( statement_line.move_id.line_ids, [ { 'account_id': statement_line.journal_id.default_account_id.id, 'debit': 200.0, 'credit': 0.0, }, { 'account_id': move_line_1.account_id.id, 'debit': 0.0, 'credit': 150.0, }, { 'account_id': move_line_2.account_id.id, 'debit': 0.0, 'credit': 50.0, }, ] ) _liquidity_lines, _suspense_lines, other_lines = statement_line._seek_for_lines() # Now remove the move line with 150 and 50 separately for id in other_lines.ids: statement_line.delete_reconciled_line(id) # Check that the counterpart suspense line is adjusted self.assertRecordValues( statement_line.move_id.line_ids, [ { 'account_id': statement_line.journal_id.default_account_id.id, 'debit': 200.0, 'credit': 0.0, }, { 'account_id': statement_line.journal_id.suspense_account_id.id, 'debit': 0.0, 'credit': 200.0, }, ] ) def test_reconciliation_base_case_invoice(self): st_line = self._create_st_line(1000.0, update_create_date=False) self.assertRecordValues(st_line.line_ids, [ {'account_id': st_line.journal_id.default_account_id.id, 'amount_currency': 1000.0, 'currency_id': self.company_data['currency'].id, 'balance': 1000.0, 'reconciled': False}, {'account_id': st_line.journal_id.suspense_account_id.id, 'amount_currency': -1000.0, 'currency_id': self.company_data['currency'].id, 'balance': -1000.0, 'reconciled': False}, ]) inv_line = self._create_invoice_line( 'out_invoice', invoice_line_ids=[{'price_unit': 1000.0, 'tax_ids': []}], ) st_line.set_line_bank_statement_line(inv_line.id) self.assertRecordValues(st_line.line_ids, [ {'account_id': st_line.journal_id.default_account_id.id, 'amount_currency': 1000.0, 'currency_id': self.company_data['currency'].id, 'balance': 1000.0, 'reconciled': False}, {'account_id': self.partner_a.property_account_receivable_id.id, 'amount_currency': -1000.0, 'currency_id': self.company_data['currency'].id, 'balance': -1000.0, 'reconciled': True}, ]) def test_reconciliation_base_case_bill(self): st_line = self._create_st_line(-1000.0, update_create_date=False) self.assertRecordValues(st_line.line_ids, [ {'account_id': st_line.journal_id.default_account_id.id, 'amount_currency': -1000.0, 'currency_id': self.company_data['currency'].id, 'balance': -1000.0, 'reconciled': False}, {'account_id': st_line.journal_id.suspense_account_id.id, 'amount_currency': 1000.0, 'currency_id': self.company_data['currency'].id, 'balance': 1000.0, 'reconciled': False}, ]) inv_line = self._create_invoice_line( 'in_invoice', invoice_line_ids=[{'price_unit': 1000.0, 'tax_ids': []}], ) st_line.set_line_bank_statement_line(inv_line.id) self.assertRecordValues(st_line.line_ids, [ {'account_id': st_line.journal_id.default_account_id.id, 'amount_currency': -1000.0, 'currency_id': self.company_data['currency'].id, 'balance': -1000.0, 'reconciled': False}, {'account_id': self.partner_a.property_account_payable_id.id, 'amount_currency': 1000.0, 'currency_id': self.company_data['currency'].id, 'balance': 1000.0, 'reconciled': True}, ]) def test_reconciliation_with_unique_label_memo_match(self): """Test reconciliation when a unique memo fragment matches the label and amount also match.""" payment = self._create_and_post_payment(amount=100, memo="pay_AretqwwXerereE") statement_line = self._create_st_line(amount=100, payment_ref="pay_AretqwwXerereE", update_create_date=False) statement_line._try_auto_reconcile_statement_lines() self.assertRecordValues(statement_line.line_ids, [ {'account_id': statement_line.journal_id.default_account_id.id, 'amount_currency': 100.0, 'currency_id': self.company_data['currency'].id, 'balance': 100.0, 'reconciled': False}, {'account_id': payment.outstanding_account_id.id, 'amount_currency': -100.0, 'currency_id': self.company_data['currency'].id, 'balance': -100.0, 'reconciled': True}, ]) def test_reconciliation_with_unique_label_memo_match_and_negative_amounts(self): """Make sure the behaviour is the same if the statement line is encoded with negative amounts.""" payment = self._create_and_post_payment(amount=100, memo="pay_AretqwwXerereE") statement_line = self._create_st_line(amount=-100, payment_ref="pay_AretqwwXerereE", update_create_date=False) statement_line._try_auto_reconcile_statement_lines() self.assertRecordValues(statement_line.line_ids, [ {'account_id': statement_line.journal_id.default_account_id.id, 'amount_currency': -100.0, 'currency_id': self.company_data['currency'].id, 'balance': -100.0, 'reconciled': False}, {'account_id': payment.destination_account_id.id, 'amount_currency': 100.0, 'currency_id': self.company_data['currency'].id, 'balance': 100.0, 'reconciled': True}, ]) def test_reconciliation_with_unique_label_memo_match_and_other_currency_on_payment(self): """Try to create a payment with a different currency to see if it match.""" payment = self._create_and_post_payment(amount=200, memo="pay_AretqwwXerereE", currency_id=self.other_currency.id) statement_line = self._create_st_line(amount=100, payment_ref="pay_AretqwwXerereE", update_create_date=False) statement_line._try_auto_reconcile_statement_lines() self.assertRecordValues(statement_line.line_ids, [ {'account_id': statement_line.journal_id.default_account_id.id, 'amount_currency': 100.0, 'currency_id': self.company_data['currency'].id, 'balance': 100.0, 'reconciled': False}, {'account_id': payment.outstanding_account_id.id, 'amount_currency': -200.0, 'currency_id': self.other_currency.id, 'balance': -100.0, 'reconciled': True}, ]) def test_reconciliation_with_unique_label_memo_match_and_other_currency_on_payment_and_st_line(self): """Create a payment with foreign currency on both payment and st_line.""" payment = self._create_and_post_payment(amount=200, memo="pay_AretqwwXerereE", currency_id=self.other_currency.id) statement_line = self._create_st_line(amount=100, amount_currency=200, payment_ref="pay_AretqwwXerereE", update_create_date=False, foreign_currency_id=self.other_currency.id) statement_line._try_auto_reconcile_statement_lines() self.assertRecordValues(statement_line.line_ids, [ {'account_id': statement_line.journal_id.default_account_id.id, 'amount_currency': 100.0, 'currency_id': self.company_data['currency'].id, 'balance': 100.0, 'reconciled': False}, {'account_id': payment.outstanding_account_id.id, 'amount_currency': -200.0, 'currency_id': self.other_currency.id, 'balance': -100.0, 'reconciled': True}, ]) def test_multiple_reconcile_with_same_payment(self): """Create a payment, then create 2 st_lines matching the payment.""" payment = self._create_and_post_payment(amount=200, memo="pay_AretqwwXerereE") statement_line_1 = self._create_st_line(amount=100, payment_ref="pay_AretqwwXerereE", update_create_date=False) statement_line_1._try_auto_reconcile_statement_lines() self.assertRecordValues(statement_line_1.line_ids, [ {'account_id': statement_line_1.journal_id.default_account_id.id, 'balance': 100.0, 'reconciled': False}, {'account_id': payment.outstanding_account_id.id, 'balance': -100.0, 'reconciled': True}, ]) # Only 100 is paid on the payment for now, he's not fully reconciled yet self.assertEqual(payment.state, 'in_process') statement_line_2 = self._create_st_line(amount=100, payment_ref="pay_AretqwwXerereE", update_create_date=False) statement_line_2._try_auto_reconcile_statement_lines() self.assertRecordValues(statement_line_2.line_ids, [ {'account_id': statement_line_2.journal_id.default_account_id.id, 'balance': 100.0, 'reconciled': False}, {'account_id': payment.outstanding_account_id.id, 'balance': -100.0, 'reconciled': True}, ]) # Now the payment should be fully reconciled and marked as paid self.assertEqual(payment.state, 'paid') def test_multiple_reconcile_with_foreign_currency(self): """Create a payment, then create 2 st_lines matching the payment.""" payment = self._create_and_post_payment(amount=400, memo="pay_AretqwwXerereE", currency_id=self.other_currency.id) statement_line_1 = self._create_st_line(amount=200, payment_ref="pay_AretqwwXerereE", update_create_date=False) statement_line_1._try_auto_reconcile_statement_lines() self.assertRecordValues(statement_line_1.line_ids, [ {'account_id': statement_line_1.journal_id.default_account_id.id, 'balance': 200.0, 'reconciled': False}, {'account_id': payment.outstanding_account_id.id, 'balance': -200.0, 'reconciled': True}, ]) inv_line = self._create_invoice_line( 'out_invoice', invoice_date='2017-01-01', invoice_line_ids=[{'price_unit': 1000.0}], ) statement_line_2 = self._create_st_line(amount=1000, payment_ref=inv_line.name, update_create_date=False) statement_line_2._try_auto_reconcile_statement_lines() self.assertRecordValues(statement_line_2.line_ids, [ {'account_id': statement_line_2.journal_id.default_account_id.id, 'balance': 1000.0, 'reconciled': False}, {'account_id': inv_line.account_id.id, 'balance': -1000.0, 'reconciled': True}, ]) foreign_inv_line = self._create_invoice_line( 'out_invoice', currency_id=self.other_currency.id, invoice_date='2017-01-01', invoice_line_ids=[{'price_unit': 2000.0}], ) statement_line_3 = self._create_st_line(amount=1000, payment_ref=foreign_inv_line.name, update_create_date=False) statement_line_3._try_auto_reconcile_statement_lines() self.assertRecordValues(statement_line_3.line_ids, [ {'account_id': statement_line_2.journal_id.default_account_id.id, 'balance': 1000.0, 'reconciled': False}, {'account_id': statement_line_2.journal_id.suspense_account_id.id, 'balance': -1000.0, 'reconciled': False}, ]) def test_unreconciliation_base_case_invoice(self): st_line = self._create_st_line(1000.0, update_create_date=False) self.assertRecordValues(st_line.line_ids, [ {'account_id': st_line.journal_id.default_account_id.id, 'amount_currency': 1000.0, 'currency_id': self.company_data['currency'].id, 'balance': 1000.0, 'reconciled': False}, {'account_id': st_line.journal_id.suspense_account_id.id, 'amount_currency': -1000.0, 'currency_id': self.company_data['currency'].id, 'balance': -1000.0, 'reconciled': False}, ]) inv_line = self._create_invoice_line( 'out_invoice', invoice_line_ids=[{'price_unit': 1000.0, 'tax_ids': []}], ) st_line.set_line_bank_statement_line(inv_line.id) self.assertRecordValues(st_line.line_ids, [ {'account_id': st_line.journal_id.default_account_id.id, 'amount_currency': 1000.0, 'currency_id': self.company_data['currency'].id, 'balance': 1000.0, 'reconciled': False}, {'account_id': self.partner_a.property_account_receivable_id.id, 'amount_currency': -1000.0, 'currency_id': self.company_data['currency'].id, 'balance': -1000.0, 'reconciled': True}, ]) line_to_remove = st_line.line_ids[-1] line_to_remove_source = line_to_remove.reconciled_lines_ids self.assertTrue(line_to_remove.matched_debit_ids) self.assertTrue(line_to_remove_source.matched_credit_ids) st_line.delete_reconciled_line(st_line.line_ids[-1].id) # line to remove has been deleted and reco broken self.assertFalse(line_to_remove_source.matched_credit_ids) self.assertRecordValues(st_line.line_ids, [ {'account_id': st_line.journal_id.default_account_id.id, 'amount_currency': 1000.0, 'currency_id': self.company_data['currency'].id, 'balance': 1000.0, 'reconciled': False}, {'account_id': st_line.journal_id.suspense_account_id.id, 'amount_currency': -1000.0, 'currency_id': self.company_data['currency'].id, 'balance': -1000.0, 'reconciled': False}, ]) def test_unreconciliation_base_case_bill(self): st_line = self._create_st_line(-1000.0, update_create_date=False) self.assertRecordValues(st_line.line_ids, [ {'account_id': st_line.journal_id.default_account_id.id, 'amount_currency': -1000.0, 'currency_id': self.company_data['currency'].id, 'balance': -1000.0, 'reconciled': False}, {'account_id': st_line.journal_id.suspense_account_id.id, 'amount_currency': 1000.0, 'currency_id': self.company_data['currency'].id, 'balance': 1000.0, 'reconciled': False}, ]) inv_line = self._create_invoice_line( 'in_invoice', invoice_line_ids=[{'price_unit': 1000.0, 'tax_ids': []}], ) st_line.set_line_bank_statement_line(inv_line.id) self.assertRecordValues(st_line.line_ids, [ {'account_id': st_line.journal_id.default_account_id.id, 'amount_currency': -1000.0, 'currency_id': self.company_data['currency'].id, 'balance': -1000.0, 'reconciled': False}, {'account_id': self.partner_a.property_account_payable_id.id, 'amount_currency': 1000.0, 'currency_id': self.company_data['currency'].id, 'balance': 1000.0, 'reconciled': True}, ]) line_to_remove = st_line.line_ids[-1] line_to_remove_source = line_to_remove.reconciled_lines_ids self.assertTrue(line_to_remove.matched_credit_ids) self.assertTrue(line_to_remove_source.matched_debit_ids) st_line.delete_reconciled_line(st_line.line_ids[-1].id) # line to remove has been deleted and reco broken self.assertFalse(line_to_remove_source.matched_debit_ids) self.assertRecordValues(st_line.line_ids, [ {'account_id': st_line.journal_id.default_account_id.id, 'amount_currency': -1000.0, 'currency_id': self.company_data['currency'].id, 'balance': -1000.0, 'reconciled': False}, {'account_id': st_line.journal_id.suspense_account_id.id, 'amount_currency': 1000.0, 'currency_id': self.company_data['currency'].id, 'balance': 1000.0, 'reconciled': False}, ]) def test_set_account_negative_statement_line(self): st_line = self._create_st_line(-1000.0, update_create_date=False) self.assertRecordValues(st_line.line_ids, [ {'account_id': st_line.journal_id.default_account_id.id, 'amount_currency': -1000.0, 'currency_id': self.company_data['currency'].id, 'balance': -1000.0, 'reconciled': False}, {'account_id': st_line.journal_id.suspense_account_id.id, 'amount_currency': 1000.0, 'currency_id': self.company_data['currency'].id, 'balance': 1000.0, 'reconciled': False}, ]) # Switch to a custom account. account = self.env['account.account'].create({ 'name': "test_validation_using_custom_account", 'code': "424242", 'account_type': "asset_current", }) st_line.set_account_bank_statement_line(st_line.line_ids[-1].id, account.id) self.assertRecordValues(st_line.line_ids, [ {'account_id': st_line.journal_id.default_account_id.id, 'amount_currency': -1000.0, 'currency_id': self.company_data['currency'].id, 'balance': -1000.0, 'reconciled': False}, {'account_id': account.id, 'amount_currency': 1000.0, 'currency_id': self.company_data['currency'].id, 'balance': 1000.0, 'reconciled': False}, ]) def test_validation_changed_default_account(self): st_line = self._create_st_line(1000.0, update_create_date=False) original_journal_account_id = st_line.journal_id.default_account_id self.assertRecordValues(st_line.line_ids, [ {'account_id': original_journal_account_id.id, 'amount_currency': 1000.0, 'currency_id': self.company_data['currency'].id, 'balance': 1000.0, 'reconciled': False}, {'account_id': st_line.journal_id.suspense_account_id.id, 'amount_currency': -1000.0, 'currency_id': self.company_data['currency'].id, 'balance': -1000.0, 'reconciled': False}, ]) # Change the default account of the journal (exceptional case) st_line.journal_id.default_account_id = self.company_data['default_journal_cash'].default_account_id # This will not change for existing ones self.assertRecordValues(st_line.line_ids, [ {'account_id': original_journal_account_id.id, 'amount_currency': 1000.0, 'currency_id': self.company_data['currency'].id, 'balance': 1000.0, 'reconciled': False}, {'account_id': st_line.journal_id.suspense_account_id.id, 'amount_currency': -1000.0, 'currency_id': self.company_data['currency'].id, 'balance': -1000.0, 'reconciled': False}, ]) # But any new statement line should have the new default new_st_line = self._create_st_line(1000.0, update_create_date=False) self.assertRecordValues(new_st_line.line_ids, [ {'account_id': st_line.journal_id.default_account_id.id, 'amount_currency': 1000.0, 'currency_id': self.company_data['currency'].id, 'balance': 1000.0, 'reconciled': False}, {'account_id': new_st_line.journal_id.suspense_account_id.id, 'amount_currency': -1000.0, 'currency_id': self.company_data['currency'].id, 'balance': -1000.0, 'reconciled': False}, ]) def test_manual_edit_basic_case(self): st_line = self._create_st_line(1000.0, update_create_date=False) self.assertRecordValues(st_line.line_ids, [ {'account_id': st_line.journal_id.default_account_id.id, 'amount_currency': 1000.0, 'currency_id': self.company_data['currency'].id, 'balance': 1000.0, 'reconciled': False}, {'account_id': st_line.journal_id.suspense_account_id.id, 'amount_currency': -1000.0, 'currency_id': self.company_data['currency'].id, 'balance': -1000.0, 'reconciled': False}, ]) st_line.set_account_bank_statement_line(st_line.line_ids[-1].id, self.account_revenue_1.id) self.assertRecordValues(st_line.line_ids, [ {'account_id': st_line.journal_id.default_account_id.id, 'amount_currency': 1000.0, 'currency_id': self.company_data['currency'].id, 'balance': 1000.0, 'reconciled': False}, {'account_id': self.account_revenue_1.id, 'amount_currency': -1000.0, 'currency_id': self.company_data['currency'].id, 'balance': -1000.0, 'reconciled': False}, ]) st_line.edit_reconcile_line(st_line.line_ids[-1].id, {'balance': -500, 'amount_currency': -500}) self.assertRecordValues(st_line.line_ids, [ {'account_id': st_line.journal_id.default_account_id.id, 'amount_currency': 1000.0, 'currency_id': self.company_data['currency'].id, 'balance': 1000.0, 'reconciled': False}, {'account_id': self.account_revenue_1.id, 'amount_currency': -500.0, 'currency_id': self.company_data['currency'].id, 'balance': -500.0, 'reconciled': False}, {'account_id': st_line.journal_id.suspense_account_id.id, 'amount_currency': -500.0, 'currency_id': self.company_data['currency'].id, 'balance': -500.0, 'reconciled': False}, ]) def test_manual_edit_change_currency(self): self.account_current_assets_1 = self.company_data['default_account_deferred_expense'] st_line = self._create_st_line(1000.0, update_create_date=False) self.assertRecordValues(st_line.line_ids, [ {'account_id': st_line.journal_id.default_account_id.id, 'amount_currency': 1000.0, 'currency_id': self.company_data['currency'].id, 'balance': 1000.0, 'reconciled': False}, {'account_id': st_line.journal_id.suspense_account_id.id, 'amount_currency': -1000.0, 'currency_id': self.company_data['currency'].id, 'balance': -1000.0, 'reconciled': False}, ]) st_line.set_account_bank_statement_line(st_line.line_ids[-1].id, self.account_current_assets_1.id) self.assertRecordValues(st_line.line_ids, [ {'account_id': st_line.journal_id.default_account_id.id, 'amount_currency': 1000.0, 'currency_id': self.company_data['currency'].id, 'balance': 1000.0, 'reconciled': False}, {'account_id': self.account_current_assets_1.id, 'amount_currency': -1000.0, 'currency_id': self.company_data['currency'].id, 'balance': -1000.0, 'reconciled': False}, ]) st_line.edit_reconcile_line(st_line.line_ids[1].id, {'balance': -500, 'amount_currency': -500, 'currency_id': self.other_currency.id}) self.assertRecordValues(st_line.line_ids, [ {'account_id': st_line.journal_id.default_account_id.id, 'amount_currency': 1000.0, 'currency_id': self.company_data['currency'].id, 'balance': 1000.0, 'reconciled': False}, {'account_id': self.account_current_assets_1.id, 'amount_currency': -500.0, 'currency_id': self.other_currency.id, 'balance': -500.0, 'reconciled': False}, {'account_id': st_line.journal_id.suspense_account_id.id, 'amount_currency': -500.0, 'currency_id': self.company_data['currency'].id, 'balance': -500.0, 'reconciled': False}, ]) def test_res_partner_bank_find_create_multi_account(self): """ Make sure that we can save multiple bank accounts for a partner. """ partner = self.env['res.partner'].create({'name': "Zitycard"}) for acc_number in ("123456789", "123456780"): st_line = self._create_st_line(100.0, account_number=acc_number, update_create_date=False) inv_line = self._create_invoice_line( 'out_invoice', partner_id=partner.id, invoice_line_ids=[{'price_unit': 100.0, 'tax_ids': []}], ) st_line.set_line_bank_statement_line(inv_line.id) bank_accounts = self.env['res.partner.bank'].sudo().with_context(active_test=False).search([ ('partner_id', '=', partner.id), ]) self.assertEqual(len(bank_accounts), 2, "Second bank account was not registered!") def test_early_payment_discount_basic_case(self): st_line = self._create_st_line(90.0, date='2017-01-10', update_create_date=False) early_pay_acc = self.env.company.account_journal_early_pay_discount_loss_account_id inv_line_with_epd = self._create_invoice_line( 'out_invoice', date='2017-01-04', invoice_payment_term_id=self.early_payment_term.id, invoice_line_ids=[{'price_unit': 100.0}], ) st_line.set_line_bank_statement_line(inv_line_with_epd.id) self.assertRecordValues(st_line.line_ids, [ {'account_id': st_line.journal_id.default_account_id.id, 'amount_currency': 90.0, 'currency_id': self.company_data['currency'].id, 'balance': 90.0, 'reconciled': False}, {'account_id': inv_line_with_epd.account_id.id, 'amount_currency': -100.0, 'currency_id': self.company_data['currency'].id, 'balance': -100.0, 'reconciled': True}, {'account_id': early_pay_acc.id, 'amount_currency': 10.0, 'currency_id': self.company_data['currency'].id, 'balance': 10.0, 'reconciled': False}, ]) def test_early_payment_discount_basic_case_after_date(self): inv_line_with_epd = self._create_invoice_line( 'out_invoice', date='2017-01-10', invoice_payment_term_id=self.early_payment_term.id, invoice_line_ids=[{'price_unit': 100.0}], ) st_line = self._create_st_line(90.0, date='2017-02-10', update_create_date=False) st_line.set_line_bank_statement_line(inv_line_with_epd.id) self.assertRecordValues(st_line.line_ids, [ {'account_id': st_line.journal_id.default_account_id.id, 'amount_currency': 90.0, 'currency_id': self.company_data['currency'].id, 'balance': 90.0, 'reconciled': False}, {'account_id': inv_line_with_epd.account_id.id, 'amount_currency': -90.0, 'currency_id': self.company_data['currency'].id, 'balance': -90.0, 'reconciled': True}, ]) def test_early_payment_discount_basic_case_smaller_amount(self): early_pay_acc = self.env.company.account_journal_early_pay_discount_loss_account_id st_line = self._create_st_line(100.0, date='2017-01-10', update_create_date=False) inv_line_with_epd = self._create_invoice_line( 'out_invoice', date='2017-01-10', invoice_payment_term_id=self.early_payment_term.id, invoice_line_ids=[{'price_unit': 90.0}], ) st_line.set_line_bank_statement_line(inv_line_with_epd.id) self.assertRecordValues(st_line.line_ids, [ {'account_id': st_line.journal_id.default_account_id.id, 'amount_currency': 100.0, 'currency_id': self.company_data['currency'].id, 'balance': 100.0, 'reconciled': False}, {'account_id': inv_line_with_epd.account_id.id, 'amount_currency': -90.0, 'currency_id': self.company_data['currency'].id, 'balance': -90.0, 'reconciled': True}, {'account_id': early_pay_acc.id, 'amount_currency': 9.0, 'currency_id': self.company_data['currency'].id, 'balance': 9.0, 'reconciled': False}, {'account_id': st_line.journal_id.suspense_account_id.id, 'amount_currency': -19.0, 'currency_id': self.company_data['currency'].id, 'balance': -19.0, 'reconciled': False}, ]) def test_early_payment_discount_batch_reconcile_merges_tax_lines(self): """Batch-reconciling invoices sharing a tax with an EPD keeps one discount base line per invoice but merges their tax lines into one, so the tax report base is not doubled.""" self.early_payment_term.early_pay_discount_computation = 'included' invoice_lines = self.env['account.move.line'] for price in (100.0, 200.0): invoice_lines += self._create_invoice_line( 'out_invoice', date='2017-01-04', invoice_payment_term_id=self.early_payment_term.id, invoice_line_ids=[{'price_unit': price, 'tax_ids': [Command.set(self.tax_sale_a.ids)]}], ) st_line = self._create_st_line(310.5, date='2017-01-10', update_create_date=False) st_line.set_line_bank_statement_line(invoice_lines.ids) tax_acc = self.company_data['default_account_tax_sale'] early_pay_acc = self.env.company.account_journal_early_pay_discount_loss_account_id self.assertRecordValues(st_line.line_ids.sorted('balance'), [ {'account_id': invoice_lines.account_id.id, 'balance': -230.0, 'reconciled': True}, {'account_id': invoice_lines.account_id.id, 'balance': -115.0, 'reconciled': True}, {'account_id': tax_acc.id, 'balance': 4.5, 'reconciled': False}, {'account_id': early_pay_acc.id, 'balance': 10.0, 'reconciled': False}, {'account_id': early_pay_acc.id, 'balance': 20.0, 'reconciled': False}, {'account_id': st_line.journal_id.default_account_id.id, 'balance': 310.5, 'reconciled': False}, ]) # Removing the larger discount recomputes the merged tax line from 4.5 to 1.5. epd_base_line = st_line.line_ids.filtered(lambda l: l.account_id == early_pay_acc and l.tax_ids).sorted('balance')[-1] st_line.delete_reconciled_line(epd_base_line.id) epd_tax_line = st_line.line_ids.filtered(lambda l: l.tax_line_id == self.tax_sale_a) self.assertEqual(epd_tax_line.balance, 1.5) def test_exchange_diff_basic_case(self): self.other_currency.rate_ids = [Command.create({ 'rate': 1, 'name': '2017-01-03', })] st_line = self._create_st_line( 100.0, date='2017-01-05', update_create_date=False, ) # 100.0 curr2 == 50.0 comp_curr inv_line = self._create_invoice_line( 'out_invoice', currency_id=self.other_currency.id, invoice_date='2017-01-01', invoice_line_ids=[{'price_unit': 100.0}], ) st_line.set_line_bank_statement_line(inv_line.id) self.assertRecordValues(st_line.line_ids, [ {'account_id': st_line.journal_id.default_account_id.id, 'amount_currency': 100.0, 'currency_id': self.company_data['currency'].id, 'balance': 100.0, 'reconciled': False}, {'account_id': inv_line.account_id.id, 'amount_currency': -100.0, 'currency_id': self.other_currency.id, 'balance': -100.0, 'reconciled': True}, ]) exchange_move = st_line.line_ids[1].matched_debit_ids.exchange_move_id self.assertRecordValues(exchange_move, [{ 'date': fields.Date.from_string('2017-01-31'), 'amount_total_signed': 50, }]) def test_exchange_diff_multiple_lines(self): self.other_currency.rate_ids = [Command.create({ 'rate': 1, 'name': '2017-01-03', })] st_line = self._create_st_line( 200.0, date='2017-01-05', update_create_date=False, ) inv_line_1 = self._create_invoice_line( 'out_invoice', invoice_date='2017-01-01', invoice_line_ids=[{'price_unit': 100.0}], ) # 100.0 curr2 == 50.0 comp_curr inv_line_2 = self._create_invoice_line( 'out_invoice', currency_id=self.other_currency.id, invoice_date='2017-01-01', invoice_line_ids=[{'price_unit': 100.0}], ) st_line.set_line_bank_statement_line((inv_line_1 + inv_line_2).ids) self.assertRecordValues(st_line.line_ids, [ {'account_id': st_line.journal_id.default_account_id.id, 'amount_currency': 200.0, 'currency_id': self.company_data['currency'].id, 'balance': 200.0, 'reconciled': False}, {'account_id': inv_line_1.account_id.id, 'amount_currency': -100.0, 'currency_id': self.company_data['currency'].id, 'balance': -100.0, 'reconciled': True}, {'account_id': inv_line_2.account_id.id, 'amount_currency': -100.0, 'currency_id': self.other_currency.id, 'balance': -100.0, 'reconciled': True}, ]) self.assertFalse(st_line.line_ids[1].matched_debit_ids.exchange_move_id) exchange_move = st_line.line_ids[2].matched_debit_ids.exchange_move_id self.assertRecordValues(exchange_move, [{ 'date': fields.Date.from_string('2017-01-31'), 'amount_total_signed': 50, }]) def test_validation_caba_tax_account(self): """ Cash basis taxes usually put their tax lines on a transition account, and the cash basis entries then move those amounts to the regular tax accounts. When using a cash basis tax in the bank reconciliation widget, their won't be any cash basis entry and the lines will directly be exigible, so we want to use the final tax account directly. """ tax_account = self.company_data['default_account_tax_sale'] tax_account.reconcile = True caba_tax = self.env['account.tax'].create({ 'name': "CABA", 'amount_type': 'percent', 'amount': 20.0, 'tax_exigibility': 'on_payment', 'cash_basis_transition_account_id': self.safe_copy(tax_account).id, 'invoice_repartition_line_ids': [ Command.create({ 'repartition_type': 'base', }), Command.create({ 'repartition_type': 'tax', 'account_id': tax_account.id, }), ], 'refund_repartition_line_ids': [ Command.create({ 'repartition_type': 'base', }), Command.create({ 'repartition_type': 'tax', 'account_id': tax_account.id, }), ], }) st_line = self._create_st_line(120.0, update_create_date=False) st_line.set_account_bank_statement_line(st_line.line_ids[-1].id, self.account_revenue_1.id) self.assertRecordValues(st_line.line_ids, [ {'account_id': st_line.journal_id.default_account_id.id, 'amount_currency': 120.0, 'currency_id': self.company_data['currency'].id, 'balance': 120.0, 'reconciled': False}, {'account_id': self.account_revenue_1.id, 'amount_currency': -120, 'currency_id': self.company_data['currency'].id, 'balance': -120, 'reconciled': False}, ]) st_line.edit_reconcile_line(st_line.line_ids[-1].id, { 'tax_ids': [Command.link(caba_tax.id)], }) self.assertRecordValues(st_line.line_ids, [ {'account_id': st_line.journal_id.default_account_id.id, 'tax_ids': [], 'tax_line_id': False, 'amount_currency': 120.0, 'currency_id': self.company_data['currency'].id, 'balance': 120.0, 'reconciled': False}, {'account_id': self.account_revenue_1.id, 'tax_ids': caba_tax.ids, 'tax_line_id': False, 'amount_currency': -100.0, 'currency_id': self.company_data['currency'].id, 'balance': -100.0, 'reconciled': False}, {'account_id': tax_account.id, 'tax_ids': [], 'tax_line_id': caba_tax.id, 'amount_currency': -20.0, 'currency_id': self.company_data['currency'].id, 'balance': -20.0, 'reconciled': False}, ]) def test_multicurrency_flows_all_same_currency(self): st_line = self._create_st_line(250.0, update_create_date=False) inv_line = self._create_invoice_line('out_invoice', invoice_line_ids=[{'price_unit': 100.0}]) st_line.set_line_bank_statement_line(inv_line.id) self.assertRecordValues(st_line.line_ids, [ {'account_id': st_line.journal_id.default_account_id.id, 'amount_currency': 250.0, 'currency_id': self.company_data['currency'].id, 'balance': 250.0, 'reconciled': False}, {'account_id': self.partner_a.property_account_receivable_id.id, 'amount_currency': -100.0, 'currency_id': self.company_data['currency'].id, 'balance': -100.0, 'reconciled': True}, {'account_id': st_line.journal_id.suspense_account_id.id, 'amount_currency': -150.0, 'currency_id': self.company_data['currency'].id, 'balance': -150.0, 'reconciled': False}, ]) st_line = self._create_st_line(250.0, update_create_date=False) inv_line = self._create_invoice_line('out_invoice', invoice_line_ids=[{'price_unit': 500.0}]) st_line.set_line_bank_statement_line(inv_line.id) self.assertRecordValues(st_line.line_ids, [ {'account_id': st_line.journal_id.default_account_id.id, 'amount_currency': 250.0, 'currency_id': self.company_data['currency'].id, 'balance': 250.0, 'reconciled': False}, {'account_id': self.partner_a.property_account_receivable_id.id, 'amount_currency': -250.0, 'currency_id': self.company_data['currency'].id, 'balance': -250.0, 'reconciled': True}, ]) def test_multicurrency_flows_currency_transaction_different(self): st_line = self._create_st_line(250.0, update_create_date=False) inv_line = self._create_invoice_line('out_invoice', invoice_line_ids=[{'price_unit': 100.0}], currency_id=self.other_currency.id) st_line.set_line_bank_statement_line(inv_line.id) self.assertRecordValues(st_line.line_ids, [ {'account_id': st_line.journal_id.default_account_id.id, 'amount_currency': 250.0, 'currency_id': self.company_data['currency'].id, 'balance': 250.0, 'reconciled': False}, {'account_id': self.partner_a.property_account_receivable_id.id, 'amount_currency': -100.0, 'currency_id': self.other_currency.id, 'balance': -50.0, 'reconciled': True}, {'account_id': st_line.journal_id.suspense_account_id.id, 'amount_currency': -200.0, 'currency_id': self.company_data['currency'].id, 'balance': -200.0, 'reconciled': False}, ]) st_line = self._create_st_line(250.0, update_create_date=False) inv_line = self._create_invoice_line('out_invoice', invoice_line_ids=[{'price_unit': 500.0}], currency_id=self.other_currency.id) st_line.set_line_bank_statement_line(inv_line.id) self.assertRecordValues(st_line.line_ids, [ {'account_id': st_line.journal_id.default_account_id.id, 'amount_currency': 250.0, 'currency_id': self.company_data['currency'].id, 'balance': 250.0, 'reconciled': False}, {'account_id': self.partner_a.property_account_receivable_id.id, 'amount_currency': -500.0, 'currency_id': self.other_currency.id, 'balance': -250.0, 'reconciled': True}, ]) def test_multicurrency_flows_currency_journal_different_currency(self): new_journal = self.env['account.journal'].create({ 'name': 'test', 'code': 'TBNK', 'type': 'bank', 'currency_id': self.other_currency.id, }) st_line = self._create_st_line(250.0, journal_id=new_journal.id, update_create_date=False) inv_line = self._create_invoice_line('out_invoice', invoice_line_ids=[{'price_unit': 100.0}]) st_line.set_line_bank_statement_line(inv_line.id) self.assertRecordValues(st_line.line_ids, [ {'account_id': st_line.journal_id.default_account_id.id, 'amount_currency': 250.0, 'currency_id': self.other_currency.id, 'balance': 125.0, 'reconciled': False}, {'account_id': self.partner_a.property_account_receivable_id.id, 'amount_currency': -100.0, 'currency_id': self.company_data['currency'].id, 'balance': -100.0, 'reconciled': True}, {'account_id': st_line.journal_id.suspense_account_id.id, 'amount_currency': -50, 'currency_id': self.other_currency.id, 'balance': -25.0, 'reconciled': False}, ]) st_line = self._create_st_line(250.0, journal_id=new_journal.id, update_create_date=False) inv_line = self._create_invoice_line('out_invoice', invoice_line_ids=[{'price_unit': 500.0}]) st_line.set_line_bank_statement_line(inv_line.id) self.assertRecordValues(st_line.line_ids, [ {'account_id': st_line.journal_id.default_account_id.id, 'amount_currency': 250.0, 'currency_id': self.other_currency.id, 'balance': 125.0, 'reconciled': False}, {'account_id': self.partner_a.property_account_receivable_id.id, 'amount_currency': -125.0, 'currency_id': self.company_data['currency'].id, 'balance': -125.0, 'reconciled': True}, ]) def test_multicurrency_flows_journal_and_transaction_in_other_currency_than_company(self): new_journal = self.env['account.journal'].create({ 'name': 'test', 'code': 'TBNK', 'type': 'bank', 'currency_id': self.other_currency.id, }) st_line = self._create_st_line(250.0, journal_id=new_journal.id, update_create_date=False) inv_line = self._create_invoice_line('out_invoice', invoice_line_ids=[{'price_unit': 100.0}], currency_id=self.other_currency.id) st_line.set_line_bank_statement_line(inv_line.id) self.assertRecordValues(st_line.line_ids, [ {'account_id': st_line.journal_id.default_account_id.id, 'amount_currency': 250.0, 'currency_id': self.other_currency.id, 'balance': 125.0, 'reconciled': False}, {'account_id': self.partner_a.property_account_receivable_id.id, 'amount_currency': -100.0, 'currency_id': self.other_currency.id, 'balance': -50.0, 'reconciled': True}, {'account_id': st_line.journal_id.suspense_account_id.id, 'amount_currency': -150, 'currency_id': self.other_currency.id, 'balance': -75.0, 'reconciled': False}, ]) st_line = self._create_st_line(250.0, journal_id=new_journal.id, update_create_date=False) inv_line = self._create_invoice_line('out_invoice', invoice_line_ids=[{'price_unit': 500.0}], currency_id=self.other_currency.id) st_line.set_line_bank_statement_line(inv_line.id) self.assertRecordValues(st_line.line_ids, [ {'account_id': st_line.journal_id.default_account_id.id, 'amount_currency': 250.0, 'currency_id': self.other_currency.id, 'balance': 125.0, 'reconciled': False}, {'account_id': self.partner_a.property_account_receivable_id.id, 'amount_currency': -250.0, 'currency_id': self.other_currency.id, 'balance': -125.0, 'reconciled': True}, ]) def test_multicurrency_flows_triple_currency(self): currency_yen = self.setup_other_currency('JPY', rounding=0.001, rates=[('2017-01-01', 10.0)]) new_journal = self.env['account.journal'].create({ 'name': 'test', 'code': 'TBNK', 'type': 'bank', 'currency_id': self.other_currency.id, }) st_line = self._create_st_line(250.0, journal_id=new_journal.id, update_create_date=False) inv_line = self._create_invoice_line('out_invoice', invoice_line_ids=[{'price_unit': 100.0}], currency_id=currency_yen.id) st_line.set_line_bank_statement_line(inv_line.id) self.assertRecordValues(st_line.line_ids, [ {'account_id': st_line.journal_id.default_account_id.id, 'amount_currency': 250.0, 'currency_id': self.other_currency.id, 'balance': 125.0, 'reconciled': False}, {'account_id': self.partner_a.property_account_receivable_id.id, 'amount_currency': -100.0, 'currency_id': currency_yen.id, 'balance': -10.0, 'reconciled': True}, {'account_id': st_line.journal_id.suspense_account_id.id, 'amount_currency': -230, 'currency_id': self.other_currency.id, 'balance': -115.0, 'reconciled': False}, ]) st_line = self._create_st_line(250.0, journal_id=new_journal.id, update_create_date=False) inv_line = self._create_invoice_line('out_invoice', invoice_line_ids=[{'price_unit': 500.0}], currency_id=currency_yen.id) st_line.set_line_bank_statement_line(inv_line.id) self.assertRecordValues(st_line.line_ids, [ {'account_id': st_line.journal_id.default_account_id.id, 'amount_currency': 250.0, 'currency_id': self.other_currency.id, 'balance': 125.0, 'reconciled': False}, {'account_id': self.partner_a.property_account_receivable_id.id, 'amount_currency': -500.0, 'currency_id': currency_yen.id, 'balance': -50.0, 'reconciled': True}, {'account_id': st_line.journal_id.suspense_account_id.id, 'amount_currency': -150, 'currency_id': self.other_currency.id, 'balance': -75.0, 'reconciled': False}, ]) st_line = self._create_st_line(250.0, journal_id=new_journal.id, update_create_date=False) inv_line = self._create_invoice_line('out_invoice', invoice_line_ids=[{'price_unit': 5000.0}], currency_id=currency_yen.id) st_line.set_line_bank_statement_line(inv_line.id) self.assertRecordValues(st_line.line_ids, [ {'account_id': st_line.journal_id.default_account_id.id, 'amount_currency': 250.0, 'currency_id': self.other_currency.id, 'balance': 125.0, 'reconciled': False}, {'account_id': self.partner_a.property_account_receivable_id.id, 'amount_currency': -1250.0, 'currency_id': currency_yen.id, 'balance': -125.0, 'reconciled': True} ]) def test_retrieve_partner_from_account_number(self): st_line = self._create_st_line(1000.0, partner_id=None, account_number="014 474 8555") bank_account = self.env['res.partner.bank'].create({ 'acc_number': '0144748555', 'partner_id': self.partner_a.id, }) st_line.partner_id = False st_line._retrieve_partner() self.assertEqual(st_line.partner_id, bank_account.partner_id) # Can't retrieve the partner since the bank account is used by multiple partners. self.env['res.partner.bank'].create({ 'acc_number': '0144748555', 'partner_id': self.partner_b.id, }) st_line.partner_id = False st_line._retrieve_partner() self.assertEqual(st_line.partner_id, self.env['res.partner']) # Archive partner_a and see if partner_b is then chosen self.partner_a.active = False st_line.partner_id = False st_line._retrieve_partner() self.assertEqual(st_line.partner_id, self.partner_b) self.partner_a.active = True self.partner_b.active = False # Normally, we should have partner_a on the line if we retrieve the partner, # but as the partner is already set on the st_line, the retrieve partner shouldn't change the value. st_line._retrieve_partner() self.assertEqual(st_line.partner_id, self.partner_b) def test_retrieve_partner_from_account_number_in_other_company(self): st_line = self._create_st_line(1000.0, partner_id=None, account_number="014 474 8555", update_create_date=False) self.env['res.partner.bank'].create({ 'acc_number': '0144748555', 'partner_id': self.partner_a.id, }) # Bank account is owned by another company. new_company = self.env['res.company'].create({'name': "test_retrieve_partner_from_account_number_in_other_company"}) self.partner_a.company_id = new_company st_line._retrieve_partner() self.assertEqual(st_line.partner_id, self.env['res.partner']) def test_retrieve_partner_from_partner_name(self): """ Ensure the partner having a name fitting exactly the 'partner_name' is retrieved first. This test create two partners that will be ordered in the lexicographic order when performing a search. So: row1: "Turlututu tsoin tsoin" row2: "turlututu" Since "turlututu" matches exactly (case insensitive) the partner_name of the statement line, it should be suggested first. However if we have two partners called turlututu, we should not suggest any or we risk selecting the wrong one. """ _partner_a, partner_b = self.env['res.partner'].create([ {'name': "Turlututu tsoin tsoin"}, {'name': "turlututu"}, ]) st_line = self._create_st_line(1000.0, partner_id=None, partner_name="Turlututu", update_create_date=False) self.assertEqual(st_line.partner_id, partner_b) self.env['res.partner'].create({'name': "turlututu"}) st_line.partner_id = False st_line._retrieve_partner() self.assertFalse(st_line.partner_id) def test_retrieve_partner_from_partner_name_odoobot(self): """This test ensure that OdooBot is not set on a statement line with the retrieve partner""" odoobot = self.env.ref('base.partner_root') odoobot.company_id = self.env.company.id st_line = self._create_st_line( 1000.0, partner_id=None, partner_name="ODOO", update_create_date=False, ) self.assertNotEqual(st_line.partner_id, odoobot) def test_retrieve_partner_from_previous_reconciled_st_line(self): """Test the retrieve partner from a previous reconciled st-line.""" # Use 2 partner with the same name, so we can't retrieve it from the partner name _partner_a, partner_b = self.env['res.partner'].create([ {'name': "Turlututu"}, {'name': "Turlututu"}, ]) st_line = self._create_st_line(1000.0, partner_id=None, partner_name="Turlututu", update_create_date=False) # This st_line should be the one we retrieve the partner from st_line_2 = self._create_st_line(1000.0, partner_id=partner_b.id, partner_name="Turlututu", update_create_date=False) inv_line = self._create_invoice_line('out_invoice', invoice_line_ids=[{'price_unit': 1000.0}]) st_line_2.set_line_bank_statement_line(inv_line.id) st_line._retrieve_partner() self.assertEqual(st_line.partner_id, partner_b) def test_retrieve_partner_with_multiple_st_line(self): """Test if the retrieve partner in batch works properly""" _partner_a, partner_b, partner_c = self.env['res.partner'].create([ {'name': "Turlututu"}, {'name': "Turlututu"}, {'name': "Turlututu tsoin tsoin"}, ]) bank_account = self.env['res.partner.bank'].create({ 'acc_number': '0144748555', 'partner_id': self.partner_a.id, }) st_line_1 = self._create_st_line(1000.0, partner_id=partner_b.id, partner_name="Turlututu", update_create_date=False) inv_line = self._create_invoice_line('out_invoice', invoice_line_ids=[{'price_unit': 1000.0}]) st_line_1.set_line_bank_statement_line(inv_line.id) st_line_2 = self._create_st_line(1000.0, partner_id=None, partner_name="Turlututu", update_create_date=False) st_line_3 = self._create_st_line(1000.0, partner_id=None, account_number="014 474 8555") st_line_4 = self._create_st_line(1000.0, partner_id=None, partner_name="Turlututu tsoin tsoin") st_lines = (st_line_2 + st_line_3 + st_line_4) st_lines.write({'partner_id': False}) st_lines._retrieve_partner() self.assertEqual(st_line_2.partner_id, partner_b) self.assertEqual(st_line_3.partner_id, bank_account.partner_id) self.assertEqual(st_line_4.partner_id, partner_c) def test_retrieve_partner_with_multiple_matches(self): """ Test if the retrieve partner from partner name doesn't erase the result of the retrieve partner from bank account. """ partner_a, _partner_b = self.env['res.partner'].create([ {'name': 'Bobybob'}, {'name': 'Turlututu'}, ]) bank_account = self.env['res.partner.bank'].create({ 'acc_number': '123456789', 'partner_id': partner_a.id, }) st_line = self._create_st_line(1000.0, partner_id=None, account_number="123456789", partner_name="Turlututu", update_create_date=False) st_line._retrieve_partner() self.assertEqual(st_line.partner_id, bank_account.partner_id) def test_res_partner_bank_find_create_when_archived(self): """ Test we don't get the "The combination Account Number/Partner must be unique." error with archived bank account. """ partner = self.env['res.partner'].create({ 'name': "Zitycard", 'bank_ids': [Command.create({ 'acc_number': "123456789", 'active': False, })], }) st_line = self._create_st_line( 100.0, partner_name="Zeumat Zitycard", account_number="123456789", update_create_date=False, ) inv_line = self._create_invoice_line( 'out_invoice', partner_id=partner.id, invoice_line_ids=[{'price_unit': 100.0, 'tax_ids': []}], ) st_line.set_line_bank_statement_line(inv_line.id) # Should not trigger the error. self.env['res.partner.bank'].flush_model() def test_res_partner_bank_find_create_multi_company(self): """ Test we don't get the "The combination Account Number/Partner must be unique." error when the bank account already exists on another company. """ partner = self.env['res.partner'].create({ 'name': "Zitycard", 'bank_ids': [Command.create({'acc_number': "123456789"})], }) partner.bank_ids.company_id = self.company_data_2['company'] self.env.user.company_ids = self.env.company st_line = self._create_st_line( 100.0, partner_name="Zeumat Zitycard", account_number="123456789", update_create_date=False, ) inv_line = self._create_invoice_line( 'out_invoice', partner_id=partner.id, invoice_line_ids=[{'price_unit': 100.0, 'tax_ids': []}], ) st_line.set_line_bank_statement_line(inv_line.id) # Should not trigger the error. self.env['res.partner.bank'].flush_model() def test_validation_exchange_difference_draft_invoice(self): # 240.0 curr2 == 80.0 comp_curr inv_line = self._create_invoice_line( 'out_invoice', currency_id=self.other_currency.id, invoice_date='2016-01-01', invoice_line_ids=[{'price_unit': 240.0}], ) inv_line.move_id.button_draft() self.assertEqual(inv_line.move_id.state, 'draft') self.assertAlmostEqual(inv_line.amount_residual, 80.0) # 1st statement line # 120.0 curr2 == 60.0 comp_curr st_line_1 = self._create_st_line( 60.0, date='2017-01-01', foreign_currency_id=self.other_currency.id, amount_currency=120.0, update_create_date=False, ) st_line_1.set_line_bank_statement_line(inv_line.id) # Check the statement line. self.assertRecordValues(st_line_1.line_ids.sorted(), [ {'account_id': st_line_1.journal_id.default_account_id.id, 'amount_currency': 60.0, 'currency_id': self.company_data['currency'].id, 'balance': 60.0, 'reconciled': False}, {'account_id': inv_line.account_id.id, 'amount_currency': -120.0, 'currency_id': self.other_currency.id, 'balance': -60.0, 'reconciled': True}, ]) partials = st_line_1.line_ids.matched_debit_ids exchange_move = partials.exchange_move_id _liquidity_line, _suspense_line, other_line = st_line_1._seek_for_lines() self.assertRecordValues(partials.sorted(), [ { 'amount': 40.0, 'debit_amount_currency': 120.0, 'credit_amount_currency': 120.0, 'debit_move_id': inv_line.id, 'credit_move_id': other_line.id, 'exchange_move_id': exchange_move.id, }, { 'amount': 20.0, 'debit_amount_currency': 0.0, 'credit_amount_currency': 0.0, 'debit_move_id': exchange_move.line_ids.sorted()[0].id, 'credit_move_id': other_line.id, 'exchange_move_id': False, }, ]) # Check the exchange diff journal entry. self.assertEqual(exchange_move.state, 'draft') self.assertRecordValues(exchange_move.line_ids.sorted(), [ {'account_id': inv_line.account_id.id, 'amount_currency': 0.0, 'currency_id': self.other_currency.id, 'balance': 20.0, 'reconciled': True}, {'account_id': self.env.company.income_currency_exchange_account_id.id, 'amount_currency': 0.0, 'currency_id': self.other_currency.id, 'balance': -20.0, 'reconciled': False}, ]) self.assertEqual(inv_line.move_id.payment_state, 'partial') self.assertAlmostEqual(inv_line.amount_residual, 40.0) # modifying something critical before posting the invoice should remove entirely the draft exchange entry and reconciliation made inv_line.move_id.line_ids.filtered(lambda x: x.display_type == 'product').price_unit = 290 inv_line.move_id.action_post() self.assertEqual(inv_line.move_id.payment_state, 'not_paid') partials = st_line_1.line_ids.matched_debit_ids exchange_move = partials.exchange_move_id self.assertEqual(exchange_move, self.env['account.move']) # reset the invoice in draft and to previous values for the next test inv_line.move_id.button_draft() inv_line.move_id.line_ids.filtered(lambda x: x.display_type == 'product').price_unit = 240 self.assertAlmostEqual(inv_line.amount_residual, 80.0) # 2nd statement line st_line_2 = self._create_st_line( 60.0, date='2017-01-01', foreign_currency_id=self.other_currency.id, amount_currency=120.0, update_create_date=False, ) st_line_2.set_line_bank_statement_line(inv_line.id) partials = st_line_2.line_ids.matched_debit_ids exchange_move = partials.exchange_move_id self.assertEqual(exchange_move.state, 'draft') self.assertEqual(inv_line.move_id.payment_state, 'partial') self.assertAlmostEqual(inv_line.amount_residual, 40.0) # 2nd statement creates exactly the same as 1st, so there's no need to assert those values/entries # modifying something uncritical before posting the invoice shouldn't remove the draft exchange entry and reconciliation made inv_line.ref = 'new reference' inv_line.move_id.action_post() self.assertEqual(inv_line.move_id.payment_state, 'partial') self.assertAlmostEqual(inv_line.amount_residual, 40.0) self.assertEqual(exchange_move.state, 'posted') def test_validation_expense_exchange_difference(self): expense_exchange_account = self.env.company.expense_currency_exchange_account_id # 1200.0 comp_curr = 3600.0 foreign_curr in 2016 (rate 1:3) st_line = self._create_st_line( 1200.0, date='2016-01-01', update_create_date=False, ) # 1800.0 comp_curr = 3600.0 foreign_curr in 2017 (rate 1:2) inv_line = self._create_invoice_line( 'out_invoice', currency_id=self.other_currency.id, invoice_date='2017-01-01', invoice_line_ids=[{'price_unit': 3600.0}], ) st_line.set_line_bank_statement_line(inv_line.id) self.assertRecordValues(st_line.line_ids, [ {'account_id': st_line.journal_id.default_account_id.id, 'amount_currency': 1200.0, 'currency_id': self.company_data['currency'].id, 'balance': 1200.0, 'reconciled': False}, {'account_id': inv_line.account_id.id, 'amount_currency': -3600.0, 'currency_id': self.other_currency.id, 'balance': -1200.0, 'reconciled': True}, ]) self.assertRecordValues(st_line, [{'is_reconciled': True}]) self.assertRecordValues(inv_line.move_id, [{'payment_state': 'paid'}]) self.assertRecordValues(inv_line.matched_credit_ids.exchange_move_id.line_ids, [ {'account_id': inv_line.account_id.id, 'amount_currency': 0.0, 'currency_id': self.other_currency.id, 'balance': -600.0, 'reconciled': True, 'date': fields.Date.from_string('2017-01-31')}, {'account_id': expense_exchange_account.id, 'amount_currency': 0.0, 'currency_id': self.other_currency.id, 'balance': 600.0, 'reconciled': False, 'date': fields.Date.from_string('2017-01-31')}, ]) def test_validation_income_exchange_difference(self): income_exchange_account = self.env.company.income_currency_exchange_account_id # 1800.0 comp_curr = 3600.0 foreign_curr in 2017 (rate 1:2) st_line = self._create_st_line( 1800.0, date='2017-01-01', update_create_date=False, ) # 1200.0 comp_curr = 3600.0 foreign_curr in 2016 (rate 1:3) inv_line = self._create_invoice_line( 'out_invoice', currency_id=self.other_currency.id, invoice_date='2016-01-01', invoice_line_ids=[{'price_unit': 3600.0}], ) st_line.set_line_bank_statement_line(inv_line.id) self.assertRecordValues(st_line.line_ids, [ {'account_id': st_line.journal_id.default_account_id.id, 'amount_currency': 1800.0, 'currency_id': self.company_data['currency'].id, 'balance': 1800.0, 'reconciled': False}, {'account_id': inv_line.account_id.id, 'amount_currency': -3600.0, 'currency_id': self.other_currency.id, 'balance': -1800.0, 'reconciled': True}, ]) self.assertRecordValues(st_line, [{'is_reconciled': True}]) self.assertRecordValues(inv_line.move_id, [{'payment_state': 'paid'}]) self.assertRecordValues(inv_line.matched_credit_ids.exchange_move_id.line_ids, [ {'account_id': inv_line.account_id.id, 'amount_currency': 0.0, 'currency_id': self.other_currency.id, 'balance': 600.0, 'reconciled': True, 'date': fields.Date.from_string('2017-01-31')}, {'account_id': income_exchange_account.id, 'amount_currency': 0.0, 'currency_id': self.other_currency.id, 'balance': -600.0, 'reconciled': False, 'date': fields.Date.from_string('2017-01-31')}, ]) def test_validation_exchange_diff_multiple(self): income_exchange_account = self.env.company.income_currency_exchange_account_id foreign_currency = self.setup_other_currency('AED', rates=[('2016-01-01', 6.0), ('2017-01-01', 5.0)]) # 6000.0 curr2 == 1200.0 comp_curr (bank rate 5:1 instead of the odoo rate 6:1) st_line = self._create_st_line( 1200.0, date='2016-01-01', foreign_currency_id=foreign_currency.id, amount_currency=6000.0, update_create_date=False, ) # 1000.0 foreign_curr == 166.67 comp_curr (rate 6:1) inv_line_1 = self._create_invoice_line( 'out_invoice', currency_id=foreign_currency.id, invoice_date='2016-01-01', invoice_line_ids=[{'price_unit': 1000.0}], ) # 2000.00 foreign_curr == 400.0 comp_curr (rate 5:1) inv_line_2 = self._create_invoice_line( 'out_invoice', currency_id=foreign_currency.id, invoice_date='2017-01-01', invoice_line_ids=[{'price_unit': 2000.0}], ) # 3000.0 foreign_curr == 500.0 comp_curr (rate 6:1) inv_line_3 = self._create_invoice_line( 'out_invoice', currency_id=foreign_currency.id, invoice_date='2016-01-01', invoice_line_ids=[{'price_unit': 3000.0}], ) st_line.set_line_bank_statement_line((inv_line_1 + inv_line_2 + inv_line_3).ids) self.assertRecordValues(st_line.line_ids, [ {'account_id': st_line.journal_id.default_account_id.id, 'amount_currency': 1200.0, 'currency_id': self.company_data['currency'].id, 'balance': 1200.0, 'reconciled': False}, {'account_id': inv_line_1.account_id.id, 'amount_currency': -1000.0, 'currency_id': foreign_currency.id, 'balance': -200.0, 'reconciled': True}, {'account_id': inv_line_2.account_id.id, 'amount_currency': -2000.0, 'currency_id': foreign_currency.id, 'balance': -400.0, 'reconciled': True}, {'account_id': inv_line_3.account_id.id, 'amount_currency': -3000.0, 'currency_id': foreign_currency.id, 'balance': -600.0, 'reconciled': True}, ]) self.assertRecordValues(st_line, [{'is_reconciled': True}]) self.assertRecordValues(inv_line_1.move_id, [{'payment_state': 'paid'}]) self.assertRecordValues(inv_line_2.move_id, [{'payment_state': 'paid'}]) self.assertRecordValues(inv_line_3.move_id, [{'payment_state': 'paid'}]) self.assertRecordValues((inv_line_1 + inv_line_2 + inv_line_3).matched_credit_ids.exchange_move_id.line_ids, [ {'account_id': inv_line_1.account_id.id, 'amount_currency': 0.0, 'currency_id': foreign_currency.id, 'balance': 33.33, 'reconciled': True}, {'account_id': income_exchange_account.id, 'amount_currency': 0.0, 'currency_id': foreign_currency.id, 'balance': -33.33, 'reconciled': False}, {'account_id': inv_line_3.account_id.id, 'amount_currency': 0.0, 'currency_id': foreign_currency.id, 'balance': 100.0, 'reconciled': True}, {'account_id': income_exchange_account.id, 'amount_currency': 0.0, 'currency_id': foreign_currency.id, 'balance': -100.0, 'reconciled': False}, ]) def test_early_payment_included_intracomm_bill(self): tax_tags = self.env['account.account.tag'].create([{ 'name': f'tax_tag_{i}', 'applicability': 'taxes', 'country_id': self.env.company.account_fiscal_country_id.id, } for i in range(6)]) intracomm_tax = self.env['account.tax'].create({ 'name': 'tax20', 'amount_type': 'percent', 'amount': 20, 'type_tax_use': 'purchase', 'invoice_repartition_line_ids': [ Command.create({'repartition_type': 'base', 'factor_percent': 100.0, 'tag_ids': [Command.set(tax_tags[0].ids)]}), Command.create({'repartition_type': 'tax', 'factor_percent': 100.0, 'tag_ids': [Command.set(tax_tags[1].ids)]}), Command.create({'repartition_type': 'tax', 'factor_percent': -100.0, 'tag_ids': [Command.set(tax_tags[2].ids)]}), ], 'refund_repartition_line_ids': [ Command.create({'repartition_type': 'base', 'factor_percent': 100.0, 'tag_ids': [Command.set(tax_tags[3].ids)]}), Command.create({'repartition_type': 'tax', 'factor_percent': 100.0, 'tag_ids': [Command.set(tax_tags[4].ids)]}), Command.create({'repartition_type': 'tax', 'factor_percent': -100.0, 'tag_ids': [Command.set(tax_tags[5].ids)]}), ], }) early_payment_term = self.env['account.payment.term'].create({ 'name': "early_payment_term", 'company_id': self.company_data['company'].id, 'early_pay_discount_computation': 'included', 'early_discount': True, 'discount_percentage': 2, 'discount_days': 7, 'line_ids': [ Command.create({ 'value': 'percent', 'value_amount': 100.0, 'nb_days': 30, }), ], }) bill = self.env['account.move'].create({ 'move_type': 'in_invoice', 'partner_id': self.partner_a.id, 'invoice_payment_term_id': early_payment_term.id, 'invoice_date': '2019-01-01', 'date': '2019-01-01', 'invoice_line_ids': [ Command.create({ 'name': 'line', 'price_unit': 1000.0, 'tax_ids': [Command.set(intracomm_tax.ids)], }), ], }) bill.action_post() st_line = self._create_st_line( -980.0, date='2017-01-01', update_create_date=False, ) st_line.set_line_bank_statement_line(bill.line_ids.filtered(lambda x: x.account_type == 'liability_payable').ids) self.assertRecordValues(st_line.line_ids.sorted('balance'), [ {'amount_currency': -980.0, 'tax_ids': [], 'tax_tag_ids': []}, {'amount_currency': -20.0, 'tax_ids': intracomm_tax.ids, 'tax_tag_ids': tax_tags[3].ids}, {'amount_currency': -4.0, 'tax_ids': [], 'tax_tag_ids': tax_tags[4].ids}, {'amount_currency': 4.0, 'tax_ids': [], 'tax_tag_ids': tax_tags[5].ids}, {'amount_currency': 1000.0, 'tax_ids': [], 'tax_tag_ids': []}, ]) def test_partial_reconciliation_suggestion_with_mixed_invoice_and_refund(self): """ Test the partial reconciliation suggestion is well recomputed when adding another line. For example, when adding 2 invoices having an higher amount then a refund. In that case, the partial on the second invoice should be removed since the difference is filled by the newly added refund. """ st_line = self._create_st_line( 1800.0, date='2017-01-01', foreign_currency_id=self.other_currency.id, amount_currency=3600.0, update_create_date=False, ) inv1 = self._create_invoice_line( 'out_invoice', currency_id=self.other_currency.id, invoice_date='2016-01-01', invoice_line_ids=[{'price_unit': 2400.0}], ) inv2 = self._create_invoice_line( 'out_invoice', currency_id=self.other_currency.id, invoice_date='2016-01-01', invoice_line_ids=[{'price_unit': 600.0}], ) refund = self._create_invoice_line( 'out_refund', currency_id=self.other_currency.id, invoice_date='2016-01-01', invoice_line_ids=[{'price_unit': 1200.0}], ) st_line.set_line_bank_statement_line(inv1.id) self.assertRecordValues(st_line.line_ids, [ {'account_id': st_line.journal_id.default_account_id.id, 'amount_currency': 1800.0, 'currency_id': self.company_data['currency'].id, 'balance': 1800.0, 'reconciled': False}, {'account_id': inv1.account_id.id, 'amount_currency': -2400.0, 'currency_id': self.other_currency.id, 'balance': -1200.0, 'reconciled': True}, {'account_id': st_line.journal_id.suspense_account_id.id, 'amount_currency': -1200.0, 'currency_id': self.other_currency.id, 'balance': -600.0, 'reconciled': False}, ]) exchange_move_1 = st_line.line_ids[1].matched_debit_ids.exchange_move_id self.assertRecordValues(exchange_move_1, [{ 'date': fields.Date.from_string('2017-01-31'), 'amount_total_signed': 400, }]) st_line.set_line_bank_statement_line(inv2.id) self.assertRecordValues(st_line.line_ids, [ {'account_id': st_line.journal_id.default_account_id.id, 'amount_currency': 1800.0, 'currency_id': self.company_data['currency'].id, 'balance': 1800.0, 'reconciled': False}, {'account_id': inv1.account_id.id, 'amount_currency': -2400.0, 'currency_id': self.other_currency.id, 'balance': -1200.0, 'reconciled': True}, {'account_id': inv2.account_id.id, 'amount_currency': -600.0, 'currency_id': self.other_currency.id, 'balance': -300.0, 'reconciled': True}, {'account_id': st_line.journal_id.suspense_account_id.id, 'amount_currency': -600.0, 'currency_id': self.other_currency.id, 'balance': -300.0, 'reconciled': False}, ]) exchange_move_2 = st_line.line_ids[2].matched_debit_ids.exchange_move_id self.assertRecordValues(exchange_move_2, [{ 'date': fields.Date.from_string('2017-01-31'), 'amount_total_signed': 100, }]) st_line.set_line_bank_statement_line(refund.id) self.assertRecordValues(st_line.line_ids, [ {'account_id': st_line.journal_id.default_account_id.id, 'amount_currency': 1800.0, 'currency_id': self.company_data['currency'].id, 'balance': 1800.0, 'reconciled': False}, {'account_id': inv1.account_id.id, 'amount_currency': -2400.0, 'currency_id': self.other_currency.id, 'balance': -1200.0, 'reconciled': True}, {'account_id': inv2.account_id.id, 'amount_currency': -600.0, 'currency_id': self.other_currency.id, 'balance': -300.0, 'reconciled': True}, {'account_id': refund.account_id.id, 'amount_currency': 1200.0, 'currency_id': self.other_currency.id, 'balance': 600.0, 'reconciled': True}, {'account_id': st_line.journal_id.suspense_account_id.id, 'amount_currency': -1800.0, 'currency_id': self.other_currency.id, 'balance': -900.0, 'reconciled': False}, ]) exchange_move_3 = st_line.line_ids[3].matched_credit_ids.exchange_move_id self.assertRecordValues(exchange_move_3, [{ 'date': fields.Date.from_string('2017-01-31'), 'amount_total_signed': 200.0, }]) def test_reconciliation_with_branch(self): """ Test that the reconciliation flow doesn't break with aml from branch and st line from root company """ company = self.company_data['company'] branch = self.env['res.company'].create({ 'name': "Branch A", 'parent_id': company.id, }) # Load CoA self.cr.precommit.run() partner_branch = self.env['res.partner'].create({ 'name': 'Partner Branch', 'company_id': branch.id, }) aml_branch = self._create_invoice_line( 'out_invoice', company_id=branch.id, partner_id=partner_branch.id, invoice_date='2019-01-01', invoice_line_ids=[{'name': 'Test reco', 'quantity': 1, 'price_unit': 1000}], ) st_line_main = self._create_st_line( 1000.0, company_id=company.id, date='2019-01-01', payment_ref='Test reco', ) st_line_branch = self._create_st_line( 1000.0, company_id=branch.id, date='2019-01-01', payment_ref='Test reco2', partner_id=partner_branch.id, ) # Case 1: reconciliation with st_line on the main company + aml on the branch st_line_main.set_line_bank_statement_line(aml_branch.ids) # Assert that the partner is not set to avoid "Incompatible companies on records" error self.assertFalse(st_line_main.partner_id) st_line_main.action_undo_reconciliation() # Case 2: reconciliation with both st_line and aml on the branch st_line_branch.set_line_bank_statement_line(aml_branch.ids) # Assert that the partner is set correctly self.assertEqual(st_line_branch.partner_id, partner_branch, "The partner should remain set on the transaction for the branch company.") st_line_branch.action_undo_reconciliation() # Case 3: reconciliation with both st_line and aml on the branch, no partner on the st_line st_line_branch.partner_id = False st_line_branch.set_line_bank_statement_line(aml_branch.ids) # Assert that the partner is set from the aml on the transaction self.assertEqual(st_line_branch.partner_id, partner_branch, "The partner should be automatically set on the transaction for the branch company if it's set on the aml.") def test_residual_amount_same_currency(self): st_line_1 = self._create_st_line( 20.0, date='2017-01-01', update_create_date=False, ) st_line_2 = self._create_st_line( 100.0, date='2017-01-01', update_create_date=False, ) inv1 = self._create_invoice_line( 'out_invoice', invoice_date='2016-01-01', invoice_line_ids=[{'price_unit': 100.0}], ) st_line_1.set_line_bank_statement_line(inv1.id) self.assertRecordValues(st_line_1.line_ids, [ {'account_id': st_line_1.journal_id.default_account_id.id, 'amount_currency': 20.0, 'currency_id': self.company_data['currency'].id, 'balance': 20.0, 'reconciled': False}, {'account_id': inv1.account_id.id, 'amount_currency': -20.0, 'currency_id': self.company_data['currency'].id, 'balance': -20.0, 'reconciled': True}, ]) self.assertEqual(inv1.amount_residual, 80) self.assertEqual(inv1.move_id.payment_state, 'partial') st_line_2.set_line_bank_statement_line(inv1.id) self.assertRecordValues(st_line_2.line_ids, [ {'account_id': st_line_2.journal_id.default_account_id.id, 'amount_currency': 100.0, 'currency_id': self.company_data['currency'].id, 'balance': 100.0, 'reconciled': False}, {'account_id': inv1.account_id.id, 'amount_currency': -80.0, 'currency_id': self.company_data['currency'].id, 'balance': -80.0, 'reconciled': True}, {'account_id': st_line_2.journal_id.suspense_account_id.id, 'amount_currency': -20.0, 'currency_id': self.company_data['currency'].id, 'balance': -20.0, 'reconciled': False}, ]) self.assertEqual(inv1.amount_residual, 0) self.assertEqual(inv1.move_id.payment_state, 'paid') def test_residual_amount_other_currency(self): st_line_1 = self._create_st_line( 20.0, date='2017-01-01', update_create_date=False, ) st_line_2 = self._create_st_line( 100.0, date='2017-01-01', update_create_date=False, ) inv1 = self._create_invoice_line( 'out_invoice', currency_id=self.other_currency.id, invoice_date='2017-01-01', invoice_line_ids=[{'price_unit': 100.0}], ) st_line_1.set_line_bank_statement_line(inv1.id) self.assertRecordValues(st_line_1.line_ids, [ {'account_id': st_line_1.journal_id.default_account_id.id, 'amount_currency': 20.0, 'currency_id': self.company_data['currency'].id, 'balance': 20.0, 'reconciled': False}, {'account_id': inv1.account_id.id, 'amount_currency': -40.0, 'currency_id': self.other_currency.id, 'balance': -20.0, 'reconciled': True}, ]) self.assertEqual(inv1.amount_residual_currency, 60) self.assertEqual(inv1.move_id.payment_state, 'partial') st_line_2.set_line_bank_statement_line(inv1.id) self.assertRecordValues(st_line_2.line_ids, [ {'account_id': st_line_2.journal_id.default_account_id.id, 'amount_currency': 100.0, 'currency_id': self.company_data['currency'].id, 'balance': 100.0, 'reconciled': False}, {'account_id': inv1.account_id.id, 'amount_currency': -60.0, 'currency_id': self.other_currency.id, 'balance': -30.0, 'reconciled': True}, {'account_id': st_line_2.journal_id.suspense_account_id.id, 'amount_currency': -70.0, 'currency_id': self.company_data['currency'].id, 'balance': -70.0, 'reconciled': False}, ]) self.assertEqual(inv1.amount_residual_currency, 0) self.assertEqual(inv1.move_id.payment_state, 'paid') def test_adding_multiple_invoice_at_once(self): """ In this test we will create a statement line positive and try to add multiple invoice at once. """ statement_line = self._create_st_line(amount=160, update_create_date=False) move_line_1 = self._create_invoice_line('out_invoice', invoice_line_ids=[{'price_unit': 25.0}]) move_line_2 = self._create_invoice_line('out_invoice', invoice_line_ids=[{'price_unit': 50.0}]) move_line_3 = self._create_invoice_line('out_invoice', invoice_line_ids=[{'price_unit': 100.0}]) statement_line.set_line_bank_statement_line((move_line_1 + move_line_2 + move_line_3).ids) self.assertRecordValues(statement_line.line_ids, [ {'account_id': statement_line.journal_id.default_account_id.id, 'amount_currency': 160.0, 'currency_id': self.company_data['currency'].id, 'balance': 160.0, 'reconciled': False}, {'account_id': move_line_1.account_id.id, 'amount_currency': -25.0, 'currency_id': self.company_data['currency'].id, 'balance': -25.0, 'reconciled': True}, {'account_id': move_line_2.account_id.id, 'amount_currency': -50.0, 'currency_id': self.company_data['currency'].id, 'balance': -50.0, 'reconciled': True}, {'account_id': move_line_3.account_id.id, 'amount_currency': -85.0, 'currency_id': self.company_data['currency'].id, 'balance': -85.0, 'reconciled': True}, ]) def test_adding_multiple_bill_at_once(self): """ In this test we will create a statement line negative and try to add multiple bill at once.""" statement_line = self._create_st_line(amount=-160, update_create_date=False) move_line_1 = self._create_invoice_line('in_invoice', invoice_line_ids=[{'price_unit': 25.0}]) move_line_2 = self._create_invoice_line('in_invoice', invoice_line_ids=[{'price_unit': 50.0}]) move_line_3 = self._create_invoice_line('in_invoice', invoice_line_ids=[{'price_unit': 100.0}]) statement_line.set_line_bank_statement_line((move_line_1 + move_line_2 + move_line_3).ids) self.assertRecordValues(statement_line.line_ids, [ {'account_id': statement_line.journal_id.default_account_id.id, 'amount_currency': -160.0, 'currency_id': self.company_data['currency'].id, 'balance': -160.0, 'reconciled': False}, {'account_id': move_line_1.account_id.id, 'amount_currency': 25.0, 'currency_id': self.company_data['currency'].id, 'balance': 25.0, 'reconciled': True}, {'account_id': move_line_2.account_id.id, 'amount_currency': 50.0, 'currency_id': self.company_data['currency'].id, 'balance': 50.0, 'reconciled': True}, {'account_id': move_line_3.account_id.id, 'amount_currency': 85.0, 'currency_id': self.company_data['currency'].id, 'balance': 85.0, 'reconciled': True}, ]) def test_adding_multiple_moves_and_then_more(self): """ In this test we will create a statement line negative and try to add multiple bill and invoice at once. We will have a suspense line, now we add a line that as a bigger amount to see if the partial works correctly. """ statement_line = self._create_st_line(amount=160, update_create_date=False) move_line_1 = self._create_invoice_line('out_invoice', invoice_line_ids=[{'price_unit': 25.0}]) move_line_2 = self._create_invoice_line('out_invoice', invoice_line_ids=[{'price_unit': 50.0}]) move_line_3 = self._create_invoice_line('out_invoice', invoice_line_ids=[{'price_unit': 100.0}]) move_line_4 = self._create_invoice_line('in_invoice', invoice_line_ids=[{'price_unit': 25.0}]) statement_line.set_line_bank_statement_line((move_line_1 + move_line_2 + move_line_3 + move_line_4).ids) self.assertRecordValues(statement_line.line_ids, [ {'account_id': statement_line.journal_id.default_account_id.id, 'amount_currency': 160.0, 'currency_id': self.company_data['currency'].id, 'balance': 160.0, 'reconciled': False}, {'account_id': move_line_1.account_id.id, 'amount_currency': -25.0, 'currency_id': self.company_data['currency'].id, 'balance': -25.0, 'reconciled': True}, {'account_id': move_line_2.account_id.id, 'amount_currency': -50.0, 'currency_id': self.company_data['currency'].id, 'balance': -50.0, 'reconciled': True}, {'account_id': move_line_3.account_id.id, 'amount_currency': -100.0, 'currency_id': self.company_data['currency'].id, 'balance': -100.0, 'reconciled': True}, {'account_id': move_line_4.account_id.id, 'amount_currency': 25.0, 'currency_id': self.company_data['currency'].id, 'balance': 25.0, 'reconciled': True}, {'account_id': statement_line.journal_id.suspense_account_id.id, 'amount_currency': -10.0, 'currency_id': self.company_data['currency'].id, 'balance': -10.0, 'reconciled': False}, ]) move_line_5 = self._create_invoice_line('out_invoice', invoice_line_ids=[{'price_unit': 20.0}]) statement_line.set_line_bank_statement_line(move_line_5.id) self.assertRecordValues(statement_line.line_ids, [ {'account_id': statement_line.journal_id.default_account_id.id, 'amount_currency': 160.0, 'currency_id': self.company_data['currency'].id, 'balance': 160.0, 'reconciled': False}, {'account_id': move_line_1.account_id.id, 'amount_currency': -25.0, 'currency_id': self.company_data['currency'].id, 'balance': -25.0, 'reconciled': True}, {'account_id': move_line_2.account_id.id, 'amount_currency': -50.0, 'currency_id': self.company_data['currency'].id, 'balance': -50.0, 'reconciled': True}, {'account_id': move_line_3.account_id.id, 'amount_currency': -100.0, 'currency_id': self.company_data['currency'].id, 'balance': -100.0, 'reconciled': True}, {'account_id': move_line_4.account_id.id, 'amount_currency': 25.0, 'currency_id': self.company_data['currency'].id, 'balance': 25.0, 'reconciled': True}, {'account_id': move_line_5.account_id.id, 'amount_currency': -10.0, 'currency_id': self.company_data['currency'].id, 'balance': -10.0, 'reconciled': True}, ]) def test_adding_multiple_moves_and_then_more_multi_currencies(self): statement_line = self._create_st_line(amount=160, update_create_date=False) move_line_1 = self._create_invoice_line('out_invoice', invoice_line_ids=[{'price_unit': 25.0}]) move_line_2 = self._create_invoice_line('out_invoice', invoice_line_ids=[{'price_unit': 100.0}], currency_id=self.other_currency.id) move_line_3 = self._create_invoice_line('out_invoice', invoice_line_ids=[{'price_unit': 100.0}]) move_line_4 = self._create_invoice_line('in_invoice', invoice_line_ids=[{'price_unit': 50.0}], currency_id=self.other_currency.id) statement_line.set_line_bank_statement_line((move_line_1 + move_line_2 + move_line_3 + move_line_4).ids) self.assertRecordValues(statement_line.line_ids, [ {'account_id': statement_line.journal_id.default_account_id.id, 'amount_currency': 160.0, 'currency_id': self.company_data['currency'].id, 'balance': 160.0, 'reconciled': False}, {'account_id': move_line_1.account_id.id, 'amount_currency': -25.0, 'currency_id': self.company_data['currency'].id, 'balance': -25.0, 'reconciled': True}, {'account_id': move_line_2.account_id.id, 'amount_currency': -100.0, 'currency_id': self.other_currency.id, 'balance': -50.0, 'reconciled': True}, {'account_id': move_line_3.account_id.id, 'amount_currency': -100.0, 'currency_id': self.company_data['currency'].id, 'balance': -100.0, 'reconciled': True}, {'account_id': move_line_4.account_id.id, 'amount_currency': 50.0, 'currency_id': self.other_currency.id, 'balance': 25.0, 'reconciled': True}, {'account_id': statement_line.journal_id.suspense_account_id.id, 'amount_currency': -10.0, 'currency_id': self.company_data['currency'].id, 'balance': -10.0, 'reconciled': False}, ]) def test_partial_auto_tolerance(self): self.env['ir.config_parameter'].set_param('account_accountant.bank_rec_payment_tolerance', '0.03') inv1 = self._create_invoice_line( 'out_invoice', partner_id=self.partner_a.id, invoice_date='2020-01-01', invoice_line_ids=[{'price_unit': 500.0}], ) st_line = self._create_st_line( 450.0, date='2020-01-05', partner_id=self.partner_a.id, update_create_date=False, ) st_line._try_auto_reconcile_statement_lines() self.assertFalse(st_line.is_reconciled) st_line = self._create_st_line( 490.0, date='2020-01-05', partner_id=self.partner_a.id, update_create_date=False, ) st_line._try_auto_reconcile_statement_lines() # The invoice is fully reconciled, with the surplus on the suspense account self.assertRecordValues(st_line.line_ids, [ {'account_id': st_line.journal_id.default_account_id.id, 'balance': 490.0, 'reconciled': False}, {'account_id': inv1.account_id.id, 'balance': -500.0, 'reconciled': True}, {'account_id': st_line.journal_id.suspense_account_id.id, 'balance': 10.0, 'reconciled': False}, ]) st_line.set_account_bank_statement_line(st_line.line_ids[-1].id, self.account_revenue_1.id) reco_model = self.env.ref(f'account.account_reco_model_fee_{st_line.journal_id.id}', raise_if_not_found=False) self.assertTrue(reco_model, "A new reco model for fees should have been created") self._create_invoice_line( 'out_invoice', partner_id=self.partner_a.id, invoice_date='2020-01-01', invoice_line_ids=[{'price_unit': 500.0}], ) st_line = self._create_st_line( 490.0, date='2020-01-05', partner_id=self.partner_a.id, update_create_date=False, ) st_line._try_auto_reconcile_statement_lines() self.assertEqual( st_line.line_ids[-1].reconcile_model_id, reco_model, "The fees reco model should be assigned to a new line that is close to the invoice", ) def test_partial_auto_tolerance_different_amount(self): self.env['ir.config_parameter'].set_param('account_accountant.bank_rec_payment_tolerance', '0.05') inv = self._create_invoice_line( 'out_invoice', partner_id=self.partner_a.id, invoice_date='2020-01-01', invoice_line_ids=[{'price_unit': 100.0}], ) st_line = self._create_st_line( 90.0, date='2020-01-05', partner_id=self.partner_a.id, update_create_date=False, ) st_line.set_line_bank_statement_line(inv.id) self.assertRecordValues(st_line.line_ids, [ {'account_id': st_line.journal_id.default_account_id.id, 'balance': 90.0, 'reconciled': False}, {'account_id': inv.account_id.id, 'balance': -90.0, 'reconciled': True}, ]) inv = self._create_invoice_line( 'out_invoice', partner_id=self.partner_a.id, invoice_date='2020-01-01', invoice_line_ids=[{'price_unit': 100.0}], ) st_line = self._create_st_line( 96.0, date='2020-01-05', partner_id=self.partner_a.id, update_create_date=False, ) st_line.set_line_bank_statement_line(inv.id) # The invoice is fully reconciled, with the surplus on the suspense account self.assertRecordValues(st_line.line_ids, [ {'account_id': st_line.journal_id.default_account_id.id, 'balance': 96.0, 'reconciled': False}, {'account_id': inv.account_id.id, 'balance': -100.0, 'reconciled': True}, {'account_id': st_line.journal_id.suspense_account_id.id, 'balance': 4.0, 'reconciled': False}, ]) st_line.set_account_bank_statement_line(st_line.line_ids[-1].id, self.account_revenue_1.id) reco_model = self.env.ref(f'account.account_reco_model_fee_{st_line.journal_id.id}', raise_if_not_found=False) self.assertTrue(reco_model, "A new reco model for fees should have been created") inv = self._create_invoice_line( 'out_invoice', partner_id=self.partner_a.id, invoice_date='2020-01-01', invoice_line_ids=[{'price_unit': 500.0}], ) st_line = self._create_st_line( 490.0, date='2020-01-05', partner_id=self.partner_a.id, update_create_date=False, ) st_line._try_auto_reconcile_statement_lines() self.assertEqual( st_line.line_ids[-1].reconcile_model_id, reco_model, "The fees reco model should be assigned to a new line that is close to the invoice", ) def test_partial_auto_tolerance_multicurrency(self): self.env['ir.config_parameter'].set_param('account_accountant.bank_rec_payment_tolerance', '0.03') other_currency = self.setup_other_currency('JPY', rates=[('2020-01-01', 10.0), ('2020-01-20', 9.9)]) inv1 = self._create_invoice_line( 'out_invoice', partner_id=self.partner_a.id, currency_id=other_currency.id, invoice_date='2020-01-20', invoice_line_ids=[{'price_unit': 4950.0}], ) st_line = self._create_st_line( 485.0, date='2020-01-01', partner_id=self.partner_a.id, update_create_date=False, ) st_line.set_line_bank_statement_line([inv1.id]) # with the exchange diff, it's not 500 but 495 that is reconciled. And so the invoice is fully paid self.assertRecordValues(st_line.line_ids, [ {'account_id': st_line.journal_id.default_account_id.id, 'amount_currency': 485.0, 'currency_id': self.company_data['currency'].id, 'balance': 485.0, 'reconciled': False}, {'account_id': inv1.account_id.id, 'amount_currency': -4950.0, 'currency_id': other_currency.id, 'balance': -495.0, 'reconciled': True}, {'account_id': st_line.journal_id.suspense_account_id.id, 'amount_currency': 10.0, 'currency_id': self.company_data['currency'].id, 'balance': 10.0, 'reconciled': False}, ]) self.assertEqual(inv1.amount_residual, 0) st_line.set_account_bank_statement_line(st_line.line_ids[-1].id, self.account_revenue_1.id) reco_model = self.env.ref(f'account.account_reco_model_fee_{st_line.journal_id.id}', raise_if_not_found=False) self.assertTrue(reco_model, "A new reco model for fees should have been created") inv1 = self._create_invoice_line( 'out_invoice', partner_id=self.partner_a.id, currency_id=other_currency.id, invoice_date='2020-01-20', invoice_line_ids=[{'price_unit': 4950.0}], ) st_line = self._create_st_line( 485.0, date='2020-01-01', partner_id=self.partner_a.id, payment_ref=inv1.name, update_create_date=False, ) st_line._try_auto_reconcile_statement_lines() self.assertEqual( st_line.line_ids[-1].reconcile_model_id, reco_model, "The fees reco model should be assigned to a new line that is close to the invoice", ) def test_partial_auto_tolerance_st_line_foreign_currency(self): self.env['ir.config_parameter'].set_param('account_accountant.bank_rec_payment_tolerance', '0.03') other_currency = self.setup_other_currency('JPY', rates=[('2020-01-01', 9.5)]) inv1 = self._create_invoice_line( 'out_invoice', partner_id=self.partner_a.id, invoice_date='2020-01-20', invoice_line_ids=[{'price_unit': 495.0}], ) st_line = self._create_st_line( 485.0, date='2020-01-01', foreign_currency_id=other_currency.id, partner_id=self.partner_a.id, update_create_date=False, amount_currency=4900, ) st_line.set_line_bank_statement_line([inv1.id]) # with the exchange diff, it's not 500 but 495 that is reconciled. And so the invoice is fully paid self.assertRecordValues(st_line.line_ids, [ {'account_id': st_line.journal_id.default_account_id.id, 'amount_currency': 485.0, 'currency_id': self.company_data['currency'].id, 'balance': 485.0, 'reconciled': False}, {'account_id': inv1.account_id.id, 'amount_currency': -495.0, 'currency_id': self.company_data['currency'].id, 'balance': -495.0, 'reconciled': True}, {'account_id': st_line.journal_id.suspense_account_id.id, 'amount_currency': 95.0, 'currency_id': other_currency.id, 'balance': 10.0, 'reconciled': False}, ]) self.assertEqual(inv1.amount_residual, 0) st_line.set_account_bank_statement_line(st_line.line_ids[-1].id, self.account_revenue_1.id) reco_model = self.env.ref(f'account.account_reco_model_fee_{st_line.journal_id.id}', raise_if_not_found=False) self.assertTrue(reco_model, "A new reco model for fees should have been created") def test_do_not_tolerate_if_rec_from_invoice(self): self.env['ir.config_parameter'].set_param('account_accountant.bank_rec_payment_tolerance', '0.03') inv_line = self._create_invoice_line( 'out_invoice', partner_id=self.partner_a.id, invoice_date='2020-01-01', invoice_line_ids=[{'price_unit': 500.0}], ) move = inv_line.move_id st_line = self._create_st_line( 490.0, date='2020-01-05', partner_id=self.partner_a.id, update_create_date=False, ) move.js_assign_outstanding_line(st_line.line_ids[0].id) self.assertEqual(move.status_in_payment, 'partial') # Since we don't have the payment tolerance, we have a residual amount on the line and can be added to the statement self.assertEqual(10, inv_line.amount_residual) inv_line = self._create_invoice_line( 'out_invoice', partner_id=self.partner_a.id, invoice_date='2020-01-01', invoice_line_ids=[{'price_unit': 500.0}], ) move = inv_line.move_id st_line = self._create_st_line( 490.0, date='2020-01-05', partner_id=self.partner_a.id, update_create_date=False, ) st_line.set_line_bank_statement_line(inv_line.id) self.assertEqual(move.status_in_payment, 'paid') def test_auto_match_multiple_candidates_tolerance(self): move_line = self._create_invoice_line('out_invoice', invoice_date='2017-01-10', invoice_line_ids=[{'price_unit': 149.5}]) statement_line = self._create_st_line(amount=150, partner_id=self.partner_a.id, date='2017-01-08', update_create_date=False) statement_line._try_auto_reconcile_statement_lines() self.assertFalse(statement_line.line_ids.reconciled_lines_ids) self.env['ir.config_parameter'].set_param('account_accountant.bank_rec_payment_tolerance', '0.03') statement_line = self._create_st_line(amount=150, partner_id=self.partner_a.id, date='2017-01-08', update_create_date=False) statement_line._try_auto_reconcile_statement_lines() self.assertEqual(statement_line.line_ids.reconciled_lines_ids, move_line) def test_exchange_diff_single_currency(self): """ This test will create a new journal with another currencies as the one from the company with a rounding of 1. Then do a statement line in that currency and adding an invoice in that currency aswell, it should not create an exchange diff move """ currency_yen = self.setup_other_currency('JPY', rounding=1.0, rates=[('2017-01-01', 133.62)]) new_journal = self.env['account.journal'].create({ 'name': 'test', 'code': 'TBNK', 'type': 'bank', 'currency_id': currency_yen.id, }) st_line = self._create_st_line(50.0, journal_id=new_journal.id, update_create_date=False) inv_line = self._create_invoice_line('out_invoice', invoice_line_ids=[{'price_unit': 100.0}], currency_id=currency_yen.id) st_line.set_line_bank_statement_line(inv_line.id) self.assertRecordValues(st_line.line_ids, [ {'account_id': st_line.journal_id.default_account_id.id, 'amount_currency': 50.0, 'currency_id': currency_yen.id, 'balance': 0.37, 'reconciled': False}, {'account_id': self.partner_a.property_account_receivable_id.id, 'amount_currency': -50.0, 'currency_id': currency_yen.id, 'balance': -0.37, 'reconciled': True}, ]) self.assertFalse(st_line.line_ids[1].matched_debit_ids.exchange_move_id) def test_multi_currency_with_foreign(self): currency_yen = self.setup_other_currency('JPY', rounding=1.0, rates=[('2017-01-01', 10.00)]) new_journal = self.env['account.journal'].create({ 'name': 'test', 'code': 'TBNK', 'type': 'bank', 'currency_id': currency_yen.id, }) st_line = self._create_st_line( 1000.0, journal_id=new_journal.id, update_create_date=False, foreign_currency_id=self.company_data['currency'].id, ) inv_line = self._create_invoice_line('out_invoice', invoice_line_ids=[{'price_unit': 100.0}], currency_id=self.other_currency.id) st_line.set_line_bank_statement_line(inv_line.id) self.assertRecordValues(st_line.line_ids, [ {'account_id': st_line.journal_id.default_account_id.id, 'amount_currency': 1000.0, 'currency_id': currency_yen.id, 'balance': 100.0, 'reconciled': False}, {'account_id': inv_line.account_id.id, 'amount_currency': -100.0, 'currency_id': self.other_currency.id, 'balance': -50.0, 'reconciled': True}, {'account_id': st_line.journal_id.suspense_account_id.id, 'amount_currency': -50.0, 'currency_id': self.company_data['currency'].id, 'balance': -50.0, 'reconciled': False}, ]) inv_line2 = self._create_invoice_line('out_invoice', invoice_line_ids=[{'price_unit': 50.0}], currency_id=self.company_data['currency'].id) st_line.set_line_bank_statement_line(inv_line2.id) self.assertRecordValues(st_line.line_ids, [ {'account_id': st_line.journal_id.default_account_id.id, 'amount_currency': 1000.0, 'currency_id': currency_yen.id, 'balance': 100.0, 'reconciled': False}, {'account_id': inv_line.account_id.id, 'amount_currency': -100.0, 'currency_id': self.other_currency.id, 'balance': -50.0, 'reconciled': True}, {'account_id': inv_line2.account_id.id, 'amount_currency': -50.0, 'currency_id': self.company_data['currency'].id, 'balance': -50.0, 'reconciled': True}, ]) def test_delete_line_with_reco_model(self): reco_model = self.env['account.reconcile.model'].create({ 'name': 'test reco model', 'match_journal_ids': [Command.set([self.company_data['default_journal_bank'].id])], 'match_label': 'contains', 'match_label_param': 'blblbl', 'line_ids': [Command.create({'account_id': self.company_data['default_account_revenue'].id})], }) st_line = self._create_st_line( 500.0, date='2020-01-01', payment_ref='blblbl', partner_id=self.partner_a.id, update_create_date=False, ) reco_model._apply_reconcile_models(st_line) self.assertEqual( st_line.line_ids[-1].reconcile_model_id, reco_model, "The test reco model should be assigned", ) reco_model._trigger_reconciliation_model(st_line) self.assertRecordValues(st_line.line_ids, [ {'account_id': st_line.journal_id.default_account_id.id, 'amount_currency': 500.0, 'balance': 500.0, 'reconciled': False}, {'account_id': self.company_data['default_account_revenue'].id, 'amount_currency': -500.0, 'balance': -500.0, 'reconciled': False}, ]) st_line.delete_reconciled_line(st_line.line_ids[-1].id) self.assertEqual( st_line.line_ids[-1].reconcile_model_id, reco_model, "The test reco model should be assigned on the new suspense line", ) def test_delete_lines_with_different_reco_model(self): reco_model1 = self.env['account.reconcile.model'].create({ 'name': 'Test reco model', 'match_journal_ids': [Command.set([self.company_data['default_journal_bank'].id])], 'match_label': 'contains', 'match_label_param': 'blblbl', 'line_ids': [Command.create({'account_id': self.company_data['default_account_revenue'].id})], }) reco_model2 = reco_model1.copy() st_line = self._create_st_line( 1000.0, date='2025-08-06', payment_ref='blblbl', partner_id=self.partner_a.id, update_create_date=False, ) reco_model1._trigger_reconciliation_model(st_line) st_line.edit_reconcile_line(st_line.line_ids[-1].id, {'balance': -500, 'amount_currency': -500}) reco_model2._trigger_reconciliation_model(st_line) self.assertRecordValues(st_line.line_ids, [ {'account_id': st_line.journal_id.default_account_id.id, 'amount_currency': 1000.0, 'balance': 1000.0, 'reconciled': False, 'reconcile_model_id': False}, {'account_id': self.company_data['default_account_revenue'].id, 'amount_currency': -500.0, 'balance': -500.0, 'reconciled': False, 'reconcile_model_id': reco_model1.id}, {'account_id': self.company_data['default_account_revenue'].id, 'amount_currency': -500.0, 'balance': -500.0, 'reconciled': False, 'reconcile_model_id': reco_model2.id}, ]) st_line.delete_reconciled_line(st_line.line_ids[-1].id) self.assertEqual( st_line.line_ids[-1].reconcile_model_id.id, reco_model2.id, "The reco model from the deleted line should be assigned on the new suspense line", ) def test_account_default_taxes_of_reco_model(self): reco_model = self.env['account.reconcile.model'].create({ 'name': 'test reco model', 'line_ids': [Command.create({'account_id': self.default_tax_account.id})], }) st_line = self._create_st_line(100.0, update_create_date=False) reco_model._trigger_reconciliation_model(st_line) self.assertRecordValues(st_line.line_ids, [ {'account_id': st_line.journal_id.default_account_id.id, 'tax_ids': [], 'tax_line_id': False, 'balance': 100.0, 'reconciled': False}, {'account_id': self.default_tax_account.id, 'tax_ids': self.default_tax.ids, 'tax_line_id': False, 'balance': -90.0, 'reconciled': False}, {'account_id': self.default_tax_account.id, 'tax_ids': [], 'tax_line_id': self.default_tax.id, 'balance': -10.0, 'reconciled': False}, ]) def test_add_default_tax_of_account_with_set_account(self): st_line = self._create_st_line(200.0, update_create_date=False) st_line.with_context(account_default_taxes=True).set_account_bank_statement_line(st_line.line_ids[-1].id, self.default_tax_account.id) self.assertRecordValues(st_line.line_ids, [ {'account_id': st_line.journal_id.default_account_id.id, 'tax_ids': [], 'tax_line_id': False, 'balance': 200.0, 'reconciled': False}, {'account_id': self.default_tax_account.id, 'tax_ids': self.default_tax.ids, 'tax_line_id': False, 'balance': -190.0, 'reconciled': False}, {'account_id': self.default_tax_account.id, 'tax_ids': [], 'tax_line_id': self.default_tax.id, 'balance': -10.0, 'reconciled': False}, ]) def test_reconcile_invoice_foreign_currency_lost_precision_case(self): """ - Rate set to 1 USD = 5.421327349 R$ - Invoice for a total of 143.62 R$ - Receive a payment for 26.05 USD - Match the payment with the invoice -> The invoice should be fully reconciled. """ self.env['ir.config_parameter'].set_param('account_accountant.bank_rec_payment_tolerance', '0.03') currency_brasilian_real = self.setup_other_currency('BRL', rounding=0.01, rates=[('2017-01-01', 5.421327349)]) receivable_line = self._create_invoice_line('out_invoice', invoice_line_ids=[{'price_unit': 143.62}], currency_id=currency_brasilian_real.id) self.assertEqual(receivable_line.balance, 26.49) st_line = self._create_st_line(amount=26.05) st_line.set_line_bank_statement_line(receivable_line.ids) # Put the rest in exchange difference account exchange_diff_account = self.env.company.income_currency_exchange_account_id st_line.set_account_bank_statement_line(st_line.line_ids[-1].id, exchange_diff_account.id) self.assertRecordValues(st_line.line_ids, [ { 'account_id': st_line.journal_id.default_account_id.id, 'amount_currency': 26.05, 'currency_id': self.company_data['currency'].id, 'reconciled': False, }, { 'account_id': self.partner_a.property_account_receivable_id.id, 'amount_currency': -143.62, 'currency_id': currency_brasilian_real.id, 'reconciled': True, }, { 'account_id': exchange_diff_account.id, 'amount_currency': 0.44, 'currency_id': self.company_data['currency'].id, 'reconciled': False, } ]) self.assertTrue(receivable_line.reconciled, "The invoice should have been marked as reconciled") def test_reconcile_invoice_with_bank_statement_line(self): """ Test reconcile invoice with bank statement line. with foreign currency on both invoice and statement line. """ foreign_curr = self.setup_other_currency('EUR', rates=[ ('2019-06-28', 2.0), ('2019-06-24', 3.0), ]) invoice = self._create_invoice_line(move_type='out_invoice', invoice_date='2019-06-24', currency_id=foreign_curr.id, invoice_line_ids=[{'price_unit': 6000.0}]).move_id invoice_rec_line = invoice.line_ids.filtered(lambda x: x.account_id.account_type == 'asset_receivable') statement_line = self._create_st_line(amount=2000, amount_currency=1000, foreign_currency_id=foreign_curr.id, journal_id=self.company_data['default_journal_bank'].id, partner_id=self.partner_a.id, update_create_date=False) statement_line_rec_line = statement_line.move_id.line_ids.filtered(lambda x: x.account_id.account_type == 'asset_cash') invoice.invalidate_recordset(['invoice_outstanding_credits_debits_widget']) widget_vals = invoice.invoice_outstanding_credits_debits_widget if widget_vals: current_amounts = {vals['move_id']: vals['amount'] for vals in widget_vals['content']} else: current_amounts = {} self.assertDictEqual(current_amounts, { statement_line.move_id.id: 1000.0, }) invoice.js_assign_outstanding_line(statement_line_rec_line.id) statement_line_rec_line = statement_line.line_ids.filtered(lambda x: x.account_id.account_type == 'asset_receivable')[0] partials = (invoice_rec_line.matched_debit_ids | invoice_rec_line.matched_credit_ids | statement_line_rec_line.matched_debit_ids | statement_line_rec_line.matched_credit_ids).sorted() self.assertRecordValues(partials, [ { 'amount': 333.33, 'debit_amount_currency': 1000.0, 'credit_amount_currency': 1000.0, 'debit_move_id': invoice_rec_line.id, 'credit_move_id': statement_line_rec_line.id, }, { 'amount': 1666.67, 'debit_amount_currency': 0.0, 'credit_amount_currency': 0.0, 'debit_move_id': partials.exchange_move_id.line_ids[0].id, 'credit_move_id': statement_line_rec_line.id, }, ]) self.assertRecordValues(invoice_rec_line + statement_line_rec_line, [ {'amount_residual': 1666.67, 'amount_residual_currency': 5000, 'reconciled': False}, {'amount_residual': 0.0, 'amount_residual_currency': 0.0, 'reconciled': True}, ]) self.assert_invoice_outstanding_reconciled_widget(invoice, { statement_line.move_id.id: 1000.0, partials.exchange_move_id.id: 1666.67, }) statement_line.set_account_bank_statement_line(statement_line.line_ids[-1].id, self.company_data['default_account_revenue'].id) self.assertFalse(invoice.invoice_outstanding_credits_debits_widget, "Only statement lines with suspense account should be considered") def test_reconcile_bill_with_account_default_tax(self): """ Test reconciling a bill with the statement line, using the 'set account' button to add an account with tax for the remaining balance. This ensures we keep the bill matching after adding the account with tax. """ tax = self.env['account.tax'].create({ 'name': '10% tax', 'amount_type': 'percent', 'amount': 10.0, 'type_tax_use': 'purchase', }) self.default_tax_account.tax_ids = tax bill = self._create_invoice_line( move_type='in_invoice', invoice_date='2019-06-24', invoice_line_ids=[{'price_unit': 2000.00, 'tax_ids': tax.ids}] ).move_id bill_rec_line = bill.line_ids.filtered(lambda x: x.account_id.account_type == 'liability_payable') statement_line = self._create_st_line(amount=-2530, partner_id=self.partner_a.id) statement_line.set_line_bank_statement_line(bill_rec_line.ids) statement_line.with_context(account_default_taxes=True).set_account_bank_statement_line(statement_line.line_ids[-1].id, self.default_tax_account.id) # Statement line: 2530 # Invoice: 2200 (2000 + 10% tax) # Writeoff base line: 300 # Writeoff tax line: 30 (10% tax) self.assertRecordValues(statement_line.line_ids, [ {'account_id': statement_line.journal_id.default_account_id.id, 'tax_ids': [], 'tax_line_id': False, 'balance': -2530.00, 'reconciled': False, 'reconciled_lines_ids': []}, {'account_id': bill_rec_line.account_id.id, 'tax_ids': [], 'tax_line_id': False, 'balance': 2200.00, 'reconciled': True, 'reconciled_lines_ids': bill_rec_line.ids}, {'account_id': self.default_tax_account.id, 'tax_ids': tax.ids, 'tax_line_id': False, 'balance': 300, 'reconciled': False, 'reconciled_lines_ids': []}, {'account_id': self.default_tax_account.id, 'tax_ids': [], 'tax_line_id': tax.id, 'balance': 30, 'reconciled': False, 'reconciled_lines_ids': []}, ]) def test_reconcile_refund_with_bank_statement_line(self): """ Test reconcile refund with bank statement line. with foreign currency on the refund. """ foreign_curr = self.setup_other_currency('EUR', rates=[ ('2019-06-28', 2.0), ('2019-06-24', 3.0), ]) refund = self._create_invoice_line(move_type='in_invoice', invoice_date='2019-06-24', currency_id=foreign_curr.id, invoice_line_ids=[{'price_unit': 6000.0}]).move_id refund_rec_line = refund.line_ids.filtered(lambda x: x.account_id.account_type == 'liability_payable') statement_line = self._create_st_line(amount=-2000, date='2019-06-28', journal_id=self.company_data['default_journal_bank'].id, partner_id=self.partner_a.id, update_create_date=False) statement_line_rec_line = statement_line.move_id.line_ids.filtered(lambda x: x.account_id.account_type == 'asset_cash') refund.invalidate_recordset(['invoice_outstanding_credits_debits_widget']) widget_vals = refund.invoice_outstanding_credits_debits_widget if widget_vals: current_amounts = {vals['move_id']: vals['amount'] for vals in widget_vals['content']} else: current_amounts = {} self.assertDictEqual(current_amounts, { statement_line.move_id.id: 4000.0, # in refund currency }) refund.js_assign_outstanding_line(statement_line_rec_line.id) statement_line_rec_line = statement_line.line_ids.filtered(lambda x: x.account_id.account_type == 'liability_payable')[0] partials = (refund_rec_line.matched_debit_ids | refund_rec_line.matched_credit_ids | statement_line_rec_line.matched_debit_ids | statement_line_rec_line.matched_credit_ids).sorted() self.assertRecordValues(partials, [ { 'amount': 1333.33, 'debit_amount_currency': 3999.99, 'credit_amount_currency': 3999.99, 'credit_move_id': refund_rec_line.id, 'debit_move_id': statement_line_rec_line.id, }, { 'amount': 666.67, 'debit_amount_currency': 0.0, 'credit_amount_currency': 0.0, 'credit_move_id': partials.exchange_move_id.line_ids[0].id, 'debit_move_id': statement_line_rec_line.id, }, ]) self.assertRecordValues(refund_rec_line + statement_line_rec_line, [ {'amount_residual': -666.67, 'amount_residual_currency': -2000.01, 'reconciled': False}, {'amount_residual': 0.0, 'amount_residual_currency': 0.0, 'reconciled': True}, ]) self.assert_invoice_outstanding_reconciled_widget(refund, { statement_line.move_id.id: 3999.99, partials.exchange_move_id.id: 666.67, }) def test_currency_rate_with_cron(self): """ This test will replicate the use of the cron in multi company with different currencies. For example, when having 2 companies, one in EUR and one in USD. Eventually, the cron will be trigger and do the try_auto_reconcile function with the main company as the self.env.company. This will cause problem in the computation of the amount currency of the suspense line if the statement line is from the other company (due to a wrong rate). """ company_2 = self.company_data_2['company'] company_2.currency_id = self.other_currency new_journal = self.company_data_2['default_journal_bank'] new_journal.currency_id = self.other_currency new_journal.inbound_payment_method_line_ids.payment_account_id = self.inbound_payment_method_line.payment_account_id.copy({'company_ids': [Command.link(company_2.id)]}) payment = self._create_and_post_payment( amount=100, memo="INV/24-25/0001 - pay_AretqwwXerereE", journal_id=new_journal.id, company_id=company_2.id, currency_id=self.company_data['currency'].id, ) st_line = self._create_st_line( amount=1000, payment_ref="pay_AretqwwXerereE", update_create_date=False, journal_id=new_journal.id, company_id=company_2.id, ) # To emulate the launch of the try auto reconcile with the cron environment, we use the with company st_line.set_line_bank_statement_line(payment.move_id.line_ids.filtered(lambda l: l.account_id == payment.outstanding_account_id).ids) self.assertRecordValues(st_line.line_ids, [ {'account_id': st_line.journal_id.default_account_id.id, 'amount_currency': 1000.0, 'currency_id': self.other_currency.id, 'balance': 1000.0, 'reconciled': False}, {'account_id': payment.outstanding_account_id.id, 'amount_currency': -100.0, 'currency_id': self.company_data['currency'].id, 'balance': -100.0, 'reconciled': True}, {'account_id': st_line.journal_id.suspense_account_id.id, 'amount_currency': -900.0, 'currency_id': self.other_currency.id, 'balance': -900.0, 'reconciled': False}, ]) def test_reconcile_payment_widget_vals_partials(self): foreign_curr = self.setup_other_currency('EUR', rates=[ ('2019-06-28', 2.0), ]) foreign_currency_journal = self.company_data['default_journal_bank'].copy({'currency_id': foreign_curr.id}) statement_line = self.env['account.bank.statement.line'].create({ 'name': 'test_statement', 'date': '2019-06-28', 'payment_ref': 'line_1', 'partner_id': self.partner_a.id, 'journal_id': foreign_currency_journal.id, 'amount': 400.0, }) statement_line_rec_line = statement_line.move_id.line_ids.filtered( lambda x: x.account_id.account_type == 'asset_cash') invoice_1 = self.init_invoice(move_type='out_invoice', invoice_date='2019-06-24', amounts=[100], partner=self.partner_a, post=True) invoice_2 = self.init_invoice(move_type='out_invoice', invoice_date='2019-06-24', amounts=[100], partner=self.partner_a, post=True) invoice_1.js_assign_outstanding_line(statement_line_rec_line.id) self.assert_invoice_outstanding_to_reconcile_widget(invoice_2, { statement_line.move_id.id: 100.0, # 400/2 (rate conversion) - 100 }) invoice_2.js_assign_outstanding_line(statement_line_rec_line.id) invoice_3 = self.init_invoice(move_type='out_invoice', invoice_date='2019-06-24', amounts=[100], partner=self.partner_a, post=True) self.assert_invoice_outstanding_to_reconcile_widget(invoice_3, {}) # nothing is available for invoice_3 partial_id = invoice_1._get_all_reconciled_invoice_partials()[0].get('partial_id') statement_line_rec_line.move_id.js_remove_outstanding_partial(partial_id) # 100 has been made available again self.assert_invoice_outstanding_to_reconcile_widget(invoice_1, { statement_line.move_id.id: 100.0, }) # invoice_2 is still reconciled self.assert_invoice_outstanding_reconciled_widget(invoice_2, { statement_line.move_id.id: 100, }) def test_remove_partner_from_set_account_line(self): st_line = self._create_st_line(100.0, update_create_date=False, partner_id=self.partner_a.id) st_line.set_account_bank_statement_line(st_line.line_ids[-1].id, self.account_revenue_1.id) self.assertRecordValues(st_line.line_ids, [ {'account_id': st_line.journal_id.default_account_id.id, 'partner_id': self.partner_a.id, 'balance': 100.0, 'reconciled': False}, {'account_id': self.account_revenue_1.id, 'partner_id': self.partner_a.id, 'balance': -100.0, 'reconciled': False}, ]) st_line.edit_reconcile_line(st_line.line_ids[-1].id, {'balance': -20, 'amount_currency': -20}) st_line.set_account_bank_statement_line(st_line.line_ids[-1].id, self.account_revenue_1.id) st_line.edit_reconcile_line(st_line.line_ids[-1].id, {'balance': -20, 'amount_currency': -20}) st_line.set_account_bank_statement_line(st_line.line_ids[-1].id, self.account_revenue_1.id) self.assertRecordValues(st_line.line_ids, [ {'account_id': st_line.journal_id.default_account_id.id, 'partner_id': self.partner_a.id, 'balance': 100.0}, {'account_id': self.account_revenue_1.id, 'partner_id': self.partner_a.id, 'balance': -20.0}, {'account_id': self.account_revenue_1.id, 'partner_id': self.partner_a.id, 'balance': -20.0}, {'account_id': self.account_revenue_1.id, 'partner_id': self.partner_a.id, 'balance': -60.0}, ]) st_line.edit_reconcile_line(st_line.line_ids[-1].id, {'partner_id': False}) self.assertRecordValues(st_line.line_ids, [ {'account_id': st_line.journal_id.default_account_id.id, 'partner_id': self.partner_a.id, 'balance': 100.0}, {'account_id': self.account_revenue_1.id, 'partner_id': self.partner_a.id, 'balance': -20.0}, {'account_id': self.account_revenue_1.id, 'partner_id': self.partner_a.id, 'balance': -20.0}, {'account_id': self.account_revenue_1.id, 'partner_id': False, 'balance': -60.0}, ]) st_line.edit_reconcile_line(st_line.line_ids[2].id, {'partner_id': False}) self.assertRecordValues(st_line.line_ids, [ {'account_id': st_line.journal_id.default_account_id.id, 'partner_id': self.partner_a.id, 'balance': 100.0}, {'account_id': self.account_revenue_1.id, 'partner_id': self.partner_a.id, 'balance': -20.0}, {'account_id': self.account_revenue_1.id, 'partner_id': False, 'balance': -60.0}, {'account_id': self.account_revenue_1.id, 'partner_id': False, 'balance': -20.0}, ]) def test_analytic_distribution_for_early_payment_statement(self): """ Test that the analytic is applied for statement with early discount """ early_payment_term = self.env['account.payment.term'].create({ 'name': "early_payment_term", 'company_id': self.company_data['company'].id, 'early_pay_discount_computation': 'included', 'early_discount': True, 'discount_percentage': 2, 'discount_days': 7, 'line_ids': [ Command.create({ 'value': 'percent', 'value_amount': 100.0, 'nb_days': 30, }), ], }) analytic_plan = self.env['account.analytic.plan'].create({ 'name': 'existential plan', }) analytic_account = self.env['account.analytic.account'].create({ 'name': 'positive_account', 'plan_id': analytic_plan.id, }) cash_discount_account = self.company_data['company'].account_journal_early_pay_discount_loss_account_id self.env['account.analytic.distribution.model'].create({ 'account_prefix': cash_discount_account.code, 'analytic_distribution': {analytic_account.id: 100.0}, }) invoice = self.env['account.move'].create([{ 'move_type': 'out_invoice', 'partner_id': self.partner_a.id, 'date': '2017-01-01', 'invoice_date': '2017-01-01', 'invoice_payment_term_id': early_payment_term.id, 'invoice_line_ids': [Command.create({ 'name': 'line', 'price_unit': 100.0, })] }]) invoice.action_post() statement = self.env['account.bank.statement.line'].create({ 'date': '2017-01-01', 'payment_ref': invoice.payment_reference, 'partner_id': self.partner_b.id, 'amount': 98.0, 'journal_id': self.company_data['default_journal_bank'].id, }) receivable_line = invoice.line_ids.filtered( lambda l: l.account_id.account_type == 'asset_receivable' ) statement.set_line_bank_statement_line(receivable_line.ids) early_payment_line = statement.line_ids.filtered(lambda p: p.balance == 2.0) self.assertTrue(early_payment_line.analytic_distribution) def test_edit_account_id_analytic(self): analytic_plan = self.env['account.analytic.plan'].create({'name': 'Plan Test'}) analytic_account_1 = self.env['account.analytic.account'].create({ 'name': 'analytic_account_1', 'plan_id': analytic_plan.id, }) analytic_distribution = self.env['account.analytic.distribution.model'].create({ 'analytic_distribution': {analytic_account_1.id: 100}, }) account = self.env['account.account'].create({ 'name': "test account", 'code': "60001", 'account_type': "expense", }) st_line = self._create_st_line(amount=100, update_create_date=False) st_line.edit_reconcile_line(st_line.line_ids[-1].id, { 'analytic_distribution': analytic_distribution.analytic_distribution, }) old_analytic_line = st_line.line_ids[1].analytic_line_ids self.assertEqual(len(old_analytic_line), 1) self.assertTrue(old_analytic_line) st_line.edit_reconcile_line(st_line.line_ids[-1].id, { 'account_id': account.id, }) self.assertNotEqual(old_analytic_line, st_line.line_ids[1].analytic_line_ids) self.assertEqual(len(st_line.line_ids[1].analytic_line_ids), 1) def test_reconciliation_without_payment_account(self): """Test reconciliation when there is no payment account on the payment method.""" # make sure that no payment account is set on the payment method lines self.company_data['default_journal_bank'].inbound_payment_method_line_ids.payment_account_id = False self.company_data['default_journal_bank'].outbound_payment_method_line_ids.payment_account_id = False self._create_and_post_payment(amount=100) statement_line = self._create_st_line(amount=100, update_create_date=False) statement_line._try_auto_reconcile_statement_lines() self.assertRecordValues(statement_line.line_ids, [ {'account_id': self.company_data['default_journal_bank'].default_account_id.id, 'balance': 100.0, 'reconciled': False}, {'account_id': self.company_data['default_journal_bank'].suspense_account_id.id, 'balance': -100.0, 'reconciled': False}, ]) def test_set_account_then_edit_with_taxes(self): st_line = self._create_st_line(200.0, update_create_date=False) st_line.with_context(account_default_taxes=True).set_account_bank_statement_line(st_line.line_ids[-1].id, self.account_revenue_1.id) st_line.edit_reconcile_line(st_line.line_ids[-1].id, { 'tax_ids': [Command.link(self.default_tax.id)], }) self.assertRecordValues(st_line.line_ids, [ {'account_id': st_line.journal_id.default_account_id.id, 'tax_ids': [], 'tax_line_id': False, 'balance': 200.0, 'reconciled': False}, {'account_id': self.account_revenue_1.id, 'tax_ids': self.default_tax.ids, 'tax_line_id': False, 'balance': -190.0, 'reconciled': False}, {'account_id': self.account_revenue_1.id, 'tax_ids': [], 'tax_line_id': self.default_tax.id, 'balance': -10.0, 'reconciled': False}, ]) def test_delete_line_with_taxes(self): st_line = self._create_st_line(200.0, update_create_date=False) st_line.with_context(account_default_taxes=True).set_account_bank_statement_line(st_line.line_ids[-1].id, self.default_tax_account.id) self.assertRecordValues(st_line.line_ids, [ {'account_id': st_line.journal_id.default_account_id.id, 'tax_ids': [], 'tax_line_id': False, 'balance': 200.0, 'reconciled': False}, {'account_id': self.default_tax_account.id, 'tax_ids': self.default_tax.ids, 'tax_line_id': False, 'balance': -190.0, 'reconciled': False}, {'account_id': self.default_tax_account.id, 'tax_ids': [], 'tax_line_id': self.default_tax.id, 'balance': -10.0, 'reconciled': False}, ]) # In the ui we cannot delete tax line, so we will delete the line link to the tax st_line.delete_reconciled_line(st_line.line_ids[-2].id) self.assertRecordValues(st_line.line_ids, [ {'account_id': st_line.journal_id.default_account_id.id, 'tax_ids': [], 'tax_line_id': False, 'balance': 200.0, 'reconciled': False}, {'account_id': st_line.journal_id.suspense_account_id.id, 'tax_ids': [], 'tax_line_id': False, 'balance': -200.0, 'reconciled': False}, ]) def test_delete_line_with_taxes_with_multiple_lines(self): st_line = self._create_st_line(200.0, update_create_date=False) self.default_tax_account.tax_ids = False st_line.with_context(account_default_taxes=True).set_account_bank_statement_line(st_line.line_ids[-1].id, self.default_tax_account.id) st_line.edit_reconcile_line(st_line.line_ids[-1].id, {'balance': -100, 'amount_currency': -100, 'tax_ids': [Command.link(self.default_tax.id)]}) self.assertRecordValues(st_line.line_ids, [ {'account_id': st_line.journal_id.default_account_id.id, 'tax_ids': [], 'tax_line_id': False, 'balance': 200.0, 'reconciled': False}, {'account_id': self.default_tax_account.id, 'tax_ids': self.default_tax.ids, 'tax_line_id': False, 'balance': -90.0, 'reconciled': False}, {'account_id': self.default_tax_account.id, 'tax_ids': [], 'tax_line_id': self.default_tax.id, 'balance': -10.0, 'reconciled': False}, {'account_id': st_line.journal_id.suspense_account_id.id, 'tax_ids': [], 'tax_line_id': False, 'balance': -100.0, 'reconciled': False}, ]) st_line.with_context(account_default_taxes=True).set_account_bank_statement_line(st_line.line_ids[-1].id, self.default_tax_account.id) st_line.edit_reconcile_line(st_line.line_ids[-1].id, {'tax_ids': [Command.link(self.default_tax.id)]}) self.assertRecordValues(st_line.line_ids, [ {'account_id': st_line.journal_id.default_account_id.id, 'tax_ids': [], 'tax_line_id': False, 'balance': 200.0, 'reconciled': False}, {'account_id': self.default_tax_account.id, 'tax_ids': self.default_tax.ids, 'tax_line_id': False, 'balance': -90.0, 'reconciled': False}, {'account_id': self.default_tax_account.id, 'tax_ids': self.default_tax.ids, 'tax_line_id': False, 'balance': -90.0, 'reconciled': False}, {'account_id': self.default_tax_account.id, 'tax_ids': [], 'tax_line_id': self.default_tax.id, 'balance': -20.0, 'reconciled': False}, ]) # In the ui we cannot delete tax line, so we will delete the line link to the tax st_line.delete_reconciled_line(st_line.line_ids[-2].id) self.assertRecordValues(st_line.line_ids, [ {'account_id': st_line.journal_id.default_account_id.id, 'tax_ids': [], 'tax_line_id': False, 'balance': 200.0, 'reconciled': False}, {'account_id': self.default_tax_account.id, 'tax_ids': self.default_tax.ids, 'tax_line_id': False, 'balance': -90.0, 'reconciled': False}, {'account_id': self.default_tax_account.id, 'tax_ids': [], 'tax_line_id': self.default_tax.id, 'balance': -10.0, 'reconciled': False}, {'account_id': st_line.journal_id.suspense_account_id.id, 'tax_ids': [], 'tax_line_id': False, 'balance': -100.0, 'reconciled': False}, ]) def test_set_account_then_edit_remove_taxes(self): st_line = self._create_st_line(200.0, update_create_date=False) st_line.with_context(account_default_taxes=True).set_account_bank_statement_line(st_line.line_ids[-1].id, self.default_tax_account.id) self.assertRecordValues(st_line.line_ids, [ {'account_id': st_line.journal_id.default_account_id.id, 'tax_ids': [], 'tax_line_id': False, 'balance': 200.0, 'reconciled': False}, {'account_id': self.default_tax_account.id, 'tax_ids': self.default_tax.ids, 'tax_line_id': False, 'balance': -190.0, 'reconciled': False}, {'account_id': self.default_tax_account.id, 'tax_ids': [], 'tax_line_id': self.default_tax.id, 'balance': -10.0, 'reconciled': False}, ]) st_line.edit_reconcile_line(st_line.line_ids[-2].id, {'tax_ids': [Command.unlink(self.default_tax.id)]}) self.assertRecordValues(st_line.line_ids, [ {'account_id': st_line.journal_id.default_account_id.id, 'tax_ids': [], 'tax_line_id': False, 'balance': 200.0, 'reconciled': False}, {'account_id': self.default_tax_account.id, 'tax_ids': [], 'tax_line_id': False, 'balance': -200.0, 'reconciled': False}, ]) def test_set_account_then_edit_multiple_taxes(self): new_tax = self.env['account.tax'].create({ 'name': 'new_tax', 'amount_type': 'fixed', 'amount': 20.0, }) st_line = self._create_st_line(200.0, update_create_date=False) st_line.with_context(account_default_taxes=True).set_account_bank_statement_line(st_line.line_ids[-1].id, self.default_tax_account.id) self.assertRecordValues(st_line.line_ids, [ {'account_id': st_line.journal_id.default_account_id.id, 'tax_ids': [], 'tax_line_id': False, 'balance': 200.0, 'reconciled': False}, {'account_id': self.default_tax_account.id, 'tax_ids': self.default_tax.ids, 'tax_line_id': False, 'balance': -190.0, 'reconciled': False}, {'account_id': self.default_tax_account.id, 'tax_ids': [], 'tax_line_id': self.default_tax.id, 'balance': -10.0, 'reconciled': False}, ]) st_line.edit_reconcile_line(st_line.line_ids[-2].id, {'tax_ids': [Command.link(new_tax.id)]}) self.assertRecordValues(st_line.line_ids, [ {'account_id': st_line.journal_id.default_account_id.id, 'tax_ids': [], 'tax_line_id': False, 'balance': 200.0, 'reconciled': False}, {'account_id': self.default_tax_account.id, 'tax_ids': (self.default_tax + new_tax).ids, 'tax_line_id': False, 'balance': -170.0, 'reconciled': False}, {'account_id': self.default_tax_account.id, 'tax_ids': [], 'tax_line_id': new_tax.id, 'balance': -20.0, 'reconciled': False}, {'account_id': self.default_tax_account.id, 'tax_ids': [], 'tax_line_id': self.default_tax.id, 'balance': -10.0, 'reconciled': False}, ]) def test_set_account_multiple_taxes_delete_line(self): new_tax = self.env['account.tax'].create({ 'name': 'new_tax', 'amount_type': 'fixed', 'amount': 20.0, }) self.default_tax_account.tax_ids = [Command.set((self.default_tax + new_tax).ids)] st_line = self._create_st_line(200.0, update_create_date=False) st_line.with_context(account_default_taxes=True).set_account_bank_statement_line(st_line.line_ids[-1].id, self.default_tax_account.id) self.assertRecordValues(st_line.line_ids, [ {'account_id': st_line.journal_id.default_account_id.id, 'tax_ids': [], 'tax_line_id': False, 'balance': 200.0, 'reconciled': False}, {'account_id': self.default_tax_account.id, 'tax_ids': (self.default_tax + new_tax).ids, 'tax_line_id': False, 'balance': -170.0, 'reconciled': False}, {'account_id': self.default_tax_account.id, 'tax_ids': [], 'tax_line_id': self.default_tax.id, 'balance': -10.0, 'reconciled': False}, {'account_id': self.default_tax_account.id, 'tax_ids': [], 'tax_line_id': new_tax.id, 'balance': -20.0, 'reconciled': False}, ]) st_line.edit_reconcile_line(st_line.line_ids[1].id, {'tax_ids': [Command.unlink(self.default_tax.id)]}) self.assertRecordValues(st_line.line_ids, [ {'account_id': st_line.journal_id.default_account_id.id, 'tax_ids': [], 'tax_line_id': False, 'balance': 200.0, 'reconciled': False}, {'account_id': self.default_tax_account.id, 'tax_ids': new_tax.ids, 'tax_line_id': False, 'balance': -180.0, 'reconciled': False}, {'account_id': self.default_tax_account.id, 'tax_ids': [], 'tax_line_id': new_tax.id, 'balance': -20.0, 'reconciled': False}, ]) def test_set_account_then_edit_remove_taxes_and_add_new_tax(self): new_tax = self.env['account.tax'].create({ 'name': 'new_tax', 'amount_type': 'fixed', 'amount': 20.0, }) st_line = self._create_st_line(200.0, update_create_date=False) st_line.with_context(account_default_taxes=True).set_account_bank_statement_line(st_line.line_ids[-1].id, self.default_tax_account.id) self.assertRecordValues(st_line.line_ids, [ {'account_id': st_line.journal_id.default_account_id.id, 'tax_ids': [], 'tax_line_id': False, 'balance': 200.0, 'reconciled': False}, {'account_id': self.default_tax_account.id, 'tax_ids': self.default_tax.ids, 'tax_line_id': False, 'balance': -190.0, 'reconciled': False}, {'account_id': self.default_tax_account.id, 'tax_ids': [], 'tax_line_id': self.default_tax.id, 'balance': -10.0, 'reconciled': False}, ]) st_line.edit_reconcile_line(st_line.line_ids[1].id, {'tax_ids': [Command.unlink(self.default_tax.id), Command.set(new_tax.ids)]}) self.assertRecordValues(st_line.line_ids, [ {'account_id': st_line.journal_id.default_account_id.id, 'tax_ids': [], 'tax_line_id': False, 'balance': 200.0, 'reconciled': False}, {'account_id': self.default_tax_account.id, 'tax_ids': new_tax.ids, 'tax_line_id': False, 'balance': -180.0, 'reconciled': False}, {'account_id': self.default_tax_account.id, 'tax_ids': [], 'tax_line_id': new_tax.id, 'balance': -20.0, 'reconciled': False}, ]) def test_set_account_then_edit_remove_taxes_and_add_new_taxes(self): new_tax = self.env['account.tax'].create({ 'name': 'new_tax', 'amount_type': 'fixed', 'amount': 20.0, }) st_line = self._create_st_line(200.0, update_create_date=False) st_line.with_context(account_default_taxes=True).set_account_bank_statement_line(st_line.line_ids[-1].id, self.default_tax_account.id) self.assertRecordValues(st_line.line_ids, [ {'account_id': st_line.journal_id.default_account_id.id, 'tax_ids': [], 'tax_line_id': False, 'balance': 200.0, 'reconciled': False}, {'account_id': self.default_tax_account.id, 'tax_ids': self.default_tax.ids, 'tax_line_id': False, 'balance': -190.0, 'reconciled': False}, {'account_id': self.default_tax_account.id, 'tax_ids': [], 'tax_line_id': self.default_tax.id, 'balance': -10.0, 'reconciled': False}, ]) st_line.edit_reconcile_line(st_line.line_ids[1].id, {'tax_ids': [Command.unlink(self.default_tax.id), Command.set((new_tax + self.default_tax).ids)]}) self.assertRecordValues(st_line.line_ids, [ {'account_id': st_line.journal_id.default_account_id.id, 'tax_ids': [], 'tax_line_id': False, 'balance': 200.0, 'reconciled': False}, {'account_id': self.default_tax_account.id, 'tax_ids': (self.default_tax + new_tax).ids, 'tax_line_id': False, 'balance': -170.0, 'reconciled': False}, {'account_id': self.default_tax_account.id, 'tax_ids': [], 'tax_line_id': new_tax.id, 'balance': -20.0, 'reconciled': False}, {'account_id': self.default_tax_account.id, 'tax_ids': [], 'tax_line_id': self.default_tax.id, 'balance': -10.0, 'reconciled': False}, ]) def test_multiple_tax_with_different_types(self): sale_tax = self.env['account.tax'].create({ 'name': '10% S', 'amount': 10.0, 'amount_type': 'percent', 'type_tax_use': 'sale', }) purchase_tax = self.env['account.tax'].create({ 'name': '10% P', 'amount': 10.0, 'amount_type': 'percent', 'type_tax_use': 'purchase', }) st_line = self._create_st_line(100.0, update_create_date=False) st_line.with_context(account_default_taxes=True).set_account_bank_statement_line(st_line.line_ids[-1].id, self.account_revenue_1.id) st_line.edit_reconcile_line(st_line.line_ids[-1].id, { 'tax_ids': [Command.link(purchase_tax.id), Command.link(sale_tax.id)], }) self.assertRecordValues(st_line.line_ids, [ {'account_id': st_line.journal_id.default_account_id.id, 'tax_ids': [], 'tax_line_id': False, 'balance': 100.0, 'reconciled': False}, {'account_id': self.account_revenue_1.id, 'tax_ids': (purchase_tax + sale_tax).ids, 'tax_line_id': False, 'balance': -83.34, 'reconciled': False}, {'account_id': self.account_revenue_1.id, 'tax_ids': [], 'tax_line_id': sale_tax.id, 'balance': -8.33, 'reconciled': False}, {'account_id': self.account_revenue_1.id, 'tax_ids': [], 'tax_line_id': purchase_tax.id, 'balance': -8.33, 'reconciled': False}, ]) st_line.edit_reconcile_line(st_line.line_ids[1].id, { 'tax_ids': [Command.unlink(purchase_tax.id)], }) self.assertRecordValues(st_line.line_ids, [ {'account_id': st_line.journal_id.default_account_id.id, 'tax_ids': [], 'tax_line_id': False, 'balance': 100.0, 'reconciled': False}, {'account_id': self.account_revenue_1.id, 'tax_ids': sale_tax.ids, 'tax_line_id': False, 'balance': -90.91, 'reconciled': False}, {'account_id': self.account_revenue_1.id, 'tax_ids': [], 'tax_line_id': sale_tax.id, 'balance': -9.09, 'reconciled': False}, ]) def test_upload_xml(self): xml = b""" urn:cen.eu:en16931:2017#compliant#urn:fdc:peppol.eu:2017:poacc:billing:3.0 urn:fdc:peppol.eu:2017:poacc:billing:01:1.0 INV/2023/00009 2023-06-20 2023-06-20 380 Terms and condition USD DOEO INV_2023_00009.pdf BE0246697724 BE Company CoA 1021 Sint-Bernardsesteenweg Antwerpen 2660 BE BE0246697724 VAT BE Company CoA BE0246697724 BE Company CoA +32 470 12 34 56 info@company.beexample.com BE0665978937 Proximus BE BE0665978937 VAT Proximus BE0665978937 Proximus BE 30 +++000/0000/22329+++ BE83957821438115 100.00 100.00 100.00 0.00 100.00 984 1.0 100.00 [FURN_6666] Acoustic Bloc Screens Acoustic Bloc Screens FURN_6666 S 21.0 VAT 100.00 """ statement_line = self._create_st_line(amount=-100, update_create_date=False) attachment = self.env['ir.attachment'].create({ 'name': 'test_file', 'mimetype': 'text/xml', 'datas': base64.b64encode(xml), }) self.env['account.bank.statement.line'].with_context( statement_line_id=statement_line.id ).create_document_from_attachment(attachment.ids) self.assertRecordValues(statement_line.line_ids, [ {'account_id': self.company_data['default_journal_bank'].default_account_id.id, 'balance': -100.0, 'reconciled': False}, {'account_id': self.company_data['default_account_payable'].id, 'balance': 100.0, 'reconciled': True}, ]) def test_auto_activate_currency_with_xml_file(self): xml = b""" urn:cen.eu:en16931:2017#compliant#urn:fdc:peppol.eu:2017:poacc:billing:3.0 urn:fdc:peppol.eu:2017:poacc:billing:01:1.0 INV/2023/00009 2023-06-20 2023-06-20 380 Terms and condition INR DOEO INV_2023_00009.pdf BE0246697724 BE Company CoA 1021 Sint-Bernardsesteenweg Antwerpen 2660 BE BE0246697724 VAT BE Company CoA BE0246697724 BE Company CoA +32 470 12 34 56 info@company.beexample.com BE0665978937 Proximus BE BE0665978937 VAT Proximus BE0665978937 Proximus BE 30 +++000/0000/22329+++ BE83957821438115 100.00 100.00 100.00 0.00 100.00 984 1.0 100.00 [FURN_6666] Acoustic Bloc Screens Acoustic Bloc Screens FURN_6666 S 21.0 VAT 100.00 """ statement_line = self._create_st_line(amount=-100, update_create_date=False) attachment = self.env['ir.attachment'].create({ 'name': 'test_file', 'mimetype': 'text/xml', 'datas': base64.b64encode(xml), }) test_currency = self.setup_other_currency('INR', rates=[('2025-01-01', 1.00)], active=False) self.assertFalse(test_currency.active) self.env['account.bank.statement.line'].with_context( statement_line_id=statement_line.id, ).create_document_from_attachment(attachment.ids) self.assertRecordValues(statement_line.line_ids, [ {'account_id': self.company_data['default_journal_bank'].default_account_id.id, 'balance': -100.0, 'reconciled': False}, {'account_id': self.company_data['default_account_payable'].id, 'balance': 100.0, 'reconciled': True}, ]) self.assertTrue(test_currency.active) def test_reconciliation_with_payment_terms(self): """ Test the scenario where a transaction is already fully consumed before the last installment of the payment term. When adding the invoice lines from the bank reconciliation widget, they should all be added despite the lower amount on the statement line. When adding the statement line from the invoice, the reconciliation should stop if the statement line amount is consumed, partially paying the invoice. """ payment_term = self.env['account.payment.term'].create({ 'name': 'Test payment term', 'company_id': self.company_data['company'].id, 'line_ids': [ Command.create({ 'value': 'percent', 'value_amount': amount, 'nb_days': nb_days, }) for amount, nb_days in [(33.33, 0), (33.33, 30), (33.34, 60)] ], }) payment_term_2 = self.env['account.payment.term'].create({ 'name': 'Test payment term', 'company_id': self.company_data['company'].id, 'line_ids': [ Command.create({ 'value': 'percent', 'value_amount': amount, 'nb_days': nb_days, }) for amount, nb_days in [(50.0, 0), (50.0, 30)] ], }) suspense_account = self.company_data['default_journal_bank'].suspense_account_id # Case 1: No installments statement_line = self._create_st_line(amount=-90, update_create_date=False) move_line_1 = self._create_invoice_line('in_invoice', invoice_line_ids=[{'price_unit': 200.0}]) statement_line.set_line_bank_statement_line(move_line_1.ids) self.assertRecordValues(statement_line.line_ids, [ {'account_id': statement_line.journal_id.default_account_id.id, 'amount_currency': -90.0, 'currency_id': self.company_data['currency'].id, 'balance': -90.0, 'reconciled': False}, {'account_id': move_line_1.account_id.id, 'amount_currency': 90.0, 'currency_id': self.company_data['currency'].id, 'balance': 90.0, 'reconciled': True}, ]) # Case 2: 2 installments ## Case 2.1: Adding from the bank rec widget statement_line = self._create_st_line(amount=-90, update_create_date=False) move_line_1 = self._create_invoice_line('in_invoice', invoice_payment_term_id=payment_term_2.id, invoice_line_ids=[{'price_unit': 200.0}]) statement_line.set_line_bank_statement_line(move_line_1.ids) self.assertRecordValues(statement_line.line_ids, [ {'account_id': statement_line.journal_id.default_account_id.id, 'amount_currency': -90.0, 'currency_id': self.company_data['currency'].id, 'balance': -90.0, 'reconciled': False}, {'account_id': move_line_1.account_id.id, 'amount_currency': 100.0, 'currency_id': self.company_data['currency'].id, 'balance': 100.0, 'reconciled': True}, {'account_id': move_line_1.account_id.id, 'amount_currency': 100.0, 'currency_id': self.company_data['currency'].id, 'balance': 100.0, 'reconciled': True}, {'account_id': suspense_account.id, 'amount_currency': -110.0, 'currency_id': self.company_data['currency'].id, 'balance': -110.0, 'reconciled': False}, ]) ## Case 2.2: Adding from the invoice statement_line = self._create_st_line(amount=-90, update_create_date=False) move_line_1 = self._create_invoice_line('in_invoice', invoice_payment_term_id=payment_term_2.id, invoice_line_ids=[{'price_unit': 200.0}]) move_line_1.move_id.js_assign_outstanding_line(statement_line.line_ids[0].id) self.assertRecordValues(statement_line.line_ids, [ {'account_id': statement_line.journal_id.default_account_id.id, 'amount_currency': -90.0, 'currency_id': self.company_data['currency'].id, 'balance': -90.0, 'reconciled': False}, {'account_id': move_line_1.account_id.id, 'amount_currency': 90.0, 'currency_id': self.company_data['currency'].id, 'balance': 90.0, 'reconciled': True}, ]) ## Case 2.3: Adding from the invoice, with just enough for the first installment to be fully paid statement_line = self._create_st_line(amount=-100, update_create_date=False) move_line_1 = self._create_invoice_line('in_invoice', invoice_payment_term_id=payment_term_2.id, invoice_line_ids=[{'price_unit': 200.0}]) move_line_1.move_id.js_assign_outstanding_line(statement_line.line_ids[0].id) self.assertRecordValues(statement_line.line_ids, [ {'account_id': statement_line.journal_id.default_account_id.id, 'amount_currency': -100.0, 'currency_id': self.company_data['currency'].id, 'balance': -100.0, 'reconciled': False}, {'account_id': move_line_1.account_id.id, 'amount_currency': 100.0, 'currency_id': self.company_data['currency'].id, 'balance': 100.0, 'reconciled': True}, ]) # Case 3: 3 installments ## Case 3.1: Adding from the bank rec widget statement_line = self._create_st_line(amount=-90, update_create_date=False) move_line_1 = self._create_invoice_line('in_invoice', invoice_payment_term_id=payment_term.id, invoice_line_ids=[{'price_unit': 200.0}]) statement_line.set_line_bank_statement_line(move_line_1.ids) self.assertRecordValues(statement_line.line_ids, [ {'account_id': statement_line.journal_id.default_account_id.id, 'amount_currency': -90.0, 'currency_id': self.company_data['currency'].id, 'balance': -90.0, 'reconciled': False}, {'account_id': move_line_1.account_id.id, 'amount_currency': 66.66, 'currency_id': self.company_data['currency'].id, 'balance': 66.66, 'reconciled': True}, {'account_id': move_line_1.account_id.id, 'amount_currency': 66.66, 'currency_id': self.company_data['currency'].id, 'balance': 66.66, 'reconciled': True}, {'account_id': move_line_1.account_id.id, 'amount_currency': 66.68, 'currency_id': self.company_data['currency'].id, 'balance': 66.68, 'reconciled': True}, {'account_id': suspense_account.id, 'amount_currency': -110.0, 'currency_id': self.company_data['currency'].id, 'balance': -110.0, 'reconciled': False}, ]) ## Case 3.2: Adding from the invoice statement_line = self._create_st_line(amount=-90, update_create_date=False) move_line_1 = self._create_invoice_line('in_invoice', invoice_payment_term_id=payment_term.id, invoice_line_ids=[{'price_unit': 200.0}]) move_line_1.move_id.js_assign_outstanding_line(statement_line.line_ids[0].id) self.assertRecordValues(statement_line.line_ids, [ {'account_id': statement_line.journal_id.default_account_id.id, 'amount_currency': -90.0, 'currency_id': self.company_data['currency'].id, 'balance': -90.0, 'reconciled': False}, {'account_id': move_line_1.account_id.id, 'amount_currency': 66.66, 'currency_id': self.company_data['currency'].id, 'balance': 66.66, 'reconciled': True}, {'account_id': move_line_1.account_id.id, 'amount_currency': 23.34, 'currency_id': self.company_data['currency'].id, 'balance': 23.34, 'reconciled': True}, ]) # Case 4: 3 installments with foreign currency on invoice ## Case 4.1: Adding from the bank rec widget statement_line_2 = self._create_st_line(amount=-90, update_create_date=False) move_line_2 = self._create_invoice_line('in_invoice', currency_id=self.other_currency.id, invoice_payment_term_id=payment_term.id, invoice_line_ids=[{'price_unit': 400.0}]) statement_line_2.set_line_bank_statement_line(move_line_2.ids) self.assertRecordValues(statement_line_2.line_ids, [ {'account_id': statement_line_2.journal_id.default_account_id.id, 'amount_currency': -90.0, 'currency_id': self.company_data['currency'].id, 'balance': -90.0, 'reconciled': False}, {'account_id': move_line_2.account_id.id, 'amount_currency': 133.32, 'currency_id': self.other_currency.id, 'balance': 66.66, 'reconciled': True}, {'account_id': move_line_2.account_id.id, 'amount_currency': 133.32, 'currency_id': self.other_currency.id, 'balance': 66.66, 'reconciled': True}, {'account_id': move_line_2.account_id.id, 'amount_currency': 133.36, 'currency_id': self.other_currency.id, 'balance': 66.68, 'reconciled': True}, {'account_id': suspense_account.id, 'amount_currency': -110.0, 'currency_id': self.company_data['currency'].id, 'balance': -110.0, 'reconciled': False}, ]) ## Case 4.2: Adding from the invoice statement_line_2 = self._create_st_line(amount=-90, update_create_date=False) move_line_2 = self._create_invoice_line('in_invoice', currency_id=self.other_currency.id, invoice_payment_term_id=payment_term.id, invoice_line_ids=[{'price_unit': 400.0}]) move_line_2.move_id.js_assign_outstanding_line(statement_line_2.line_ids[0].id) self.assertRecordValues(statement_line_2.line_ids, [ {'account_id': statement_line_2.journal_id.default_account_id.id, 'amount_currency': -90.0, 'currency_id': self.company_data['currency'].id, 'balance': -90.0, 'reconciled': False}, {'account_id': move_line_2.account_id.id, 'amount_currency': 133.32, 'currency_id': self.other_currency.id, 'balance': 66.66, 'reconciled': True}, {'account_id': move_line_2.account_id.id, 'amount_currency': 46.68, 'currency_id': self.other_currency.id, 'balance': 23.34, 'reconciled': True}, ]) # Case 5: 3 installments with foreign currency on statement line new_journal = self.env['account.journal'].create({ 'name': 'test', 'code': 'TBNK', 'type': 'bank', 'currency_id': self.other_currency.id, }) suspense_account_other = new_journal.suspense_account_id ## Case 5.1: Adding from the bank rec widget statement_line_3 = self._create_st_line(amount=-180, journal_id=new_journal.id, update_create_date=False) move_line_3 = self._create_invoice_line('in_invoice', invoice_payment_term_id=payment_term.id, invoice_line_ids=[{'price_unit': 200.0}]) statement_line_3.set_line_bank_statement_line(move_line_3.ids) self.assertRecordValues(statement_line_3.line_ids, [ {'account_id': statement_line_3.journal_id.default_account_id.id, 'amount_currency': -180.0, 'currency_id': self.other_currency.id, 'balance': -90.0, 'reconciled': False}, {'account_id': move_line_3.account_id.id, 'amount_currency': 66.66, 'currency_id': self.company_data['currency'].id, 'balance': 66.66, 'reconciled': True}, {'account_id': move_line_3.account_id.id, 'amount_currency': 66.66, 'currency_id': self.company_data['currency'].id, 'balance': 66.66, 'reconciled': True}, {'account_id': move_line_3.account_id.id, 'amount_currency': 66.68, 'currency_id': self.company_data['currency'].id, 'balance': 66.68, 'reconciled': True}, {'account_id': suspense_account_other.id, 'amount_currency': -220.0, 'currency_id': self.other_currency.id, 'balance': -110.0, 'reconciled': False}, ]) ## Case 5.2: Adding from the invoice statement_line_3 = self._create_st_line(amount=-180, journal_id=new_journal.id, update_create_date=False) move_line_3 = self._create_invoice_line('in_invoice', invoice_payment_term_id=payment_term.id, invoice_line_ids=[{'price_unit': 200.0}]) move_line_3.move_id.js_assign_outstanding_line(statement_line_3.line_ids[0].id) self.assertRecordValues(statement_line_3.line_ids, [ {'account_id': statement_line_3.journal_id.default_account_id.id, 'amount_currency': -180.0, 'currency_id': self.other_currency.id, 'balance': -90.0, 'reconciled': False}, {'account_id': move_line_3.account_id.id, 'amount_currency': 66.66, 'currency_id': self.company_data['currency'].id, 'balance': 66.66, 'reconciled': True}, {'account_id': move_line_3.account_id.id, 'amount_currency': 23.34, 'currency_id': self.company_data['currency'].id, 'balance': 23.34, 'reconciled': True}, ]) # Case 6: 3 installments with foreign currency on both invoice and statement line ## Case 6.1: Adding from the bank rec widget statement_line_4 = self._create_st_line(amount=-180, journal_id=new_journal.id, update_create_date=False) move_line_4 = self._create_invoice_line('in_invoice', currency_id=self.other_currency.id, invoice_payment_term_id=payment_term.id, invoice_line_ids=[{'price_unit': 400.0}]) statement_line_4.set_line_bank_statement_line(move_line_4.ids) self.assertRecordValues(statement_line_4.line_ids, [ {'account_id': statement_line_4.journal_id.default_account_id.id, 'amount_currency': -180.0, 'currency_id': self.other_currency.id, 'balance': -90.0, 'reconciled': False}, {'account_id': move_line_4.account_id.id, 'amount_currency': 133.32, 'currency_id': self.other_currency.id, 'balance': 66.66, 'reconciled': True}, {'account_id': move_line_4.account_id.id, 'amount_currency': 133.32, 'currency_id': self.other_currency.id, 'balance': 66.66, 'reconciled': True}, {'account_id': move_line_4.account_id.id, 'amount_currency': 133.36, 'currency_id': self.other_currency.id, 'balance': 66.68, 'reconciled': True}, {'account_id': suspense_account_other.id, 'amount_currency': -220.0, 'currency_id': self.other_currency.id, 'balance': -110.0, 'reconciled': False}, ]) ## Case 6.2: Adding from the invoice statement_line_4 = self._create_st_line(amount=-180, journal_id=new_journal.id, update_create_date=False) move_line_4 = self._create_invoice_line('in_invoice', currency_id=self.other_currency.id, invoice_payment_term_id=payment_term.id, invoice_line_ids=[{'price_unit': 400.0}]) move_line_4.move_id.js_assign_outstanding_line(statement_line_4.line_ids[0].id) self.assertRecordValues(statement_line_4.line_ids, [ {'account_id': statement_line_4.journal_id.default_account_id.id, 'amount_currency': -180.0, 'currency_id': self.other_currency.id, 'balance': -90.0, 'reconciled': False}, {'account_id': move_line_4.account_id.id, 'amount_currency': 133.32, 'currency_id': self.other_currency.id, 'balance': 66.66, 'reconciled': True}, {'account_id': move_line_4.account_id.id, 'amount_currency': 46.68, 'currency_id': self.other_currency.id, 'balance': 23.34, 'reconciled': True}, ]) for move in (move_line_1 + move_line_2 + move_line_3 + move_line_4).mapped('move_id'): self.assertEqual(move.payment_state, 'partial') def test_create_rule_during_reconcile_with_previous_statements(self): """ Test auto creation of reconciliation rule with line references having a common trailing space """ refs = ( 'TEST REFERENCE TO NORMALIZE 123,12', 'TEST REFERENCE TO NORMALIZE 234,23', 'TEST REFERENCE TO NORMALIZE 345,12', 'TEST REFERENCE TO NORMALIZE 234,12', 'TEST REFERENCE TO NORMALIZE 123,12', ) bank_lines = self.env['account.bank.statement.line'].create([ { 'journal_id': self.company_data['default_journal_bank'].id, 'date': '2020-01-01', 'payment_ref': payment_ref, 'amount': 100, 'sequence': 1, 'counterpart_account_id': self.account_revenue_1.id, } for payment_ref in refs ]) bank_lines[-1].set_account_bank_statement_line(bank_lines[-1].line_ids[-1].id, self.account_revenue_1.id) # Reconciliation rule created succesfully with the escaped common substring rule = self.env['account.reconcile.model'].search([ ('match_label_param', '=', 'TEST REFERENCE TO NORMALIZE'), ], limit=1) self.assertTrue(rule) def test_auto_match_multiple_candidates_select_closer_prior_date(self): self._create_invoice_line('out_invoice', invoice_date='2017-01-08', invoice_line_ids=[{'price_unit': 150}]) self._create_invoice_line('out_invoice', invoice_date='2017-01-06', invoice_line_ids=[{'price_unit': 150}]) statement_line = self._create_st_line(amount=150, partner_id=self.partner_a.id, date='2017-01-08', update_create_date=False) statement_line._try_auto_reconcile_statement_lines() # Nothing will be selected since we have 2 candidates with a prior date self.assertFalse(statement_line.line_ids[-1].reconciled_lines_ids) def test_auto_match_one_canditate_selected_prior_date(self): self._create_invoice_line('out_invoice', invoice_date='2017-01-10', invoice_line_ids=[{'price_unit': 150}]) move_line_2 = self._create_invoice_line('out_invoice', invoice_date='2017-01-08', invoice_line_ids=[{'price_unit': 150}]) statement_line = self._create_st_line(amount=150, partner_id=self.partner_a.id, date='2017-01-08', update_create_date=False) statement_line._try_auto_reconcile_statement_lines() # move_line_2 will be selected because it's the one with the closer prior or equal date. self.assertEqual(statement_line.line_ids[-1].reconciled_lines_ids, move_line_2) def test_auto_match_no_canditate_selected_prior_date(self): self._create_invoice_line('out_invoice', invoice_date='2017-01-10', invoice_line_ids=[{'price_unit': 150}]) self._create_invoice_line('out_invoice', invoice_date='2017-01-09', invoice_line_ids=[{'price_unit': 150}]) statement_line = self._create_st_line(amount=150, partner_id=self.partner_a.id, date='2017-01-08', update_create_date=False) statement_line._try_auto_reconcile_statement_lines() # move_line_2 will be selected because it's the one with the closer prior or equal date. self.assertFalse(statement_line.line_ids[-1].reconciled_lines_ids) def test_set_partner_on_statement_line_reconciles_with_move_line_missing_invoice_date(self): """Test that setting a partner on statement line reconciles it with a move line missing an invoice_date.""" # Draft invoice (no invoice_date set) draft_move = self.env['account.move'].create({ 'move_type': 'out_invoice', 'partner_id': self.partner_a.id, 'invoice_line_ids': [Command.create({'name': 'line', 'price_unit': 100})], }) self.assertFalse(draft_move.line_ids[0].invoice_date) prior_move_line = self._create_invoice_line( 'out_invoice', invoice_date='2017-01-08', invoice_line_ids=[{'price_unit': 100}] ) statement_line = self._create_st_line(100.0, date='2017-01-10') statement_line.set_partner_bank_statement_line(self.partner_a.id) self.assertEqual(statement_line.partner_id, self.partner_a) self.assertEqual(statement_line.line_ids[-1].reconciled_lines_ids, prior_move_line) def test_edit_statement_line_with_exchange_diff(self): """ When editing a bank statement line with an exchange diff, like setting the invoice as fully paid, the exchange diff should be replaced by a new one, and the invoice should be marked as fully paid. That's what we do, but when the currency rate decrease, the exchange diff is first reverted and reconcile, then we try to reconcile the other lines. But the reconciled exchange diff line is interfering with the other one's. It shouldn't be the case, and the reconciled exchange diff lines should be filtered out of the lines to edit. """ chf_currency = self.setup_other_currency('CHF', rates=[('2016-01-01', 2.0), ('2016-06-20', 1.0)]) chf_journal = self.env['account.journal'].create({ 'name': 'Test Bank CHF', 'type': 'bank', 'code': 'TBCHF', 'currency_id': chf_currency.id, }) invoice_line = self._create_invoice_line( 'out_invoice', invoice_date='2016-06-15', invoice_line_ids=[{'price_unit': 1000.0}], currency_id=chf_currency.id, ) st_line = self._create_st_line(950.0, date='2016-06-21', update_create_date=False, foreign_currency_id=chf_currency.id, journal_id=chf_journal.id) st_line.set_line_bank_statement_line(invoice_line.ids) st_line.edit_reconcile_line(st_line.line_ids[1].id, {'balance': -1000, 'amount_currency': -1000}) self.assertEqual(invoice_line.move_id.status_in_payment, 'paid') self.assertRecordValues(st_line.line_ids, [ {'balance': 950.0, 'reconciled': False}, {'balance': -1000.0, 'reconciled': True}, {'balance': 50.0, 'reconciled': False}, ]) def test_reconcile_epd_with_exchange_diff(self): """ When setting an invoice containing an early payment discount that should also apply an exchange difference, a payment with move is created to correctly handle the EPD + exchange différence, as we don't want any exchange difference line in the bank rec widget. """ chf_currency = self.setup_other_currency('CHF', rates=[('2016-01-01', 2.0), ('2016-06-20', 4.0)]) invoice_line = self._create_invoice_line( 'out_invoice', invoice_date='2016-06-15', invoice_payment_term_id=self.early_payment_term.id, invoice_line_ids=[{'price_unit': 100.0}], currency_id=chf_currency.id, ) st_line = self._create_st_line(22.5, date='2016-06-21', update_create_date=False, foreign_currency_id=chf_currency.id) st_line.set_line_bank_statement_line(invoice_line.ids) payment_move = invoice_line.move_id.matched_payment_ids.move_id self.assertTrue(payment_move) outstanding_line = payment_move.line_ids.filtered(lambda line: line.account_type == 'asset_current') self.assertRecordValues(st_line.line_ids, [ {'account_id': st_line.journal_id.default_account_id.id, 'partner_id': self.partner_a.id, 'balance': 22.5, 'amount_currency': 22.5, 'reconciled': False}, {'account_id': outstanding_line.account_id.id, 'partner_id': self.partner_a.id, 'balance': -22.5, 'amount_currency': -90.0, 'reconciled': True}, ]) def test_reconcile_epd_with_partial(self): """ When reconciling an invoice with an early payment discount on a statement line with an amount smaller than the invoice's one (even after applying the EPD), the line should still be a partial. """ st_line = self._create_st_line(70.0, date='2017-01-10', update_create_date=False) inv_line_with_epd = self._create_invoice_line( 'out_invoice', date='2017-01-04', invoice_payment_term_id=self.early_payment_term.id, invoice_line_ids=[{'price_unit': 100.0}], ) st_line.set_line_bank_statement_line(inv_line_with_epd.id) self.assertRecordValues(st_line.line_ids, [ {'account_id': st_line.journal_id.default_account_id.id, 'balance': 70.0, 'reconciled': False}, {'account_id': inv_line_with_epd.account_id.id, 'balance': -70.0, 'reconciled': True}, ]) def test_reconcile_bill_epd_with_larger_statement_line(self): """ Tests a basic case of applying an early payment discount on a vendor bill. When adding it on a statement line that has a larger amount, it should still apply the EPD """ st_line = self._create_st_line(-200.0, date='2017-01-10', update_create_date=False) early_pay_acc = self.env.company.account_journal_early_pay_discount_gain_account_id bill_line_with_epd = self._create_invoice_line( 'in_invoice', date='2017-01-04', invoice_payment_term_id=self.early_payment_term.id, invoice_line_ids=[{'price_unit': 100.0}], ) st_line.set_line_bank_statement_line(bill_line_with_epd.id) self.assertRecordValues(st_line.line_ids, [ {'account_id': st_line.journal_id.default_account_id.id, 'balance': -200.0, 'reconciled': False}, {'account_id': bill_line_with_epd.account_id.id, 'balance': 100.0, 'reconciled': True}, {'account_id': early_pay_acc.id, 'balance': -10.0, 'reconciled': False}, {'account_id': st_line.journal_id.suspense_account_id.id, 'balance': 110.0, 'reconciled': False}, ]) def test_line_computation_on_edit_line(self): """ When editing a statement line, only the edited line, the suspense and the not reconciled lines should be modified. """ usd_currency = self.setup_other_currency('USD', rates=[('2017-01-01', 2.00)]) eur_currency = self.setup_other_currency('EUR', rates=[('2017-01-01', 1.00)]) eur_journal = self.env['account.journal'].create({ 'name': 'Test Bank EUR', 'type': 'bank', 'code': 'TBEUR', 'currency_id': eur_currency.id, }) st_line = self._create_st_line(5000, date='2017-01-10', update_create_date=False, journal_id=eur_journal.id) eur_invoice_line = self._create_invoice_line( 'out_invoice', invoice_date='2017-01-10', invoice_line_ids=[{'price_unit': 1000.0}], currency_id=eur_currency.id, ) usd_invoice_line = self._create_invoice_line( 'out_invoice', invoice_date='2017-01-10', invoice_line_ids=[{'price_unit': 1000.0}], currency_id=usd_currency.id, ) st_line.set_line_bank_statement_line(usd_invoice_line.id) st_line.set_line_bank_statement_line(eur_invoice_line.id) st_line.set_account_bank_statement_line(st_line.line_ids[-1].id, self.account_revenue_1.id) st_line.edit_reconcile_line(st_line.line_ids[-1].id, {'balance': -2000, 'amount_currency': -1000}) self.assertRecordValues(st_line.line_ids, [ {'amount_currency': 5000.0, 'balance': 10000.0, 'reconciled': False}, {'amount_currency': -1000.0, 'balance': -1000.0, 'reconciled': True}, {'amount_currency': -1000.0, 'balance': -2000.0, 'reconciled': True}, {'amount_currency': -1000.0, 'balance': -2000.0, 'reconciled': False}, {'amount_currency': -2500.0, 'balance': -5000.0, 'reconciled': False}, ]) def test_reconcile_partialed_amounts(self): """ Scenario of reconciling an invoice line with multiple statements of lesser amounts, resulting in multiple partial reconciliation amounts. """ st_line_50 = self._create_st_line(50.0, update_create_date=False) st_line_300 = self._create_st_line(300.0, update_create_date=False) st_line_400 = self._create_st_line(400.0, update_create_date=False) st_line_500 = self._create_st_line(500.0, update_create_date=False) inv_line = self._create_invoice_line('out_invoice', invoice_line_ids=[{'price_unit': 1000.0}]) st_line_50.set_line_bank_statement_line(inv_line.id) self.assertRecordValues(st_line_50.line_ids, [ {'account_id': st_line_50.journal_id.default_account_id.id, 'balance': 50.0, 'reconciled': False}, {'account_id': inv_line.account_id.id, 'balance': -50.0, 'reconciled': True}, ]) st_line_300.set_line_bank_statement_line(inv_line.id) self.assertRecordValues(st_line_300.line_ids, [ {'account_id': st_line_300.journal_id.default_account_id.id, 'balance': 300.0, 'reconciled': False}, {'account_id': inv_line.account_id.id, 'balance': -300.0, 'reconciled': True}, ]) st_line_400.set_line_bank_statement_line(inv_line.id) self.assertRecordValues(st_line_400.line_ids, [ {'account_id': st_line_400.journal_id.default_account_id.id, 'balance': 400.0, 'reconciled': False}, {'account_id': inv_line.account_id.id, 'balance': -400.0, 'reconciled': True}, ]) st_line_500.set_line_bank_statement_line(inv_line.id) self.assertRecordValues(st_line_500.line_ids, [ {'account_id': st_line_500.journal_id.default_account_id.id, 'balance': 500.0, 'reconciled': False}, {'account_id': inv_line.account_id.id, 'balance': -250.0, 'reconciled': True}, {'account_id': st_line_500.journal_id.suspense_account_id.id, 'balance': -250.0, 'reconciled': False}, ]) def test_full_amount_switch_html_multi_statements(self): """ Test full_amount_switch_html is computed only for move lines related to statement lines """ st_line_50 = self._create_st_line(50.0, update_create_date=False) st_line_300 = self._create_st_line(300.0, update_create_date=False) inv_line = self._create_invoice_line('out_invoice', invoice_line_ids=[{'price_unit': 350.0}]) st_line_50.set_line_bank_statement_line(inv_line.id) st_line_300.set_line_bank_statement_line(inv_line.id) self.assertFalse(inv_line.full_amount_switch_html) self.assertTrue(any(line.full_amount_switch_html for line in st_line_50.line_ids)) self.assertTrue(any(line.full_amount_switch_html for line in st_line_300.line_ids)) def test_apply_full_amount_html_currencies(self): st_line = self._create_st_line(50.0, update_create_date=False) invoice_line = self._create_invoice_line( 'out_invoice', invoice_date='2017-01-1', invoice_payment_term_id=self.early_payment_term.id, invoice_line_ids=[{'price_unit': 150.0}], currency_id=self.other_currency.id, ) st_line.set_line_bank_statement_line(invoice_line.id) self.assertRecordValues(st_line.line_ids, [ {'account_id': st_line.journal_id.default_account_id.id, 'balance': 50.0, 'amount_currency': 50.0, 'reconciled': False}, {'account_id': invoice_line.account_id.id, 'balance': -50.0, 'amount_currency': -100.0, 'reconciled': True}, ]) st_line.edit_reconcile_line(st_line.line_ids[-1].id, {'balance': -150, 'amount_currency': -300}) self.assertRecordValues(st_line.line_ids, [ {'account_id': st_line.journal_id.default_account_id.id, 'balance': 50.0, 'amount_currency': 50.0, 'reconciled': False}, {'account_id': invoice_line.account_id.id, 'balance': -150.0, 'amount_currency': -300.0, 'reconciled': False}, {'account_id': st_line.journal_id.suspense_account_id.id, 'balance': 100.0, 'amount_currency': 100.0, 'reconciled': False}, ]) def test_edit_reconcile_line_with_positive_balance(self): """Editing a reconciled bank statement line with a positive balance should update the line and create a suspense line.""" st_line = self._create_st_line(50.0, update_create_date=False) invoice_line = self._create_invoice_line( 'out_invoice', invoice_date='2017-01-1', invoice_payment_term_id=self.early_payment_term.id, invoice_line_ids=[{'price_unit': 150.0}], currency_id=self.other_currency.id, ) st_line.set_line_bank_statement_line(invoice_line.id) self.assertRecordValues(st_line.line_ids, [ {'account_id': st_line.journal_id.default_account_id.id, 'balance': 50.0, 'amount_currency': 50.0, 'reconciled': False}, {'account_id': invoice_line.account_id.id, 'balance': -50.0, 'amount_currency': -100.0, 'reconciled': True}, ]) st_line.edit_reconcile_line(st_line.line_ids[-1].id, {'balance': 150, 'amount_currency': 300}) self.assertRecordValues(st_line.line_ids, [ {'account_id': st_line.journal_id.default_account_id.id, 'balance': 50.0, 'amount_currency': 50.0, 'reconciled': False}, {'account_id': invoice_line.account_id.id, 'balance': 150.0, 'amount_currency': 300.0, 'reconciled': False}, {'account_id': st_line.journal_id.suspense_account_id.id, 'balance': -200.0, 'amount_currency': -200.0, 'reconciled': False}, ]) def test_reco_model_empty_space_at_the_end(self): st_line_1 = self._create_st_line(amount=100, payment_ref="Pret ", update_create_date=False) st_line_2 = self._create_st_line(amount=100, payment_ref="Course ", update_create_date=False) st_line_1.set_account_bank_statement_line(st_line_1.line_ids[-1].id, self.account_revenue_1.id) st_line_2.set_account_bank_statement_line(st_line_2.line_ids[-1].id, self.account_revenue_1.id) # No reco model should be created reco_model = self.env['account.reconcile.model'].search([ ('match_label', '=', 'match_regex'), ('match_label_param', '=', '\\ \\ \\ \\ \\ \\ \\ \\ \\ \\ \\ \\ \\ \\ \\ \\ \\ \\ \\ \\ \\ \\ \\ \\ \\ \\ \\ \\ '), ]) self.assertFalse(reco_model.exists()) def test_set_partner_multiple_statement_line(self): invoice_1 = self._create_invoice_line('out_invoice', partner_id=self.partner_a.id, invoice_line_ids=[{'price_unit': 1000.0}]) invoice_2 = self._create_invoice_line('out_invoice', partner_id=self.partner_a.id, invoice_line_ids=[{'price_unit': 1500.0}]) statement_line_1 = self._create_st_line(amount=100, update_create_date=False, partner_id=False) statement_line_2 = self._create_st_line(1000.0, partner_name='xyz', payment_ref=invoice_1.move_id.name, update_create_date=False) statement_line_3 = self._create_st_line(1500.0, partner_name='xyz', payment_ref=invoice_2.move_id.name, update_create_date=False) self.env.cr.flush() # force tracking message (statement_line_1 + statement_line_2).with_context( tracking_disable=False, mail_notrack=False, ).set_partner_bank_statement_line(self.partner_a.id) self.env.cr.flush() # force tracking message self.assertEqual(statement_line_1.partner_id, self.partner_a) self.assertEqual(statement_line_2.partner_id, self.partner_a) self.assertEqual(statement_line_3.partner_id, self.partner_a) def test_set_account_multiple_statement_lines(self): st_line_1 = self._create_st_line(500.0, update_create_date=False) st_line_2 = self._create_st_line(500.0, update_create_date=False) (st_line_1 + st_line_2).set_account_bank_statement_line([st_line_1.line_ids[-1].id, st_line_2.line_ids[-1].id], self.account_revenue_1.id) self.assertRecordValues(st_line_1.line_ids, [ {'account_id': st_line_1.journal_id.default_account_id.id, 'amount_currency': 500.0, 'balance': 500.0, 'reconciled': False}, {'account_id': self.company_data['default_account_revenue'].id, 'amount_currency': -500.0, 'balance': -500.0, 'reconciled': False}, ]) self.assertRecordValues(st_line_2.line_ids, [ {'account_id': st_line_2.journal_id.default_account_id.id, 'amount_currency': 500.0, 'balance': 500.0, 'reconciled': False}, {'account_id': self.company_data['default_account_revenue'].id, 'amount_currency': -500.0, 'balance': -500.0, 'reconciled': False}, ]) def test_remove_epd_with_tax(self): """ When adding an invoice with an eligible early payment discount, with a tax on its line, an EPD line as well as a tax line for that EPD will be added. When removing the EPD line, the tax line should be deleted aswell (but not the invoice line) """ tax = self.env['account.tax'].create({ 'name': 'new_tax', 'amount_type': 'percent', 'amount': 20.0, 'type_tax_use': 'sale', }) suspense_account = self.company_data['default_journal_bank'].suspense_account_id early_pay_acc = self.env.company.account_journal_early_pay_discount_loss_account_id statement_line = self._create_st_line(amount=200, date='2017-01-10', update_create_date=False) move_line = self._create_invoice_line( 'out_invoice', date='2017-01-04', invoice_payment_term_id=self.early_payment_term.id, invoice_line_ids=[{'price_unit': 100.0, 'tax_ids': tax.ids}]) statement_line.set_line_bank_statement_line(move_line.ids) self.assertRecordValues(statement_line.line_ids, [ {'account_id': statement_line.journal_id.default_account_id.id, 'amount_currency': 200.0, 'currency_id': self.company_data['currency'].id, 'balance': 200.0, 'reconciled': False}, {'account_id': move_line.account_id.id, 'amount_currency': -120.0, 'currency_id': self.company_data['currency'].id, 'balance': -120.0, 'reconciled': True}, {'account_id': early_pay_acc.id, 'amount_currency': 10.0, 'currency_id': self.company_data['currency'].id, 'balance': 10.0, 'reconciled': False}, {'account_id': self.account_revenue_1.id, 'amount_currency': 2.0, 'currency_id': self.company_data['currency'].id, 'balance': 2.0, 'reconciled': False}, {'account_id': suspense_account.id, 'amount_currency': -92.0, 'currency_id': self.company_data['currency'].id, 'balance': -92.0, 'reconciled': False}, ]) epd_line = statement_line.line_ids.filtered(lambda line: line.account_id == early_pay_acc) statement_line.delete_reconciled_line(epd_line.ids) self.assertRecordValues(statement_line.line_ids, [ {'account_id': statement_line.journal_id.default_account_id.id, 'amount_currency': 200.0, 'currency_id': self.company_data['currency'].id, 'balance': 200.0, 'reconciled': False}, {'account_id': move_line.account_id.id, 'amount_currency': -120.0, 'currency_id': self.company_data['currency'].id, 'balance': -120.0, 'reconciled': True}, {'account_id': suspense_account.id, 'amount_currency': -80.0, 'currency_id': self.company_data['currency'].id, 'balance': -80.0, 'reconciled': False}, ]) def test_delete_reconciled_line_with_tax_reconcile_account(self): """Test removing a move line when the tax account is reconcilable""" inv_line = self._create_invoice_line( 'out_invoice', invoice_line_ids=[{'price_unit': 100.0, 'tax_ids': [Command.set(self.default_tax.ids)]}], ) tax_line = inv_line.move_id.line_ids.filtered(lambda l: l.tax_line_id) st_line = self._create_st_line(-200.0, update_create_date=False) st_line.set_line_bank_statement_line([tax_line.id]) self.assertRecordValues(st_line.line_ids, [ {'account_id': st_line.journal_id.default_account_id.id, 'tax_ids': [], 'tax_line_id': False, 'balance': -200.0, 'reconciled': False}, {'account_id': tax_line.account_id.id, 'tax_ids': [], 'tax_line_id': tax_line.tax_line_id.id, 'balance': 10.0, 'reconciled': True}, {'account_id': st_line.journal_id.suspense_account_id.id, 'tax_ids': [], 'tax_line_id': False, 'balance': 190.0, 'reconciled': False}, ]) st_line.delete_reconciled_line(st_line.line_ids[-2].id) self.assertRecordValues(st_line.line_ids, [ {'account_id': st_line.journal_id.default_account_id.id, 'tax_ids': [], 'tax_line_id': False, 'balance': -200.0, 'reconciled': False}, {'account_id': st_line.journal_id.suspense_account_id.id, 'tax_ids': [], 'tax_line_id': False, 'balance': 200.0, 'reconciled': False}, ]) def test_early_payment_discount_multi_bill_statement(self): early_pay_acc = self.env.company.account_journal_early_pay_discount_gain_account_id self.company_data['default_journal_bank'].outbound_payment_method_line_ids.payment_account_id = False inv_line_with_epd = self._create_invoice_line( 'in_invoice', date='2017-01-10', invoice_payment_term_id=self.early_payment_term.id, invoice_line_ids=[{'price_unit': 100.0}], ) payment = self.env['account.payment.register'].with_context(active_model='account.move', active_ids=inv_line_with_epd.move_id.ids).create({ 'payment_date': '2017-01-10', })._create_payments() st_line = self._create_st_line(-100.0, date='2017-01-11', update_create_date=False) st_line.set_line_bank_statement_line([inv_line_with_epd.id]) self.assertEqual(payment.state, 'paid') def test_credit_warning_excludes_unreconciled_bank_statement_line(self): """Credit warning must account for outstanding bank statement lines.""" self.env.company.account_use_credit_limit = True self.partner_a.credit_limit = 1000.0 inv_line = self._create_invoice_line( 'out_invoice', invoice_line_ids=[{'price_unit': 2000.0, 'tax_ids': []}], ) invoice = inv_line.move_id invoice.button_draft() # warning only shows on draft invoices self.assertTrue(invoice.partner_credit_warning) # An unreconciled bank statement line for the partner # should reduce the effective outstanding exposure. self._create_st_line( amount=1500.0, date='2017-01-01', partner_id=self.partner_a.id, ) invoice.invalidate_recordset(['partner_credit_warning']) self.assertFalse(invoice.partner_credit_warning) def test_credit_warning_excludes_reconciled_bank_statement_line(self): """Credit warning must account for bank statement lines already matched to the invoice.""" self.env.company.account_use_credit_limit = True self.partner_a.credit_limit = 1000.0 inv_line = self._create_invoice_line( 'out_invoice', invoice_line_ids=[{'price_unit': 2000.0, 'tax_ids': []}], ) invoice = inv_line.move_id invoice.button_draft() # warning only shows on draft invoices self.assertTrue(invoice.partner_credit_warning) st_line = self._create_st_line( amount=1500.0, date='2017-01-01', partner_id=self.partner_a.id, ) invoice.action_post() # reconciliation requires a posted move bank_cash_line = st_line.move_id.line_ids.filtered( lambda l: l.account_id.account_type == 'asset_cash' ) invoice.js_assign_outstanding_line(bank_cash_line.id) invoice.button_draft() # warning only shows on draft invoices self.assertFalse(invoice.partner_credit_warning) def test_common_substring(self): """Test finding longest common substring in fake payment_ref""" # Fake Creditor Reference payment_ref1 = "RF33 4434 5675 9111 4543 8787" payment_ref2 = "RF13 4434 5675 9111 4512 7777" payment_ref3 = "RF67 3791 6912 4434 5675 9132" non_matching_ref = "ABCDEFG" BankStatementLine = self.env['account.bank.statement.line'] self.assertEqual(BankStatementLine._get_common_substring([payment_ref1, payment_ref2]), '3 4434 5675 9111 45') self.assertEqual(BankStatementLine._get_common_substring([payment_ref1, payment_ref2, payment_ref3]), '4434 5675 91') self.assertIsNone(BankStatementLine._get_common_substring([payment_ref1, non_matching_ref])) self.assertIsNone(BankStatementLine._get_common_substring([payment_ref1, payment_ref2, non_matching_ref])) def test_tax_base_amount_bank_fees_reconciliation(self): """Test tax_base_amount value is correctly updated after reconciliation.""" journal_entry = self.env['account.move'].create({ 'move_type': 'entry', 'date': fields.Date.context_today(self), 'journal_id': self.company_data['default_journal_misc'].id, 'line_ids': [ Command.create({ 'account_id': self.company_data['default_account_revenue'].id, 'credit': 1000.0, 'tax_ids': [Command.set(self.tax_sale_a.ids)], }), Command.create({ 'account_id': self.company_data['default_journal_bank'].default_account_id.id, 'debit': 1150.0, }), ] }) journal_entry.action_post() reco_model = self.env['account.reconcile.model'].search([ ('name', '=', 'Bank Fees'), ('company_id', '=', self.company_data['company'].id) ], limit=1) reco_model.line_ids.write({'tax_ids': [Command.set(self.tax_sale_a.ids)]}) st_line = self._create_st_line( amount=-2000.0, payment_ref='bank fees', update_create_date=False, ) reco_model._trigger_reconciliation_model(st_line) st_tax_line = st_line.line_ids.filtered(lambda l: l.tax_line_id == self.tax_sale_a) self.assertEqual(st_tax_line.tax_base_amount, 1739.13) def test_try_auto_reconcile_retry_after_error(self): """Ensures the lines of a failing batch will be retried during the next run of _try_auto_reconcile_statement_lines""" original_method = self.env.registry['account.bank.statement.line']._try_auto_reconcile_statement_lines has_failed = False def patched_try_auto_reconcile(self_model, company_id=None): nonlocal has_failed if not has_failed: has_failed = True raise ValidationError("Simulating a validation error") return original_method(self_model, company_id) payment = self._create_and_post_payment(amount=100, memo="pay_AretqwwXerereE") statement_line = self._create_st_line(amount=100, payment_ref="pay_AretqwwXerereE", update_create_date=False) self.patch(self.env.registry['account.bank.statement.line'], '_try_auto_reconcile_statement_lines', patched_try_auto_reconcile) with self.assertLogs('odoo.addons.account_accountant.models.account_bank_statement') as log_catcher: self.env['account.bank.statement.line']._cron_try_auto_reconcile_statement_lines(batch_size=50) self.assertEqual( [ Like('...found 1 statement lines...'), Like('...Simulating a validation error...'), Like('...found 0 statement lines...'), Like('...the cron will be triggered again...'), ], log_catcher.output, "The first run should have failed. The cron should retry to process the failed line" ) with self.assertLogs('odoo.addons.account_accountant.models.account_bank_statement') as log_catcher: self.env['account.bank.statement.line']._cron_try_auto_reconcile_statement_lines(batch_size=50) self.assertEqual( [ Like('...found 1 statement lines...'), Like('...set line...'), Like('...found 0 statement lines...'), ], log_catcher.output, "The second run should have been succesfull." ) self.assertRecordValues(statement_line.line_ids, [ {'account_id': statement_line.journal_id.default_account_id.id, 'balance': 100.0, 'reconciled': False}, {'account_id': payment.outstanding_account_id.id, 'balance': -100.0, 'reconciled': True}, ]) def test_try_auto_reconcile_drop_after_many_errors(self): """Ensures the lines that fails twice won't be retried again""" original_method = self.env.registry['account.bank.statement.line']._try_auto_reconcile_statement_lines has_failed = 0 def patched_try_auto_reconcile(self_model, company_id=None): nonlocal has_failed if has_failed < 2: has_failed = has_failed + 1 raise ValidationError("Simulating a validation error") return original_method(self_model, company_id) self._create_and_post_payment(amount=100, memo="pay_AretqwwXerereE") statement_line = self._create_st_line(amount=100, payment_ref="pay_AretqwwXerereE", update_create_date=False) self.patch(self.env.registry['account.bank.statement.line'], '_try_auto_reconcile_statement_lines', patched_try_auto_reconcile) with self.assertLogs('odoo.addons.account_accountant.models.account_bank_statement') as log_catcher: self.env['account.bank.statement.line']._cron_try_auto_reconcile_statement_lines(batch_size=50) self.assertEqual( [ Like('...found 1 statement lines...'), Like('...Simulating a validation error...'), Like('...found 0 statement lines...'), Like('...the cron will be triggered again...'), ], log_catcher.output, "The first run should have failed. The cron should retry to process the failed line" ) with self.assertLogs('odoo.addons.account_accountant.models.account_bank_statement') as log_catcher: self.env['account.bank.statement.line']._cron_try_auto_reconcile_statement_lines(batch_size=50) self.assertEqual( [ Like('...found 1 statement lines...'), Like('...Simulating a validation error...'), Like('...found 0 statement lines...'), ], log_catcher.output, "The second run should have failed too." ) with self.assertLogs('odoo.addons.account_accountant.models.account_bank_statement') as log_catcher: self.env['account.bank.statement.line']._cron_try_auto_reconcile_statement_lines(batch_size=50) self.assertEqual([Like('...found 0 statement lines...')], log_catcher.output, "We shouldn't find any new line.") self.assertRecordValues(statement_line.line_ids, [ {'account_id': statement_line.journal_id.default_account_id.id, 'balance': 100.0, 'reconciled': False}, {'account_id': statement_line.journal_id.suspense_account_id.id, 'balance': -100.0, 'reconciled': False}, ]) def test_try_auto_reconcile_multiple_batches(self): """Ensure one batch failing and retried later on doesn't interfer with other batch""" original_method = self.env.registry['account.bank.statement.line']._try_auto_reconcile_statement_lines count = 0 def patched_try_auto_reconcile(self_model, company_id=None): nonlocal count count = count + 1 if count % 2: raise ValidationError("Simulating a validation error") return original_method(self_model, company_id) self._create_and_post_payment(amount=100, memo="line #1") statement_line_1 = self._create_st_line(amount=100, payment_ref="line #1", update_create_date=False) payment = self._create_and_post_payment(amount=100, memo="line #2") statement_line_2 = self._create_st_line(amount=100, payment_ref="line #2", update_create_date=False) self.patch(self.env.registry['account.bank.statement.line'], '_try_auto_reconcile_statement_lines', patched_try_auto_reconcile) with self.assertLogs('odoo.addons.account_accountant.models.account_bank_statement') as log_catcher: self.env['account.bank.statement.line']._cron_try_auto_reconcile_statement_lines(batch_size=1) self.assertEqual( [ Like('...found 2 statement lines...'), Like('...Simulating a validation error...'), Like('...found 1 statement lines...'), Like('...set line...'), Like('...found 0 statement lines...'), Like('...the cron will be triggered again...'), ], log_catcher.output, "The first run should have found 2 batch of 1 line, fail one of them and succesfully process the other. The cron should retry to process the failed line" ) with self.assertLogs('odoo.addons.account_accountant.models.account_bank_statement') as log_catcher: self.env['account.bank.statement.line']._cron_try_auto_reconcile_statement_lines(batch_size=1) self.assertEqual( [ Like('...found 1 statement lines...'), Like('...Simulating a validation error...'), Like('...found 0 statement lines...'), ], log_catcher.output, "The second run should have failed." ) self.assertRecordValues(statement_line_1.line_ids, [ {'account_id': statement_line_1.journal_id.default_account_id.id, 'balance': 100.0, 'reconciled': False}, {'account_id': statement_line_1.journal_id.suspense_account_id.id, 'balance': -100.0, 'reconciled': False}, ]) self.assertRecordValues(statement_line_2.line_ids, [ {'account_id': statement_line_2.journal_id.default_account_id.id, 'balance': 100.0, 'reconciled': False}, {'account_id': payment.outstanding_account_id.id, 'balance': -100.0, 'reconciled': True}, ])