# -*- coding: utf-8 -*- # Part of Odoo. See LICENSE file for full copyright and licensing details. import time from unittest.mock import patch from odoo import Command from odoo.addons.account_accountant.tests.test_account_bank_statement import TestAccountBankStatement from odoo.tests import Form, tagged from odoo.exceptions import ValidationError @tagged('post_install', '-at_install') class TestBatchPayment(TestAccountBankStatement): @classmethod def setUpClass(cls): super().setUpClass() cls.env.user.group_ids |= cls.env.ref('account.group_validate_bank_account') # Create a bank journal cls.journal = cls.company_data['default_journal_bank'] cls.batch_deposit_method = cls.env.ref('account_batch_payment.account_payment_method_batch_deposit') cls.batch_deposit = cls.journal.inbound_payment_method_line_ids.filtered(lambda l: l.code == 'batch_payment') cls.other_currency = cls.setup_other_currency('EUR') cls.partner_bank_account = cls.env['res.partner.bank'].create({ 'acc_number': 'BE32707171912447', 'partner_id': cls.partner_a.id, 'allow_out_payment': True, 'acc_type': 'bank', }) @classmethod def create_payment(cls, partner, amount, **kwargs): """ Create a batch deposit payment """ payment = cls.env['account.payment'].create({ 'journal_id': cls.journal.id, 'payment_type': 'inbound', 'date': time.strftime('%Y') + '-07-15', 'amount': amount, 'partner_id': partner.id, 'partner_type': 'customer', **kwargs, }) payment.action_post() return payment @classmethod def _create_st_line(cls, amount=1000.0, date='2019-01-01', payment_ref='turlututu', **kwargs): return cls.env['account.bank.statement.line'].create({ 'journal_id': cls.journal.id, 'amount': amount, 'date': date, 'payment_ref': payment_ref, 'partner_id': cls.partner_a.id, **kwargs, }) def test_zero_amount_payment(self): zero_payment = self.create_payment(self.partner_a, 0, payment_method_line_id=self.batch_deposit.id) batch_vals = { 'journal_id': self.journal.id, 'payment_ids': [(4, zero_payment.id, None)], 'payment_method_id': self.batch_deposit_method.id, } self.assertRaises(ValidationError, self.env['account.batch.payment'].create, batch_vals) def test_exchange_diff_batch_payment(self): """ This test will do a basic case where the company is in US Dollars but the st_line and the payment are in another currencies. Between the moment where we created the batch payment and the st_line a difference of rates happens and so an exchange diff is created. """ self.other_currency = self.setup_other_currency('EUR', rates=[('2016-01-03', 2.0), ('2017-01-03', 1.0)]) payment = self.create_payment( partner=self.partner_a, amount=100, date='2017-01-02', journal_id=self.company_data['default_journal_bank'].id, currency_id=self.other_currency.id, ) payment.create_batch_payment() st_line = self._create_st_line( 100.0, date='2017-01-05', ) st_line.set_batch_payment_bank_statement_line(payment.batch_payment_id.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': payment.outstanding_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.line_ids, [ { 'account_id': payment.outstanding_account_id.id, 'currency_id': self.other_currency.id, 'balance': 50.0, }, { 'account_id': self.env.company.income_currency_exchange_account_id.id, 'currency_id': self.other_currency.id, 'balance': -50.0, }, ]) def test_partner_account_batch_payments_with_journal_entry(self): """ Test the account for batch payments with a linked journal entry """ for payment_type, account_a, account_b in [ ('inbound', self.partner_a.property_account_receivable_id, self.partner_b.property_account_receivable_id), ('outbound', self.partner_a.property_account_payable_id, self.partner_b.property_account_payable_id), ]: outstanding_account = self.env['account.payment']._get_outstanding_account(payment_type) self.batch_deposit.payment_account_id = outstanding_account payment_1 = self.env['account.payment'].create({ 'date': '2015-01-01', 'payment_type': payment_type, 'partner_type': 'customer' if payment_type == 'inbound' else 'supplier', 'partner_id': self.partner_a.id, 'payment_method_line_id': self.batch_deposit.id, 'amount': 100.0, }) payment_2 = self.env['account.payment'].create({ 'date': '2015-01-01', 'payment_type': payment_type, 'partner_type': 'customer' if payment_type == 'inbound' else 'supplier', 'partner_id': self.partner_b.id, 'payment_method_line_id': self.batch_deposit.id, 'amount': 200.0, }) payments = payment_1 + payment_2 payments.action_post() batch = self.env['account.batch.payment'].create({ 'batch_type': payment_type, 'journal_id': self.journal.id, 'payment_ids': [Command.set(payments.ids)], 'payment_method_id': self.batch_deposit_method.id, }) batch.validate_batch() st_line_amount = 300.0 if payment_type == 'inbound' else -300.0 st_line = self.env['account.bank.statement.line'].create({ 'journal_id': self.journal.id, 'amount': st_line_amount, 'date': '2015-01-01', 'payment_ref': batch.name, }) st_line.set_batch_payment_bank_statement_line(batch.id) bank_account = self.journal.default_account_id if payment_type == 'inbound': self.assertRecordValues(payments.move_id.line_ids.sorted('balance'), [ {'account_id': account_b.id, 'partner_id': self.partner_b.id, 'balance': -200.0}, {'account_id': account_a.id, 'partner_id': self.partner_a.id, 'balance': -100.0}, {'account_id': outstanding_account.id, 'partner_id': self.partner_a.id, 'balance': 100.0}, {'account_id': outstanding_account.id, 'partner_id': self.partner_b.id, 'balance': 200.0}, ]) self.assertRecordValues(st_line.move_id.line_ids.sorted('balance'), [ {'account_id': outstanding_account.id, 'partner_id': self.partner_b.id, 'balance': -200.0}, {'account_id': outstanding_account.id, 'partner_id': self.partner_a.id, 'balance': -100.0}, {'account_id': bank_account.id, 'partner_id': False, 'balance': 300.0}, ]) else: self.assertRecordValues(payments.move_id.line_ids.sorted('balance'), [ {'account_id': outstanding_account.id, 'partner_id': self.partner_b.id, 'balance': -200.0}, {'account_id': outstanding_account.id, 'partner_id': self.partner_a.id, 'balance': -100.0}, {'account_id': account_a.id, 'partner_id': self.partner_a.id, 'balance': 100.0}, {'account_id': account_b.id, 'partner_id': self.partner_b.id, 'balance': 200.0}, ]) self.assertRecordValues(st_line.move_id.line_ids.sorted('balance'), [ {'account_id': bank_account.id, 'partner_id': False, 'balance': -300.0}, {'account_id': outstanding_account.id, 'partner_id': self.partner_a.id, 'balance': 100.0}, {'account_id': outstanding_account.id, 'partner_id': self.partner_b.id, 'balance': 200.0}, ]) def test_bank_rec_widget_batch_payment_with_entries(self): payment = self.create_payment(self.partner_a, 100, journal_id=self.company_data['default_journal_bank'].id) payment.create_batch_payment() st_line = self._create_st_line(amount=100) st_line.set_batch_payment_bank_statement_line(payment.batch_payment_id.id) self.assertRecordValues(st_line.line_ids, [ {'account_id': st_line.journal_id.default_account_id.id, 'name': st_line.payment_ref, 'amount_currency': 100.0, 'currency_id': self.company_data['currency'].id, 'balance': 100.0, 'reconciled': False}, {'account_id': payment.journal_id.inbound_payment_method_line_ids.payment_account_id.id, 'name': payment.journal_id.inbound_payment_method_line_ids[0].name, 'amount_currency': -100.0, 'currency_id': self.company_data['currency'].id, 'balance': -100.0, 'reconciled': True}, ]) self.assertEqual(st_line.line_ids.reconciled_lines_ids, payment.move_id.line_ids.filtered(lambda x: x.account_id.account_type == 'asset_current')) def test_bank_rec_widget_batch_payment_delete_payment(self): payment = self.create_payment(self.partner_a, 100, payment_method_line_id=self.batch_deposit.id) payment.create_batch_payment() st_line = self._create_st_line(amount=100) st_line.set_batch_payment_bank_statement_line(payment.batch_payment_id.id) # When removing the payment line, the payment should go back to in_process but the batch remains untouched st_line.delete_reconciled_line(st_line.line_ids[-1].id) self.assertEqual(payment.state, 'in_process') def test_unreconcile_keeps_invoice_posted_when_post_is_blocked(self): """ Ensure that unreconciling a batch payment from a bank statement line keeps the linked invoice posted even if the internal repost is blocked by a third-party module (ex: Studio Approval). """ invoice = self._create_invoice_one_line(price_unit=100, post=True) payment = self.create_payment( self.partner_a, invoice.amount_total, payment_method_line_id=self.batch_deposit.id, invoice_ids=[Command.set(invoice.ids)], ) payment.create_batch_payment() st_line = self._create_st_line(amount=invoice.amount_total) st_line.set_batch_payment_bank_statement_line(payment.batch_payment_id.id) # Simulate an approval rule that silently rejects action_post AccountMove = self.env.registry['account.move'] original_action_post = AccountMove.action_post def gated_action_post(records, *args, **kwargs): if records.env.su: return original_action_post(records, *args, **kwargs) return {'type': 'ir.actions.client', 'tag': 'display_notification', 'params': {}} with patch.object(AccountMove, 'action_post', gated_action_post): st_line.delete_reconciled_line(st_line.line_ids[-1].id) self.assertEqual(invoice.state, 'posted') self.assertEqual(payment.state, 'in_process') def test_unreconcile_sepa_ct_batch_with_pending_online_status(self): """ Ensure that delete_reconciled_line does not raise error when un-reconciling a SEPA CT batch payment whose payment_online_status = 'pending'. """ if self.env['ir.module.module']._get('account_online_payment').state != 'installed': self.skipTest("account_online_payment not installed") # Create a bank account for the company and a SEPA CT journal company_bank = self.env['res.partner.bank'].create({ 'partner_id': self.company_data['company'].partner_id.id, 'acc_number': 'BE48363523682327', }) sepa_journal = self.env['account.journal'].create({ 'name': 'SEPA CT Test', 'type': 'bank', 'code': 'SEPA', 'bank_account_id': company_bank.id, 'currency_id': self.other_currency.id, }) sepa_ct_method = sepa_journal.outbound_payment_method_line_ids.filtered( lambda l: l.code == 'sepa_ct' ) invoice = self._create_invoice_one_line( move_type='in_invoice', currency_id=self.other_currency.id, price_unit=100.0, post=True, ) payment = self.env['account.payment'].create({ 'journal_id': sepa_journal.id, 'currency_id': self.other_currency.id, 'payment_method_line_id': sepa_ct_method.id, 'payment_type': 'outbound', 'partner_type': 'supplier', 'partner_id': self.partner_a.id, 'partner_bank_id': self.partner_bank_account.id, 'amount': invoice.amount_total, 'invoice_ids': [Command.set(invoice.ids)], }) payment.action_post() batch = self.env['account.batch.payment'].create({ 'journal_id': sepa_journal.id, 'payment_ids': [Command.set(payment.ids)], 'payment_method_id': self.env.ref('account_iso20022.account_payment_method_sepa_ct').id, 'batch_type': 'outbound', }) # Simulate the batch being sent to the bank online. batch.payment_online_status = 'pending' st_line = self._create_st_line(amount=-payment.amount, journal_id=sepa_journal.id, partner_id=False) st_line.set_batch_payment_bank_statement_line(batch.id) st_line.delete_reconciled_line(st_line.line_ids[-1].id) self.assertEqual(payment.state, 'in_process') self.assertEqual(invoice.state, 'posted') def test_batch_reconciliation_multiple_installments_payment_term(self): """ Test reconciliation of payments for multiple installments payment term lines """ payment_term = self.env['account.payment.term'].create({ 'name': "20-80_payment_term", 'company_id': self.company_data['company'].id, 'line_ids': [ Command.create({'value': 'percent', 'value_amount': 20, 'nb_days': 0}), Command.create({'value': 'percent', 'value_amount': 80, 'nb_days': 20}), ], }) invoice = self.init_invoice('out_invoice', partner=self.partner_a, amounts=[1000.0]) invoice.invoice_payment_term_id = payment_term invoice.action_post() # register payment for the first installment payment_1 = self.env['account.payment.register'].with_context( active_model='account.move', active_ids=invoice.ids, ).create({ 'amount': 200.0, 'payment_date': '2015-01-01', 'payment_method_line_id': self.batch_deposit.id, })._create_payments() payment_1.create_batch_payment() st_line_1 = self._create_st_line(amount=200.0, date='2015-01-01', partner_id=False) st_line_1.set_batch_payment_bank_statement_line(payment_1.batch_payment_id.id) self.assertRecordValues(st_line_1.move_id.line_ids.sorted('balance'), [ {'balance': -200.0}, {'balance': 200.0}, ]) # register payment for the second installment payment_2 = self.env['account.payment.register'].with_context( active_model='account.move', active_ids=invoice.ids, ).create({ 'amount': 800.0, 'payment_date': '2015-01-01', 'payment_method_line_id': self.batch_deposit.id, })._create_payments() payment_2.create_batch_payment() st_line_2 = self._create_st_line(amount=800.0, date='2015-01-01', partner_id=False) st_line_2.set_batch_payment_bank_statement_line(payment_2.batch_payment_id.id) self.assertRecordValues(st_line_2.move_id.line_ids.sorted('balance'), [ {'balance': -800.0}, {'balance': 800.0}, ]) self.assertRecordValues(invoice.line_ids.filtered(lambda l: l.display_type == 'payment_term').sorted('balance'), [ {'balance': 200.0, 'amount_residual': 0.0, 'reconciled': True}, {'balance': 800.0, 'amount_residual': 0.0, 'reconciled': True}, ]) @tagged('post_install', '-at_install') class TestBatchPaymentAccountingOnly(TestBatchPayment): @classmethod def setUpClass(cls): super().setUpClass() if cls.env['ir.module.module']._get('accountant').state != 'installed': cls.skipTest(cls, "This class tests payment without entries, which happens only when Accounting is installed") def test_partner_account_batch_payments_without_journal_entry(self): """ Test that account receivable is used for inbound payments and account payable for outbound ones when no journal entry is linked to the payment """ for payment_type, account_a, account_b in [ ('inbound', self.partner_a.property_account_receivable_id, self.partner_b.property_account_receivable_id), ('outbound', self.partner_a.property_account_payable_id, self.partner_b.property_account_payable_id), ]: payment_1 = self.env['account.payment'].create({ 'date': '2015-01-01', 'payment_type': payment_type, 'partner_type': 'customer' if payment_type == 'inbound' else 'supplier', 'partner_id': self.partner_a.id, 'payment_method_line_id': self.batch_deposit.id, 'amount': 100.0, }) payment_2 = self.env['account.payment'].create({ 'date': '2015-01-01', 'payment_type': payment_type, 'partner_type': 'customer' if payment_type == 'inbound' else 'supplier', 'partner_id': self.partner_b.id, 'payment_method_line_id': self.batch_deposit.id, 'amount': 200.0, }) payments = payment_1 + payment_2 payments.action_post() batch = self.env['account.batch.payment'].create({ 'batch_type': payment_type, 'journal_id': self.journal.id, 'payment_ids': [Command.set(payments.ids)], 'payment_method_id': self.batch_deposit_method.id, }) batch.validate_batch() st_line_amount = 300.0 if payment_type == 'inbound' else -300.0 st_line = self.env['account.bank.statement.line'].create({ 'journal_id': self.journal.id, 'amount': st_line_amount, 'date': '2015-01-01', 'payment_ref': batch.name, }) st_line.set_batch_payment_bank_statement_line(batch.id) bank_account = self.journal.default_account_id if payment_type == 'inbound': self.assertRecordValues(st_line.move_id.line_ids.sorted('balance'), [ {'account_id': account_b.id, 'partner_id': self.partner_b.id, 'balance': -200.0}, {'account_id': account_a.id, 'partner_id': self.partner_a.id, 'balance': -100.0}, {'account_id': bank_account.id, 'partner_id': False, 'balance': 300.0}, ]) else: self.assertRecordValues(st_line.move_id.line_ids.sorted('balance'), [ {'account_id': bank_account.id, 'partner_id': False, 'balance': -300.0}, {'account_id': account_a.id, 'partner_id': self.partner_a.id, 'balance': 100.0}, {'account_id': account_b.id, 'partner_id': self.partner_b.id, 'balance': 200.0}, ]) def test_bank_rec_widget_batch_with_epd_without_entries(self): st_line = self._create_st_line(180.0, date='2019-01-05') early_pay_acc = self.env.company.account_journal_early_pay_discount_loss_account_id invoice_lines_with_epd = self._create_invoice_line( 'out_invoice', invoice_date='2019-01-01', invoice_payment_term_id=self.early_payment_term.id, invoice_line_ids=[{'price_unit': 100.0}], ) + self._create_invoice_line( 'out_invoice', invoice_date='2019-01-01', invoice_payment_term_id=self.early_payment_term.id, invoice_line_ids=[{'price_unit': 100.0}], ) payments = self.env['account.payment.register'].with_context( active_model='account.move', active_ids=invoice_lines_with_epd.move_id.ids, ).create({ 'payment_date': '2019-01-01', 'payment_method_line_id': self.batch_deposit.id, })._create_payments() batch = self.env['account.batch.payment'].create({ 'batch_type': payments[0].payment_type, 'journal_id': self.journal.id, 'payment_ids': [Command.set(payments.ids)], 'payment_method_id': self.batch_deposit_method.id, }) batch.validate_batch() st_line.set_batch_payment_bank_statement_line(payments.batch_payment_id.id) self.assertRecordValues(st_line.line_ids, [ {'account_id': st_line.journal_id.default_account_id.id, 'amount_currency': 180.0, 'balance': 180.0, 'reconciled': False}, {'account_id': invoice_lines_with_epd[0].account_id.id, 'amount_currency': -100.0, 'balance': -100.0, 'reconciled': True}, {'account_id': early_pay_acc.id, 'amount_currency': 10.0, 'balance': 10.0, 'reconciled': False}, {'account_id': invoice_lines_with_epd[1].account_id.id, 'amount_currency': -100.0, 'balance': -100.0, 'reconciled': True}, {'account_id': early_pay_acc.id, 'amount_currency': 10.0, 'balance': 10.0, 'reconciled': False}, ]) self.assertEqual(payments.mapped('state'), ['paid', 'paid']) self.assertEqual(batch.state, 'reconciled') st_line.delete_reconciled_line(st_line.line_ids[1].id) self.assertEqual(payments[0].state, 'in_process') self.assertEqual(batch.state, 'sent') def test_bank_rec_widget_batch_payment_without_entries(self): payment = self.create_payment(self.partner_a, 100, payment_method_line_id=self.batch_deposit.id) payment.create_batch_payment() st_line = self._create_st_line(amount=100) st_line.set_batch_payment_bank_statement_line(payment.batch_payment_id.id) self.assertRecordValues(st_line.line_ids, [ {'account_id': st_line.journal_id.default_account_id.id, 'name': st_line.payment_ref, 'amount_currency': 100.0, 'currency_id': self.company_data['currency'].id, 'balance': 100.0, 'reconciled': False}, {'account_id': self.partner_a.property_account_receivable_id.id, 'name': payment.name, 'amount_currency': -100.0, 'currency_id': self.company_data['currency'].id, 'balance': -100.0, 'reconciled': False}, ]) def test_bank_rec_widget_batch_payment_without_entries_link_to_move(self): invoice = self.env['account.move'].create({ 'move_type': 'out_invoice', 'partner_id': self.partner_a.id, 'invoice_date': '2017-01-01', 'invoice_line_ids': [ Command.create({ 'name': 'Line', 'price_unit': 100, }), ], }) invoice.action_post() payment = self.env['account.payment.register'].with_context( active_model='account.move', active_ids=invoice.ids ).create({ 'payment_date': '2019-01-01', 'payment_method_line_id': self.batch_deposit.id, })._create_payments() payment.create_batch_payment() st_line = self._create_st_line(amount=100) st_line.set_batch_payment_bank_statement_line(payment.batch_payment_id.id) self.assertRecordValues(st_line.line_ids, [ {'account_id': st_line.journal_id.default_account_id.id, 'amount_currency': 100.0, 'balance': 100.0, 'reconciled': False}, {'account_id': invoice.line_ids[-1].account_id.id, 'amount_currency': -100.0, 'balance': -100.0, 'reconciled': True}, ]) self.assertEqual(st_line.line_ids.reconciled_lines_ids, payment.invoice_ids.line_ids.filtered(lambda x: x.account_id.account_type == 'asset_receivable')) st_line.delete_reconciled_line(st_line.line_ids[-1].id) self.assertEqual(payment.state, 'in_process') self.assertEqual(invoice.payment_state, 'in_payment') def test_bank_rec_widget_batch_move_link_to_multiple_payment_without_entries(self): invoice = self.env['account.move'].create({ 'move_type': 'out_invoice', 'partner_id': self.partner_a.id, 'invoice_date': '2017-01-01', 'invoice_line_ids': [ Command.create({ 'name': 'Line', 'price_unit': 1000, }), ], }) invoice.action_post() payment_1 = self.env['account.payment.register'].with_context( active_model='account.move', active_ids=invoice.ids, ).create({ 'amount': 400, 'payment_date': '2019-01-01', 'payment_method_line_id': self.batch_deposit.id, })._create_payments() payment_2 = self.env['account.payment.register'].with_context( active_model='account.move', active_ids=invoice.ids, ).create({ 'amount': 600, 'payment_date': '2019-01-01', 'payment_method_line_id': self.batch_deposit.id, })._create_payments() (payment_1 + payment_2).create_batch_payment() st_line = self._create_st_line(amount=1000) st_line.set_batch_payment_bank_statement_line(payment_1.batch_payment_id.id) 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}, {'account_id': invoice.line_ids[-1].account_id.id, 'amount_currency': -400.0, 'balance': -400.0, 'reconciled': True}, {'account_id': invoice.line_ids[-1].account_id.id, 'amount_currency': -600.0, 'balance': -600.0, 'reconciled': True}, ]) st_line.delete_reconciled_line(st_line.line_ids[-1].id) self.assertEqual(payment_1.state, 'paid') self.assertEqual(payment_2.state, 'in_process') self.assertEqual(invoice.payment_state, 'in_payment') def test_bank_rec_widget_batch_one_move_multiple_payment_without_entries(self): invoice_1 = self.env['account.move'].create({ 'move_type': 'out_invoice', 'partner_id': self.partner_a.id, 'invoice_date': '2017-01-01', 'invoice_line_ids': [ Command.create({ 'name': 'Line', 'price_unit': 1000, }), ], }) invoice_1.action_post() invoice_2 = self.env['account.move'].create({ 'move_type': 'out_invoice', 'partner_id': self.partner_a.id, 'invoice_date': '2017-01-01', 'invoice_line_ids': [ Command.create({ 'name': 'Line', 'price_unit': 1000, }), ], }) invoice_2.action_post() payments = self.env['account.payment.register'].with_context( active_model='account.move', active_ids=(invoice_1 + invoice_2).ids, ).create({ 'amount': 2000, 'payment_date': '2019-01-01', 'payment_method_line_id': self.batch_deposit.id, })._create_payments() payments.create_batch_payment() st_line = self._create_st_line(amount=2000) st_line.set_batch_payment_bank_statement_line(payments.batch_payment_id.id) self.assertRecordValues(st_line.line_ids, [ {'account_id': st_line.journal_id.default_account_id.id, 'amount_currency': 2000.0, 'balance': 2000.0, 'reconciled': False}, {'account_id': invoice_1.line_ids[-1].account_id.id, 'amount_currency': -1000.0, 'balance': -1000.0, 'reconciled': True}, {'account_id': invoice_2.line_ids[-1].account_id.id, 'amount_currency': -1000.0, 'balance': -1000.0, 'reconciled': True}, ]) def test_bank_rec_widget_batch_one_move_multiple_payment_without_entries_grouped(self): invoice_1 = self.env['account.move'].create({ 'move_type': 'out_invoice', 'partner_id': self.partner_a.id, 'invoice_date': '2017-01-01', 'invoice_line_ids': [ Command.create({ 'name': 'Line', 'price_unit': 1000, }), ], }) invoice_1.action_post() invoice_2 = self.env['account.move'].create({ 'move_type': 'out_invoice', 'partner_id': self.partner_a.id, 'invoice_date': '2017-01-01', 'invoice_line_ids': [ Command.create({ 'name': 'Line', 'price_unit': 1000, }), ], }) invoice_2.action_post() payments = self.env['account.payment.register'].with_context( active_model='account.move', active_ids=(invoice_1 + invoice_2).ids, ).create({ 'amount': 2000, 'payment_date': '2019-01-01', 'payment_method_line_id': self.batch_deposit.id, 'group_payment': True, })._create_payments() payments.create_batch_payment() st_line = self._create_st_line(amount=2000) st_line.set_batch_payment_bank_statement_line(payments.batch_payment_id.id) self.assertRecordValues(st_line.line_ids, [ {'account_id': st_line.journal_id.default_account_id.id, 'amount_currency': 2000.0, 'balance': 2000.0, 'reconciled': False}, {'account_id': invoice_1.line_ids[-1].account_id.id, 'amount_currency': -1000.0, 'balance': -1000.0, 'reconciled': True}, {'account_id': invoice_2.line_ids[-1].account_id.id, 'amount_currency': -1000.0, 'balance': -1000.0, 'reconciled': True}, ]) def test_bank_rec_widget_batch_without_entries_grouped_with_bills(self): bills_1 = self.env['account.move'].create({ 'move_type': 'in_invoice', 'partner_id': self.partner_a.id, 'invoice_date': '2017-01-01', 'invoice_line_ids': [ Command.create({ 'name': 'Line', 'price_unit': 1000, }), ], }) bills_1.action_post() bills_2 = self.env['account.move'].create({ 'move_type': 'in_invoice', 'partner_id': self.partner_a.id, 'invoice_date': '2017-01-01', 'invoice_line_ids': [ Command.create({ 'name': 'Line', 'price_unit': 1000, }), ], }) bills_2.action_post() payments = self.env['account.payment.register'].with_context( active_model='account.move', active_ids=(bills_1 + bills_2).ids, ).create({ 'amount': 2000, 'payment_date': '2019-01-01', 'payment_method_line_id': self.batch_deposit.id, 'group_payment': True, })._create_payments() payments.create_batch_payment() st_line = self._create_st_line(amount=-2000) st_line.set_batch_payment_bank_statement_line(payments.batch_payment_id.id) self.assertRecordValues(st_line.line_ids, [ {'account_id': st_line.journal_id.default_account_id.id, 'amount_currency': -2000.0, 'balance': -2000.0, 'reconciled': False}, {'account_id': bills_1.line_ids[-1].account_id.id, 'amount_currency': 1000.0, 'balance': 1000.0, 'reconciled': True}, {'account_id': bills_2.line_ids[-1].account_id.id, 'amount_currency': 1000.0, 'balance': 1000.0, 'reconciled': True}, ]) def test_bank_rec_widget_batch_with_epd_with_exch_diff_without_entries(self): """ Tests a batch payment of a grouped payment with an amount too large for: - 1 invoice with an early payment discount AND exchange diff - 1 invoice with an early payment discount During reconciliation, a payment with move should be created for the first invoice (to correctly handle the EPD), the second invoice should correctly be added to the widget, and the surplus should be added as a payment. """ chf_currency = self.setup_other_currency('CHF', rates=[('2016-01-01', 2.0), ('2019-01-03', 4.0)]) st_line = self._create_st_line(200.0, date='2019-01-05', foreign_currency_id=chf_currency.id) early_pay_acc = self.env.company.account_journal_early_pay_discount_loss_account_id invoice_with_exch_diff = self._create_invoice_line( 'out_invoice', invoice_date='2019-01-01', invoice_payment_term_id=self.early_payment_term.id, invoice_line_ids=[{'price_unit': 100.0}], currency_id=chf_currency.id, ) invoice_without_exch_diff = self._create_invoice_line( 'out_invoice', invoice_date='2019-01-04', invoice_payment_term_id=self.early_payment_term.id, invoice_line_ids=[{'price_unit': 100.0}], currency_id=chf_currency.id, ) payment = self.env['account.payment.register'].with_context( active_model='account.move', active_ids=(invoice_with_exch_diff + invoice_without_exch_diff).move_id.ids, ).create({ 'group_payment': True, 'amount': 200, 'payment_date': '2019-01-04', 'payment_method_line_id': self.batch_deposit.id, })._create_payments() outstanding_account = self.env['account.payment']._get_outstanding_account(payment.payment_type) batch = self.env['account.batch.payment'].create({ 'batch_type': payment.payment_type, 'journal_id': self.journal.id, 'payment_ids': [Command.set(payment.ids)], 'payment_method_id': self.batch_deposit_method.id, }) batch.validate_batch() self.assertEqual(batch.amount, 50.0) st_line.set_batch_payment_bank_statement_line(payment.batch_payment_id.id) self.assertRecordValues(st_line.line_ids, [ {'account_id': st_line.journal_id.default_account_id.id, 'amount_currency': 200.0, 'balance': 200.0, 'reconciled': False}, # The first invoice and payment have been changed into a payment with move to handle the exchange diff. The next line comes from that move. {'account_id': outstanding_account.id, 'amount_currency': -90.0, 'balance': -22.5, 'reconciled': True}, # The 2 following lines correspond to the second invoice + EPD. {'account_id': invoice_without_exch_diff.account_id.id, 'amount_currency': -100.0, 'balance': -25.0, 'reconciled': True}, {'account_id': early_pay_acc.id, 'amount_currency': 10.0, 'balance': 2.5, 'reconciled': False}, # The remaining amount from the payment is added as is. {'account_id': payment.partner_id.property_account_receivable_id.id, 'amount_currency': -20.0, 'balance': -5.0, 'reconciled': False}, {'account_id': st_line.journal_id.suspense_account_id.id, 'amount_currency': -600.0, 'balance': -150.0, 'reconciled': False}, ]) self.assertEqual(payment.state, 'paid') self.assertEqual(batch.state, 'reconciled') self.assertEqual(payment.amount, 110.0, "The creation of the payment with move during reconciliation should have diminished the grouped payment amount.") def test_bank_rec_widget_batch_foreign_currency_journal_without_entries(self): """ Tests a batch payment of payments recorded in another journal with foreign currency and no outstanding account set. - 2 invoices in company currency paid in foreign currency - 1 bank transaction in foreign currency """ chf_currency = self.setup_other_currency('CHF', rates=[('2019-01-01', 1.5)]) foreign_journal = self.env['account.journal'].create({'name': 'CHF journal', 'type': 'bank', 'code': 'BNKX', 'currency_id': chf_currency.id}) invoice_1 = self._create_invoice_line( 'out_invoice', invoice_date='2019-01-01', invoice_line_ids=[{'price_unit': 100.0}], ) invoice_2 = self._create_invoice_line( 'out_invoice', invoice_date='2019-01-01', invoice_line_ids=[{'price_unit': 200.0}], ) payment_1 = self.env['account.payment.register'].with_context( active_model='account.move', active_ids=invoice_1.move_id.ids, ).create({ 'amount': 150, 'payment_date': '2019-01-01', 'journal_id': foreign_journal.id, })._create_payments() payment_2 = self.env['account.payment.register'].with_context( active_model='account.move', active_ids=invoice_2.move_id.ids, ).create({ 'amount': 300, 'payment_date': '2019-01-01', 'journal_id': foreign_journal.id, })._create_payments() batch = self.env['account.batch.payment'].create({ 'batch_type': payment_1.payment_type, 'journal_id': foreign_journal.id, 'payment_ids': [Command.set((payment_1 | payment_2).ids)], }) batch.validate_batch() st_line = self._create_st_line(450.0, date='2019-01-05', foreign_currency_id=chf_currency.id, journal_id=foreign_journal.id) st_line.set_batch_payment_bank_statement_line(batch.id) if self.env['account.move']._get_invoice_in_payment_state() == 'paid': expected_account = self.env['account.payment']._get_outstanding_account(payment_1.payment_type).id else: expected_account = self.partner_a.property_account_receivable_id.id self.assertRecordValues(st_line.line_ids, [ {'account_id': st_line.journal_id.default_account_id.id, 'amount_currency': 450.0, 'balance': 300.0, 'reconciled': False}, {'account_id': expected_account, 'amount_currency': -150.0, 'balance': -100.0, 'reconciled': True}, {'account_id': expected_account, 'amount_currency': -300.0, 'balance': -200.0, 'reconciled': True}, ]) self.assertEqual(invoice_1.move_id.payment_state, 'paid') self.assertEqual(invoice_2.move_id.payment_state, 'paid') self.assertEqual(batch.state, 'reconciled') def test_payment_state_after_invoice_edition_without_journal_entry(self): """ Test that a bank statement reconciled with a batch payment is unreconciled and the state of all the payments is set to "In Process" when the amount of one of the invoices is modified. """ self.partner_b.property_payment_term_id = self.pay_terms_a invoice_1 = self.init_invoice('out_invoice', partner=self.partner_a, amounts=[90.0], post=True) invoice_2 = self.init_invoice('out_invoice', partner=self.partner_a, amounts=[10.0], post=True) invoice_3 = self.init_invoice('out_invoice', partner=self.partner_b, amounts=[50.0], post=True) invoice_4 = self.init_invoice('out_invoice', partner=self.partner_b, amounts=[40.0], post=True) # create a group payment for invoice 1 and 2 active_ids = (invoice_1 + invoice_2).ids group_payment = self.env['account.payment.register'].with_context(active_model='account.move', active_ids=active_ids).create({ 'amount': 100.0, 'group_payment': True, 'payment_method_line_id': self.batch_deposit.id, })._create_payments() # create a simple payment for invoice 3 payment_3 = self.env['account.payment.register'].with_context(active_model='account.move', active_ids=invoice_3.ids).create({ 'amount': 50.0, 'payment_method_line_id': self.batch_deposit.id, })._create_payments() # create a simple payment for invoice 4 payment_4 = self.env['account.payment.register'].with_context(active_model='account.move', active_ids=invoice_4.ids).create({ 'amount': 40.0, 'payment_method_line_id': self.batch_deposit.id, })._create_payments() payments = group_payment + payment_3 + payment_4 payments.action_post() # create a batch payment for the 3 payments batch_payment_action = payments.create_batch_payment() batch_payment = self.env['account.batch.payment'].browse(batch_payment_action.get('res_id')) batch_payment.validate_batch() # create a statement line and reconcile it with the batch payment st_line = self._create_st_line(190.0, payment_ref=batch_payment.name) st_line.set_batch_payment_bank_statement_line(batch_payment.id) self.assertTrue(st_line.is_reconciled) for payment in payments: self.assertEqual(payment.state, 'paid') # edit the price of invoice 3 invoice_3.button_draft() with Form(invoice_3) as form: with form.invoice_line_ids.edit(0) as line_form: line_form.price_unit = 30.0 self.assertFalse(st_line.is_reconciled) for payment in payments: self.assertEqual(payment.state, 'in_process')