445 lines
19 KiB
Python
445 lines
19 KiB
Python
from odoo import Command
|
|
from odoo.tests import tagged
|
|
|
|
from odoo.addons.account_iso20022.tests.test_sepa_credit_transfer import TestSEPACreditTransferCommon
|
|
|
|
|
|
@tagged('post_install', '-at_install')
|
|
class TestIsoReconciliation(TestSEPACreditTransferCommon):
|
|
@classmethod
|
|
def setUpClass(cls):
|
|
super().setUpClass()
|
|
|
|
branch = cls._create_company(
|
|
name='Branch Company',
|
|
parent_id=cls.company_data['company'].id,
|
|
)
|
|
cls.branch_data = cls.collect_company_accounting_data(branch)
|
|
|
|
unrelated = cls._create_company(name='Unrelated Company')
|
|
cls.unrelated_data = cls.collect_company_accounting_data(unrelated)
|
|
|
|
def test_matching_on_end_to_end_uuid(self):
|
|
"""
|
|
Assert that a payment and a bank statement line are matched if they have the same end_to_end_uuid.
|
|
"""
|
|
bill = self.env['account.move'].create({
|
|
'partner_id': self.partner_a.id,
|
|
'move_type': 'in_invoice',
|
|
'invoice_date': '2025-01-01',
|
|
'invoice_line_ids': [Command.create({
|
|
'quantity': 1,
|
|
'price_unit': 100,
|
|
})],
|
|
})
|
|
bill.action_post()
|
|
bill_line = bill.line_ids.filtered(lambda l: l.account_id.account_type in {'asset_receivable', 'liability_payable'})
|
|
|
|
# The payment_method_line_id should have a payment_account_id value to generate the correct journal entries.
|
|
sepa_ct_method_line = self.bank_journal.outbound_payment_method_line_ids.filtered(lambda l: l.code == 'sepa_ct')
|
|
sepa_ct_method_line.payment_account_id = self.outbound_payment_method_line.payment_account_id
|
|
payment = self.env['account.payment.register'].with_context(
|
|
active_ids=bill_line.move_id.ids,
|
|
active_model='account.move',
|
|
).create({
|
|
'amount': 100,
|
|
'journal_id': self.bank_journal.id,
|
|
'payment_date': '2010-01-01',
|
|
'payment_method_line_id': sepa_ct_method_line.id,
|
|
})._create_payments()
|
|
|
|
bank_statement_line = self.env['account.bank.statement.line'].create([
|
|
{
|
|
'journal_id': self.bank_journal.id,
|
|
'date': '2020-01-01',
|
|
'payment_ref': 'PAYMENT',
|
|
'amount': -100,
|
|
'sequence': 1,
|
|
'end_to_end_uuid': payment.end_to_end_uuid,
|
|
},
|
|
])
|
|
|
|
bank_statement_line._try_auto_reconcile_statement_lines()
|
|
# Assert that the statement line has been reconciled with the payment and the invoice.
|
|
self.assertEqual(
|
|
payment.move_id.line_ids._all_reconciled_lines(),
|
|
payment.move_id.line_ids | bank_statement_line.line_ids[1] | bill_line
|
|
)
|
|
# Assert that the payment has been automatically validated.
|
|
self.assertEqual(payment.state, 'paid')
|
|
|
|
def test_matching_end_to_end_uuid_no_payment_entry(self):
|
|
# Check if accounting is installed as this test doesn't make sense without accounting installed
|
|
if self.env.ref('base.module_accountant').state != 'installed':
|
|
self.skipTest("`accountant` module not installed")
|
|
|
|
bill = self.env['account.move'].create({
|
|
'partner_id': self.partner_a.id,
|
|
'move_type': 'in_invoice',
|
|
'invoice_date': '2025-01-01',
|
|
'invoice_line_ids': [Command.create({
|
|
'quantity': 1,
|
|
'price_unit': 100,
|
|
})],
|
|
})
|
|
bill.action_post()
|
|
bill_line = bill.line_ids.filtered(lambda line: line.account_id.account_type in {'asset_receivable', 'liability_payable'})
|
|
|
|
payment = self.env['account.payment.register'].with_context(
|
|
active_ids=bill_line.move_id.ids,
|
|
active_model='account.move',
|
|
).create({
|
|
'amount': 100,
|
|
'journal_id': self.bank_journal.id,
|
|
'payment_date': '2010-01-01',
|
|
'payment_method_line_id': self.bank_journal.outbound_payment_method_line_ids.filtered(lambda l: l.code == 'sepa_ct').id,
|
|
})._create_payments()
|
|
|
|
bank_statement_line = self.env['account.bank.statement.line'].create([
|
|
{
|
|
'journal_id': self.bank_journal.id,
|
|
'date': '2020-01-01',
|
|
'payment_ref': 'PAYMENT',
|
|
'amount': -100,
|
|
'sequence': 1,
|
|
'end_to_end_uuid': payment.end_to_end_uuid,
|
|
},
|
|
])
|
|
|
|
bank_statement_line._try_auto_reconcile_statement_lines()
|
|
|
|
self.assertEqual(
|
|
payment.invoice_ids.line_ids.filtered(lambda line: line.account_type == 'liability_payable'),
|
|
bank_statement_line.line_ids.reconciled_lines_ids,
|
|
)
|
|
# Assert that the payment has been automatically validated.
|
|
self.assertEqual(payment.state, 'paid')
|
|
|
|
def test_matching_end_to_end_uuid_single_payment(self):
|
|
# Check if accounting is installed as this test doesn't make sense without accounting installed
|
|
if self.env.ref('base.module_accountant').state != 'installed':
|
|
self.skipTest("`accountant` module not installed")
|
|
payment = self.env['account.payment'].create({
|
|
'amount': 100,
|
|
'journal_id': self.bank_journal.id,
|
|
'date': '2010-01-01',
|
|
'partner_id': self.partner_a.id,
|
|
'payment_method_line_id': self.bank_journal.outbound_payment_method_line_ids.filtered(lambda l: l.code == 'sepa_ct').id,
|
|
'payment_type': 'outbound',
|
|
})
|
|
payment.action_post()
|
|
|
|
bank_statement_line = self.env['account.bank.statement.line'].create([
|
|
{
|
|
'journal_id': self.bank_journal.id,
|
|
'date': '2020-01-01',
|
|
'payment_ref': 'PAYMENT',
|
|
'amount': -100,
|
|
'sequence': 1,
|
|
'end_to_end_uuid': payment.end_to_end_uuid,
|
|
},
|
|
])
|
|
bank_statement_line._try_auto_reconcile_statement_lines()
|
|
# Assert that the payment has been automatically validated.
|
|
self.assertEqual(payment.state, 'paid')
|
|
|
|
self.assertRecordValues(
|
|
bank_statement_line.line_ids,
|
|
[
|
|
{
|
|
'balance': -100.0,
|
|
'account_id': bank_statement_line.journal_id.default_account_id.id,
|
|
'name': bank_statement_line.payment_ref,
|
|
},
|
|
{
|
|
'balance': 100.0,
|
|
'account_id': self.partner_a.property_account_payable_id.id,
|
|
'name': payment.name,
|
|
}
|
|
]
|
|
)
|
|
|
|
def test_no_end_to_end_uuid_matching_across_unrelated_companies(self):
|
|
"""
|
|
Assert that a bank statement line is NOT matched against a payment from an
|
|
unrelated company via end_to_end_uuid, even when both share the same UUID
|
|
(e.g. two companies on the same database doing an intercompany bank transfer).
|
|
"""
|
|
bill = self.env['account.move'].create({
|
|
'partner_id': self.partner_a.id,
|
|
'move_type': 'in_invoice',
|
|
'invoice_date': '2025-01-01',
|
|
'invoice_line_ids': [Command.create({
|
|
'quantity': 1,
|
|
'price_unit': 100,
|
|
})],
|
|
'company_id': self.company_data['company'].id,
|
|
})
|
|
bill.action_post()
|
|
|
|
sepa_ct_method_line = self.bank_journal.outbound_payment_method_line_ids.filtered(lambda l: l.code == 'sepa_ct')
|
|
sepa_ct_method_line.payment_account_id = self.outbound_payment_method_line.payment_account_id
|
|
payment = self.env['account.payment.register'].with_context(
|
|
active_ids=bill.ids,
|
|
active_model='account.move',
|
|
).create({
|
|
'amount': 100,
|
|
'journal_id': self.bank_journal.id,
|
|
'payment_date': '2010-01-01',
|
|
'payment_method_line_id': sepa_ct_method_line.id,
|
|
'company_id': self.company_data['company'].id,
|
|
})._create_payments()
|
|
|
|
bank_statement_line = self.env['account.bank.statement.line'].create({
|
|
'journal_id': self.unrelated_data['default_journal_bank'].id,
|
|
'date': '2020-01-01',
|
|
'payment_ref': 'PAYMENT',
|
|
'amount': -100,
|
|
'end_to_end_uuid': payment.end_to_end_uuid,
|
|
'company_id': self.unrelated_data['company'].id,
|
|
})
|
|
|
|
# sudo() replicates the SUPERUSER context used by the auto-reconciliation cron,
|
|
# which has access to all companies and is what triggered the original bug.
|
|
bank_statement_line.sudo()._try_auto_reconcile_statement_lines()
|
|
|
|
self.assertFalse(bank_statement_line.is_reconciled)
|
|
|
|
def test_matching_end_to_end_uuid_draft_payment(self):
|
|
payment = self.env['account.payment'].create({
|
|
'amount': 100,
|
|
'journal_id': self.bank_journal.id,
|
|
'date': '2010-01-01',
|
|
'partner_id': self.partner_a.id,
|
|
'payment_method_line_id': self.bank_journal.outbound_payment_method_line_ids.filtered(lambda l: l.code == 'sepa_ct').id,
|
|
'payment_type': 'outbound',
|
|
})
|
|
|
|
bank_statement_line = self.env['account.bank.statement.line'].create([
|
|
{
|
|
'journal_id': self.bank_journal.id,
|
|
'date': '2020-01-01',
|
|
'payment_ref': 'PAYMENT',
|
|
'amount': -100,
|
|
'sequence': 1,
|
|
'end_to_end_uuid': payment.end_to_end_uuid,
|
|
},
|
|
])
|
|
bank_statement_line._try_auto_reconcile_statement_lines()
|
|
|
|
# Keep draft payment as draft (even if the reconciliation is done)
|
|
self.assertEqual(payment.state, 'draft')
|
|
self.assertRecordValues(
|
|
bank_statement_line.line_ids,
|
|
[
|
|
{
|
|
'balance': -100.0,
|
|
'account_id': bank_statement_line.journal_id.default_account_id.id,
|
|
'name': bank_statement_line.payment_ref,
|
|
},
|
|
{
|
|
'balance': 100.0,
|
|
'account_id': self.partner_a.property_account_payable_id.id,
|
|
'name': payment.name,
|
|
}
|
|
]
|
|
)
|
|
|
|
def test_matching_end_to_end_uuid_only_access_to_branch_company_for_statement_line(self):
|
|
bill = self.env['account.move'].create({
|
|
'partner_id': self.partner_a.id,
|
|
'move_type': 'in_invoice',
|
|
'invoice_date': '2025-01-01',
|
|
'invoice_line_ids': [Command.create({
|
|
'quantity': 1,
|
|
'price_unit': 100,
|
|
})],
|
|
'company_id': self.company_data['company'].id,
|
|
})
|
|
bill.action_post()
|
|
|
|
# The payment_method_line_id should have a payment_account_id value to generate the correct journal entries.
|
|
sepa_ct_method_line = self.bank_journal.outbound_payment_method_line_ids.filtered(lambda l: l.code == 'sepa_ct')
|
|
sepa_ct_method_line.payment_account_id = self.outbound_payment_method_line.payment_account_id
|
|
payment = self.env['account.payment.register'].with_context(
|
|
active_ids=bill.ids,
|
|
active_model='account.move',
|
|
).create({
|
|
'amount': 100,
|
|
'journal_id': self.bank_journal.id,
|
|
'payment_date': '2010-01-01',
|
|
'payment_method_line_id': sepa_ct_method_line.id,
|
|
'company_id': self.company_data['company'].id,
|
|
})._create_payments()
|
|
|
|
bank_statement_line = self.env['account.bank.statement.line'].create([
|
|
{
|
|
'journal_id': self.bank_journal.id,
|
|
'date': '2020-01-01',
|
|
'payment_ref': 'PAYMENT',
|
|
'amount': -100,
|
|
'sequence': 1,
|
|
'end_to_end_uuid': payment.end_to_end_uuid,
|
|
'company_id': self.branch_data['company'].id,
|
|
},
|
|
])
|
|
|
|
# Make sure that user has only access to the branch company
|
|
self.env.user.company_id = self.branch_data['company'].id
|
|
self.env.user.company_ids = self.branch_data['company'].ids
|
|
bank_statement_line.with_company(self.branch_data['company'])._try_auto_reconcile_statement_lines()
|
|
# Reset allowed companies for the test
|
|
self.env.user.company_ids = (self.branch_data['company'] | self.company_data['company']).ids
|
|
self.assertEqual(
|
|
bank_statement_line.line_ids.reconciled_lines_ids,
|
|
payment.move_id.line_ids.filtered(lambda line: line.account_id == self.outbound_payment_method_line.payment_account_id),
|
|
)
|
|
# Assert that the payment has been automatically validated.
|
|
self.assertEqual(payment.state, 'paid')
|
|
|
|
def test_matching_end_to_end_uuid_access_to_main_and_branch_company_for_statement_line(self):
|
|
bill = self.env['account.move'].create({
|
|
'partner_id': self.partner_a.id,
|
|
'move_type': 'in_invoice',
|
|
'invoice_date': '2025-01-01',
|
|
'invoice_line_ids': [Command.create({
|
|
'quantity': 1,
|
|
'price_unit': 100,
|
|
})],
|
|
'company_id': self.company_data['company'].id,
|
|
})
|
|
bill.action_post()
|
|
|
|
# The payment_method_line_id should have a payment_account_id value to generate the correct journal entries.
|
|
sepa_ct_method_line = self.bank_journal.outbound_payment_method_line_ids.filtered(lambda l: l.code == 'sepa_ct')
|
|
sepa_ct_method_line.payment_account_id = self.outbound_payment_method_line.payment_account_id
|
|
payment = self.env['account.payment.register'].with_context(
|
|
active_ids=bill.ids,
|
|
active_model='account.move',
|
|
).create({
|
|
'amount': 100,
|
|
'journal_id': self.bank_journal.id,
|
|
'payment_date': '2010-01-01',
|
|
'payment_method_line_id': sepa_ct_method_line.id,
|
|
'company_id': self.company_data['company'].id,
|
|
})._create_payments()
|
|
|
|
bank_statement_line = self.env['account.bank.statement.line'].create([
|
|
{
|
|
'journal_id': self.bank_journal.id,
|
|
'date': '2020-01-01',
|
|
'payment_ref': 'PAYMENT',
|
|
'amount': -100,
|
|
'sequence': 1,
|
|
'end_to_end_uuid': payment.end_to_end_uuid,
|
|
'company_id': self.branch_data['company'].id,
|
|
},
|
|
])
|
|
|
|
bank_statement_line.with_company(self.branch_data['company'])._try_auto_reconcile_statement_lines()
|
|
self.assertEqual(
|
|
bank_statement_line.line_ids.reconciled_lines_ids,
|
|
payment.move_id.line_ids.filtered(lambda line: line.account_id == self.outbound_payment_method_line.payment_account_id),
|
|
)
|
|
# Assert that the payment has been automatically validated.
|
|
self.assertEqual(payment.state, 'paid')
|
|
|
|
def test_matching_end_to_end_uuid_single_payment_only_access_to_branch_company_for_statement_line(self):
|
|
# Check if accounting is installed as this test doesn't make sense without accounting installed
|
|
if self.env.ref('base.module_accountant').state != 'installed':
|
|
self.skipTest("`accountant` module not installed")
|
|
|
|
payment = self.env['account.payment'].create({
|
|
'amount': 100,
|
|
'journal_id': self.bank_journal.id,
|
|
'date': '2010-01-01',
|
|
'partner_id': self.partner_a.id,
|
|
'payment_method_line_id': self.bank_journal.outbound_payment_method_line_ids.filtered(lambda l: l.code == 'sepa_ct').id,
|
|
'payment_type': 'outbound',
|
|
'company_id': self.company_data['company'].id,
|
|
})
|
|
payment.action_post()
|
|
|
|
bank_statement_line = self.env['account.bank.statement.line'].create([
|
|
{
|
|
'journal_id': self.bank_journal.id,
|
|
'date': '2020-01-01',
|
|
'payment_ref': 'PAYMENT',
|
|
'amount': -100,
|
|
'sequence': 1,
|
|
'end_to_end_uuid': payment.end_to_end_uuid,
|
|
'company_id': self.branch_data['company'].id,
|
|
},
|
|
])
|
|
# Make sure that user has only access to the branch company
|
|
self.env.user.company_id = self.branch_data['company'].id
|
|
self.env.user.company_ids = self.branch_data['company'].ids
|
|
bank_statement_line._try_auto_reconcile_statement_lines()
|
|
# Reset allowed companies for the test
|
|
self.env.user.company_ids = (self.branch_data['company'] | self.company_data['company']).ids
|
|
# Assert that the payment has been automatically validated.
|
|
self.assertEqual(payment.state, 'paid')
|
|
|
|
self.assertRecordValues(
|
|
bank_statement_line.line_ids,
|
|
[
|
|
{
|
|
'balance': -100.0,
|
|
'account_id': bank_statement_line.journal_id.default_account_id.id,
|
|
'name': bank_statement_line.payment_ref,
|
|
},
|
|
{
|
|
'balance': 100.0,
|
|
'account_id': self.partner_a.property_account_payable_id.id,
|
|
'name': payment.name,
|
|
}
|
|
]
|
|
)
|
|
|
|
def test_matching_end_to_end_uuid_single_payment_access_to_main_and_branch_company_for_statement_line(self):
|
|
# Check if accounting is installed as this test doesn't make sense without accounting installed
|
|
if self.env.ref('base.module_accountant').state != 'installed':
|
|
self.skipTest("`accountant` module not installed")
|
|
|
|
payment = self.env['account.payment'].create({
|
|
'amount': 100,
|
|
'journal_id': self.bank_journal.id,
|
|
'date': '2010-01-01',
|
|
'partner_id': self.partner_a.id,
|
|
'payment_method_line_id': self.bank_journal.outbound_payment_method_line_ids.filtered(lambda l: l.code == 'sepa_ct').id,
|
|
'payment_type': 'outbound',
|
|
'company_id': self.company_data['company'].id,
|
|
})
|
|
payment.action_post()
|
|
|
|
bank_statement_line = self.env['account.bank.statement.line'].create([
|
|
{
|
|
'journal_id': self.bank_journal.id,
|
|
'date': '2020-01-01',
|
|
'payment_ref': 'PAYMENT',
|
|
'amount': -100,
|
|
'sequence': 1,
|
|
'end_to_end_uuid': payment.end_to_end_uuid,
|
|
'company_id': self.branch_data['company'].id,
|
|
},
|
|
])
|
|
bank_statement_line._try_auto_reconcile_statement_lines()
|
|
# Assert that the payment has been automatically validated.
|
|
self.assertEqual(payment.state, 'paid')
|
|
|
|
self.assertRecordValues(
|
|
bank_statement_line.line_ids,
|
|
[
|
|
{
|
|
'balance': -100.0,
|
|
'account_id': bank_statement_line.journal_id.default_account_id.id,
|
|
'name': bank_statement_line.payment_ref,
|
|
},
|
|
{
|
|
'balance': 100.0,
|
|
'account_id': self.partner_a.property_account_payable_id.id,
|
|
'name': payment.name,
|
|
}
|
|
]
|
|
)
|