Files
enterprise-addons/account_accountant/tests/test_account_bank_statement.py
T

3709 lines
217 KiB
Python

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"""<?xml version='1.0' encoding='UTF-8'?>
<Invoice xmlns:cac="urn:oasis:names:specification:ubl:schema:xsd:CommonAggregateComponents-2" xmlns:cbc="urn:oasis:names:specification:ubl:schema:xsd:CommonBasicComponents-2" xmlns="urn:oasis:names:specification:ubl:schema:xsd:Invoice-2">
<cbc:CustomizationID>urn:cen.eu:en16931:2017#compliant#urn:fdc:peppol.eu:2017:poacc:billing:3.0</cbc:CustomizationID>
<cbc:ProfileID>urn:fdc:peppol.eu:2017:poacc:billing:01:1.0</cbc:ProfileID>
<cbc:ID>INV/2023/00009</cbc:ID>
<cbc:IssueDate>2023-06-20</cbc:IssueDate>
<cbc:DueDate>2023-06-20</cbc:DueDate>
<cbc:InvoiceTypeCode>380</cbc:InvoiceTypeCode>
<cbc:Note>Terms and condition</cbc:Note>
<cbc:DocumentCurrencyCode>USD</cbc:DocumentCurrencyCode>
<cac:OrderReference>
<cbc:ID>DOEO</cbc:ID>
</cac:OrderReference>
<cac:AdditionalDocumentReference>
<cbc:ID>INV_2023_00009.pdf</cbc:ID>
</cac:AdditionalDocumentReference>
<cac:AccountingSupplierParty>
<cac:Party>
<cbc:EndpointID schemeID="9925">BE0246697724</cbc:EndpointID>
<cac:PartyName>
<cbc:Name>BE Company CoA</cbc:Name>
</cac:PartyName>
<cac:PostalAddress>
<cbc:StreetName>1021 Sint-Bernardsesteenweg</cbc:StreetName>
<cbc:CityName>Antwerpen</cbc:CityName>
<cbc:PostalZone>2660</cbc:PostalZone>
<cac:Country>
<cbc:IdentificationCode>BE</cbc:IdentificationCode>
</cac:Country>
</cac:PostalAddress>
<cac:PartyTaxScheme>
<cbc:CompanyID>BE0246697724</cbc:CompanyID>
<cac:TaxScheme>
<cbc:ID>VAT</cbc:ID>
</cac:TaxScheme>
</cac:PartyTaxScheme>
<cac:PartyLegalEntity>
<cbc:RegistrationName>BE Company CoA</cbc:RegistrationName>
<cbc:CompanyID>BE0246697724</cbc:CompanyID>
</cac:PartyLegalEntity>
<cac:Contact>
<cbc:Name>BE Company CoA</cbc:Name>
<cbc:Telephone>+32 470 12 34 56</cbc:Telephone>
<cbc:ElectronicMail>info@company.beexample.com</cbc:ElectronicMail>
</cac:Contact>
</cac:Party>
</cac:AccountingSupplierParty>
<cac:AccountingCustomerParty>
<cac:Party>
<cbc:EndpointID schemeID="9925">BE0665978937</cbc:EndpointID>
<cac:PartyName>
<cbc:Name>Proximus</cbc:Name>
</cac:PartyName>
<cac:PostalAddress>
<cac:Country>
<cbc:IdentificationCode>BE</cbc:IdentificationCode>
</cac:Country>
</cac:PostalAddress>
<cac:PartyTaxScheme>
<cbc:CompanyID>BE0665978937</cbc:CompanyID>
<cac:TaxScheme>
<cbc:ID>VAT</cbc:ID>
</cac:TaxScheme>
</cac:PartyTaxScheme>
<cac:PartyLegalEntity>
<cbc:RegistrationName>Proximus</cbc:RegistrationName>
<cbc:CompanyID>BE0665978937</cbc:CompanyID>
</cac:PartyLegalEntity>
<cac:Contact>
<cbc:Name>Proximus</cbc:Name>
</cac:Contact>
</cac:Party>
</cac:AccountingCustomerParty>
<cac:Delivery>
<cac:DeliveryLocation>
<cac:Address>
<cac:Country>
<cbc:IdentificationCode>BE</cbc:IdentificationCode>
</cac:Country>
</cac:Address>
</cac:DeliveryLocation>
</cac:Delivery>
<cac:PaymentMeans>
<cbc:PaymentMeansCode name="credit transfer">30</cbc:PaymentMeansCode>
<cbc:PaymentID>+++000/0000/22329+++</cbc:PaymentID>
<cac:PayeeFinancialAccount>
<cbc:ID>BE83957821438115</cbc:ID>
</cac:PayeeFinancialAccount>
</cac:PaymentMeans>
<cac:LegalMonetaryTotal>
<cbc:LineExtensionAmount currencyID="USD">100.00</cbc:LineExtensionAmount>
<cbc:TaxExclusiveAmount currencyID="USD">100.00</cbc:TaxExclusiveAmount>
<cbc:TaxInclusiveAmount currencyID="USD">100.00</cbc:TaxInclusiveAmount>
<cbc:PrepaidAmount currencyID="USD">0.00</cbc:PrepaidAmount>
<cbc:PayableAmount currencyID="USD">100.00</cbc:PayableAmount>
</cac:LegalMonetaryTotal>
<cac:InvoiceLine>
<cbc:ID>984</cbc:ID>
<cbc:InvoicedQuantity unitCode="C62">1.0</cbc:InvoicedQuantity>
<cbc:LineExtensionAmount currencyID="USD">100.00</cbc:LineExtensionAmount>
<cac:Item>
<cbc:Description>[FURN_6666] Acoustic Bloc Screens</cbc:Description>
<cbc:Name>Acoustic Bloc Screens</cbc:Name>
<cac:SellersItemIdentification>
<cbc:ID>FURN_6666</cbc:ID>
</cac:SellersItemIdentification>
<cac:ClassifiedTaxCategory>
<cbc:ID>S</cbc:ID>
<cbc:Percent>21.0</cbc:Percent>
<cac:TaxScheme>
<cbc:ID>VAT</cbc:ID>
</cac:TaxScheme>
</cac:ClassifiedTaxCategory>
</cac:Item>
<cac:Price>
<cbc:PriceAmount currencyID="USD">100.00</cbc:PriceAmount>
</cac:Price>
</cac:InvoiceLine>
</Invoice>
"""
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"""<?xml version='1.0' encoding='UTF-8'?>
<Invoice xmlns:cac="urn:oasis:names:specification:ubl:schema:xsd:CommonAggregateComponents-2" xmlns:cbc="urn:oasis:names:specification:ubl:schema:xsd:CommonBasicComponents-2" xmlns="urn:oasis:names:specification:ubl:schema:xsd:Invoice-2">
<cbc:CustomizationID>urn:cen.eu:en16931:2017#compliant#urn:fdc:peppol.eu:2017:poacc:billing:3.0</cbc:CustomizationID>
<cbc:ProfileID>urn:fdc:peppol.eu:2017:poacc:billing:01:1.0</cbc:ProfileID>
<cbc:ID>INV/2023/00009</cbc:ID>
<cbc:IssueDate>2023-06-20</cbc:IssueDate>
<cbc:DueDate>2023-06-20</cbc:DueDate>
<cbc:InvoiceTypeCode>380</cbc:InvoiceTypeCode>
<cbc:Note>Terms and condition</cbc:Note>
<cbc:DocumentCurrencyCode>INR</cbc:DocumentCurrencyCode>
<cac:OrderReference>
<cbc:ID>DOEO</cbc:ID>
</cac:OrderReference>
<cac:AdditionalDocumentReference>
<cbc:ID>INV_2023_00009.pdf</cbc:ID>
</cac:AdditionalDocumentReference>
<cac:AccountingSupplierParty>
<cac:Party>
<cbc:EndpointID schemeID="9925">BE0246697724</cbc:EndpointID>
<cac:PartyName>
<cbc:Name>BE Company CoA</cbc:Name>
</cac:PartyName>
<cac:PostalAddress>
<cbc:StreetName>1021 Sint-Bernardsesteenweg</cbc:StreetName>
<cbc:CityName>Antwerpen</cbc:CityName>
<cbc:PostalZone>2660</cbc:PostalZone>
<cac:Country>
<cbc:IdentificationCode>BE</cbc:IdentificationCode>
</cac:Country>
</cac:PostalAddress>
<cac:PartyTaxScheme>
<cbc:CompanyID>BE0246697724</cbc:CompanyID>
<cac:TaxScheme>
<cbc:ID>VAT</cbc:ID>
</cac:TaxScheme>
</cac:PartyTaxScheme>
<cac:PartyLegalEntity>
<cbc:RegistrationName>BE Company CoA</cbc:RegistrationName>
<cbc:CompanyID>BE0246697724</cbc:CompanyID>
</cac:PartyLegalEntity>
<cac:Contact>
<cbc:Name>BE Company CoA</cbc:Name>
<cbc:Telephone>+32 470 12 34 56</cbc:Telephone>
<cbc:ElectronicMail>info@company.beexample.com</cbc:ElectronicMail>
</cac:Contact>
</cac:Party>
</cac:AccountingSupplierParty>
<cac:AccountingCustomerParty>
<cac:Party>
<cbc:EndpointID schemeID="9925">BE0665978937</cbc:EndpointID>
<cac:PartyName>
<cbc:Name>Proximus</cbc:Name>
</cac:PartyName>
<cac:PostalAddress>
<cac:Country>
<cbc:IdentificationCode>BE</cbc:IdentificationCode>
</cac:Country>
</cac:PostalAddress>
<cac:PartyTaxScheme>
<cbc:CompanyID>BE0665978937</cbc:CompanyID>
<cac:TaxScheme>
<cbc:ID>VAT</cbc:ID>
</cac:TaxScheme>
</cac:PartyTaxScheme>
<cac:PartyLegalEntity>
<cbc:RegistrationName>Proximus</cbc:RegistrationName>
<cbc:CompanyID>BE0665978937</cbc:CompanyID>
</cac:PartyLegalEntity>
<cac:Contact>
<cbc:Name>Proximus</cbc:Name>
</cac:Contact>
</cac:Party>
</cac:AccountingCustomerParty>
<cac:Delivery>
<cac:DeliveryLocation>
<cac:Address>
<cac:Country>
<cbc:IdentificationCode>BE</cbc:IdentificationCode>
</cac:Country>
</cac:Address>
</cac:DeliveryLocation>
</cac:Delivery>
<cac:PaymentMeans>
<cbc:PaymentMeansCode name="credit transfer">30</cbc:PaymentMeansCode>
<cbc:PaymentID>+++000/0000/22329+++</cbc:PaymentID>
<cac:PayeeFinancialAccount>
<cbc:ID>BE83957821438115</cbc:ID>
</cac:PayeeFinancialAccount>
</cac:PaymentMeans>
<cac:LegalMonetaryTotal>
<cbc:LineExtensionAmount currencyID="INR">100.00</cbc:LineExtensionAmount>
<cbc:TaxExclusiveAmount currencyID="INR">100.00</cbc:TaxExclusiveAmount>
<cbc:TaxInclusiveAmount currencyID="INR">100.00</cbc:TaxInclusiveAmount>
<cbc:PrepaidAmount currencyID="INR">0.00</cbc:PrepaidAmount>
<cbc:PayableAmount currencyID="INR">100.00</cbc:PayableAmount>
</cac:LegalMonetaryTotal>
<cac:InvoiceLine>
<cbc:ID>984</cbc:ID>
<cbc:InvoicedQuantity unitCode="C62">1.0</cbc:InvoicedQuantity>
<cbc:LineExtensionAmount currencyID="INR">100.00</cbc:LineExtensionAmount>
<cac:Item>
<cbc:Description>[FURN_6666] Acoustic Bloc Screens</cbc:Description>
<cbc:Name>Acoustic Bloc Screens</cbc:Name>
<cac:SellersItemIdentification>
<cbc:ID>FURN_6666</cbc:ID>
</cac:SellersItemIdentification>
<cac:ClassifiedTaxCategory>
<cbc:ID>S</cbc:ID>
<cbc:Percent>21.0</cbc:Percent>
<cac:TaxScheme>
<cbc:ID>VAT</cbc:ID>
</cac:TaxScheme>
</cac:ClassifiedTaxCategory>
</cac:Item>
<cac:Price>
<cbc:PriceAmount currencyID="INR">100.00</cbc:PriceAmount>
</cac:Price>
</cac:InvoiceLine>
</Invoice>
"""
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},
])