# -*- coding: utf-8 -*- from odoo import Command from odoo.addons.account.tests.common import AccountTestInvoicingCommon from odoo.tests import tagged from odoo.exceptions import RedirectWarning, ValidationError @tagged('post_install', '-at_install') class TestAccountBatchPayment(AccountTestInvoicingCommon): @classmethod def setUpClass(cls): super().setUpClass() cls.env.user.group_ids |= cls.env.ref('account.group_validate_bank_account') cls.other_currency = cls.setup_other_currency('EUR') cls.other_currency_2 = cls.setup_other_currency('CHF') cls.payment_debit_account_id = cls.copy_account(cls.inbound_payment_method_line.payment_account_id) cls.payment_credit_account_id = cls.copy_account(cls.outbound_payment_method_line.payment_account_id) 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', }) def _create_multi_company_payments_and_context(self, companies_dict, add_company_context=None): payments = self.env['account.payment'] companies_context = self.env['res.company'] field_record = self.env['ir.model.fields']._get('res.partner', 'property_account_receivable_id') property_account_receivable = self.env['ir.default'].search( [('field_id', '=', field_record.id), ('company_id', '=', self.company_data['company'].id)], limit=1 ) for company, create_property_account_receivable in companies_dict.items(): if create_property_account_receivable: # needed for computation of payment.destination_account_id property_account_receivable.copy({'company_id': company.id}) payment = self.env['account.payment'].with_company(company).create({ 'amount': 100.0, 'payment_type': 'inbound', 'partner_type': 'customer', 'partner_id': self.partner_a.id, }) payment.action_post() payments += payment companies_context += company if add_company_context: companies_context += add_company_context context = { **self.env.context, 'allowed_company_ids': companies_context.ids, 'active_ids': payments.ids, 'active_model': 'account.payment', } return payments, context def test_create_batch_payment_from_payment(self): payments = self.env['account.payment'] for _i in range(2): payments += self.env['account.payment'].create({ 'amount': 100.0, 'payment_type': 'outbound', 'partner_type': 'supplier', 'partner_id': self.partner_a.id, 'destination_account_id': self.partner_a.property_account_payable_id.id, 'currency_id': self.other_currency.id, 'partner_bank_id': self.partner_bank_account.id, }) payments.action_post() batch_payment_action = payments.create_batch_payment() batch_payment_id = self.env['account.batch.payment'].browse(batch_payment_action.get('res_id')) self.assertEqual(len(batch_payment_id.payment_ids), 2) def test_change_payment_state(self): """ Check if the amount is well computed when we change a payment state """ payments = self.env['account.payment'] for _ in range(2): payments += self.env['account.payment'].create({ 'amount': 100.0, 'payment_type': 'inbound', 'partner_type': 'supplier', 'partner_id': self.partner_a.id, 'destination_account_id': self.partner_a.property_account_payable_id.id, 'partner_bank_id': self.partner_bank_account.id, }) payments.action_post() batch_payment = self.env['account.batch.payment'].create( { 'journal_id': payments.journal_id.id, 'payment_method_id': payments.payment_method_id.id, 'payment_ids': [ (6, 0, payments.ids) ], } ) self.assertRecordValues(batch_payment, [{ 'amount': 200.0, 'amount_residual': 200.0, 'amount_residual_currency': 200.0, }]) payments[0].move_id.button_draft() # Check that we still keep it self.assertRecordValues(batch_payment, [{ 'amount': 200.0, 'amount_residual': 200.0, 'amount_residual_currency': 200.0, }]) def test_change_payment_state_valid(self): """ Check if the amount is well computed when we change a payment state into a non valid payment status """ if self.env['account.move']._get_invoice_in_payment_state() != 'in_payment': self.skipTest('Accounting not installed') payments = self.env['account.payment'] bank_journal_2 = self.company_data['default_journal_bank'].copy() for _ in range(2): payments += self.env['account.payment'].create({ 'amount': 100.0, 'payment_type': 'inbound', 'partner_type': 'supplier', 'partner_id': self.partner_a.id, 'journal_id': bank_journal_2.id, }) payments.action_post() batch_payment = self.env['account.batch.payment'].create( { 'journal_id': payments.journal_id.id, 'payment_method_id': payments.payment_method_id.id, 'payment_ids': [ (6, 0, payments.ids) ], } ) self.assertRecordValues(batch_payment, [{ 'amount': 200.0, 'amount_residual': 200.0, 'amount_residual_currency': 200.0, }]) # Change move state to 'paid', if accounting is installed it won't be a valid payment state for batches payments[0].action_validate() self.assertRecordValues(batch_payment, [{ 'amount': 200.0, 'amount_residual': 100.0, 'amount_residual_currency': 100.0, }]) def test_validate_batch(self): """ Check that we can only validate a batch if all the payments are in progress """ payments = self.env['account.payment'] for _ in range(2): payments += self.env['account.payment'].create({ 'amount': 100.0, 'payment_type': 'inbound', 'partner_type': 'supplier', 'partner_id': self.partner_a.id, 'destination_account_id': self.partner_a.property_account_payable_id.id, 'partner_bank_id': self.partner_bank_account.id, }) payments.action_post() batch_payment = self.env['account.batch.payment'].create( { 'journal_id': payments.journal_id.id, 'payment_method_id': payments.payment_method_id.id, 'payment_ids': [Command.set(payments.ids)] } ) payments[0].action_validate() # In accounting, we can only validate a batch if all the payments are in process # While in enterprise invoicing, we can validate a batch even if some payments are paid is_accounting_installed = self.env['account.move']._get_invoice_in_payment_state() == 'in_payment' if is_accounting_installed: with self.assertRaisesRegex(RedirectWarning, "To validate the batch, payments must be in process"): batch_payment.validate_batch() # Set payment to in process state payments[0].action_draft() payments[0].action_post() action = batch_payment.validate_batch() self.assertFalse(action) def test_batch_payment_sub_company(self): """Test the creation of a batch payment from a sub company""" self.company_data['company'].write({'child_ids': [Command.create({'name': 'Good Company'})]}) child_comp = self.company_data['company'].child_ids[0] payment, context = self._create_multi_company_payments_and_context({child_comp: True}) batch = self.env['account.batch.payment'].with_context(context).create({ 'journal_id': payment.journal_id.id, }) self.assertTrue(batch) def test_batch_payment_branches(self): """ Test the creation of a batch payment with branches. When all payments are branches of a common head office, a batch payment should be allowed to be created. """ main_company = self.company_data['company'] main_company.vat = '123' branch_1 = self.env['res.company'].create({'name': "Branch 1", 'parent_id': main_company.id}) branch_2 = self.env['res.company'].create({'name': "Branch 2", 'parent_id': main_company.id, 'vat': '456'}) branch_2_1 = self.env['res.company'].create({'name': "Branch 2 sub-branch 1", 'parent_id': branch_2.id}) payments, context = self._create_multi_company_payments_and_context({branch_1: True, branch_2: True, branch_2_1: True}) batch = self.env['account.batch.payment'].with_context(context).create({ 'journal_id': payments[0].journal_id.id, 'payment_ids': payments.ids, }) self.assertTrue(batch) def test_batch_payment_different_companies(self): """ Payments from different companies not belonging to the same head company should raise an error. """ main_company = self.company_data['company'] company_b = self.setup_other_company()['company'] payments, context = self._create_multi_company_payments_and_context({main_company: False, company_b: False}, main_company) with self.assertRaisesRegex(ValidationError, "The journal of the batch payment and of the payments it contains must be the same."): self.env['account.batch.payment'].with_context(context).create({ 'journal_id': payments[0].journal_id.id, 'payment_ids': payments.ids, }) def test_batch_payment_foreign_currency(self): """ Make sure that payments in foreign currency are converted for the total amount to be displayed currency rate = 1$:10€ amount_company_currency = 100$ amount_foreign_currency = 100€ -> 10$ => batch.amount = 110$ """ payments = self.env['account.payment'] company_currency = self.env.company.currency_id foreign_currency = self.other_currency self.env['res.currency.rate'].create({ 'name': '2024-05-14', 'rate': 10, 'currency_id': foreign_currency.id, 'company_id': self.env.company.id, }) for currency in (company_currency, foreign_currency): payments += self.env['account.payment'].create({ 'amount': 100.0, 'payment_type': 'inbound', 'partner_type': 'supplier', 'partner_id': self.partner_a.id, 'currency_id': currency.id, 'date': '2024-05-14', }) payments.action_post() batch_payment_action = payments.create_batch_payment() batch_payment = self.env['account.batch.payment'].browse(batch_payment_action.get('res_id')) self.assertRecordValues(batch_payment, [{ 'amount': 110.0, 'amount_residual': 110.0, 'amount_residual_currency': 110.0, }]) def test_batch_payment_move_different_currencies(self): """ Make sure that payments linked to a move in foreign currency 1 are converted correctly when the batch is in foreign currency 2 """ payments = self.env['account.payment'] bank_journal_2 = self.company_data['default_journal_bank'].copy({'currency_id': self.other_currency_2.id}) outstanding_payment_B = self.inbound_payment_method_line.payment_account_id.copy() bank_journal_2.inbound_payment_method_line_ids.payment_account_id = outstanding_payment_B for currency, rate in [(self.other_currency, 10), (self.other_currency_2, 20)]: self.env['res.currency.rate'].create({ 'name': '2024-05-14', 'rate': rate, 'currency_id': currency.id, 'company_id': self.env.company.id, }) for amount in (100.0, 15.0): payments += self.env['account.payment'].create({ 'amount': amount, 'payment_type': 'inbound', 'partner_type': 'supplier', 'partner_id': self.partner_a.id, 'currency_id': self.other_currency.id, 'journal_id': bank_journal_2.id, 'date': '2024-05-14', }) payments.action_post() batch_payment_action = payments.create_batch_payment() batch_payment = self.env['account.batch.payment'].browse(batch_payment_action.get('res_id')) self.assertRecordValues(batch_payment, [{ 'amount': 230.0, 'amount_residual': 11.5, 'amount_residual_currency': 230.0, }]) def test_foreign_currency_batch_payment(self): """ Make sure that payments in company_currency are converted when the batch is in foreign currency """ payments = self.env['account.payment'] foreign_currency = self.other_currency bank_journal_2 = self.company_data['default_journal_bank'].copy() self.env['res.currency.rate'].create({ 'name': '2024-05-14', 'rate': 10, 'currency_id': foreign_currency.id, 'company_id': self.env.company.id, }) for amount in (100, 15): payments += self.env['account.payment'].create({ 'amount': amount, 'payment_type': 'inbound', 'partner_type': 'supplier', 'partner_id': self.partner_a.id, 'currency_id': self.other_currency.id, 'date': '2024-05-14', 'journal_id': bank_journal_2.id, }) payments.action_post() batch_payment_action = payments.create_batch_payment() batch_payment = self.env['account.batch.payment'].browse(batch_payment_action.get('res_id')) self.assertRecordValues(batch_payment, [{ 'amount': 11.5, 'amount_residual': 11.5, 'amount_residual_currency': 11.5, }]) def test_batch_payment_journal_foreign_currency(self): """ Test that, if a bank journal is set in a foreign currency, the batch payment will be correctly converted currency rate = 1$:10€ payment of 100€ -> 100☺ payment of 100$ -> 1000☺ Total -> 1100 """ payments = self.env['account.payment'] company_currency = self.env.company.currency_id foreign_currency = self.other_currency self.env['res.currency.rate'].create({ 'name': '2024-05-14', 'rate': 10, 'currency_id': foreign_currency.id, 'company_id': self.env.company.id, }) bank_journal_foreign = self.env['account.journal'].create({ 'name': 'Bank2', 'type': 'bank', 'code': 'BNK2', 'currency_id': foreign_currency.id, }) for currency in (company_currency, foreign_currency): payments += self.env['account.payment'].create({ 'amount': 100.0, 'payment_type': 'inbound', 'partner_type': 'supplier', 'partner_id': self.partner_a.id, 'currency_id': currency.id, 'date': '2024-05-14', 'journal_id': bank_journal_foreign.id }) payments.action_post() batch_payment_action = payments.create_batch_payment() batch_payment = self.env['account.batch.payment'].browse(batch_payment_action.get('res_id')) self.assertRecordValues(batch_payment, [{ 'amount': 1100.0, 'amount_residual': 110.0, 'amount_residual_currency': 1100.0, }]) def test_create_batch_from_payment_already_in_batch(self): payment = self.env['account.payment'].create({ 'amount': 100.0, 'payment_type': 'outbound', 'partner_type': 'supplier', 'partner_id': self.partner_a.id, 'destination_account_id': self.partner_a.property_account_payable_id.id, 'currency_id': self.other_currency.id, 'partner_bank_id': self.partner_bank_account.id, }) payment.action_post() batch_payment_action = payment.create_batch_payment() batch_payment_id = self.env['account.batch.payment'].browse(batch_payment_action.get('res_id')) batch_payment_id.validate_batch() with self.assertRaises(ValidationError): payment.create_batch_payment() def test_amount_in_paid_state(self): """ Verify that the batch payment amount is correctly computed when the payment state is 'paid'. """ payments = self.env['account.payment'] # Create two inbound payments of 100€ each for _ in range(2): payments += self.env['account.payment'].create({ 'amount': 100.0, 'payment_type': 'inbound', 'partner_type': 'supplier', 'partner_id': self.partner_a.id, 'destination_account_id': self.partner_a.property_account_payable_id.id, 'partner_bank_id': self.partner_bank_account.id, }) # Post the payments to validate them payments.action_post() # Update payment states to 'paid' payments.write({'state': 'paid'}) # Ensure all payments are now in the 'paid' state self.assertTrue(all(payment.state == 'paid' for payment in payments), "Payments should be in 'paid' state") # Create a batch payment including the two payments batch_payment = self.env['account.batch.payment'].create( { 'journal_id': payments.journal_id.id, 'payment_method_id': payments.payment_method_id.id, 'payment_ids': [Command.set(payments.ids)], } ) # Ensure the amount remains correct after recomputation # When accountant is not installed, payments with paid state # should be counted when recomputing amount_residual if self.env['ir.module.module']._get('accountant').state == 'installed': self.assertEqual(batch_payment.amount_residual, 0) self.assertEqual(batch_payment.amount_residual_currency, 0) else: self.assertEqual(batch_payment.amount_residual, 200) self.assertEqual(batch_payment.amount_residual_currency, 200) self.assertEqual(batch_payment.amount, 200)