1
0
forked from Mapan/odoo17e
odoo17e-kedaikipas58/addons/account_accountant/tests/test_bank_rec_widget.py
2024-12-10 09:04:09 +07:00

3024 lines
155 KiB
Python

# -*- coding: utf-8 -*-
from odoo.addons.account_accountant.tests.test_bank_rec_widget_common import TestBankRecWidgetCommon
from odoo.tests import tagged
from odoo.tools import html2plaintext
from odoo import fields, Command
from freezegun import freeze_time
from unittest.mock import patch
import re
@tagged('post_install', '-at_install')
class TestBankRecWidget(TestBankRecWidgetCommon):
@classmethod
def setUpClass(cls, chart_template_ref=None):
super().setUpClass(chart_template_ref=chart_template_ref)
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.account_revenue1 = cls.company_data['default_account_revenue']
cls.account_revenue2 = cls.copy_account(cls.account_revenue1)
def assert_form_extra_text_value(self, wizard, regex):
line = wizard.line_ids.filtered(lambda x: x.index == wizard.form_index)
value = line.suggestion_html
if regex:
cleaned_value = html2plaintext(value).replace('\n', '')
if not re.match(regex, cleaned_value):
self.fail(f"The following 'form_extra_text':\n\n'{cleaned_value}'\n\n...doesn't match the provided regex:\n\n'{regex}'")
else:
self.assertFalse(value)
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,
})
self.assertEqual(st_line._retrieve_partner(), 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,
})
self.assertEqual(st_line._retrieve_partner(), self.env['res.partner'])
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")
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
self.assertEqual(st_line._retrieve_partner(), 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")
self.assertEqual(st_line._retrieve_partner(), partner_b)
self.env['res.partner'].create({'name': "turlututu"})
self.assertFalse(st_line._retrieve_partner())
def test_retrieve_partner_suggested_account_from_rank(self):
""" Ensure a retrieved partner is proposing his receivable/payable according his customer/supplier rank. """
partner = self.env['res.partner'].create({'name': "turlututu"})
rec_account_id = partner.property_account_receivable_id.id
pay_account_id = partner.property_account_payable_id.id
st_line = self._create_st_line(1000.0, partner_id=None, partner_name="turlututu")
liq_account_id = st_line.journal_id.default_account_id.id
wizard = self.env['bank.rec.widget'].with_context(default_st_line_id=st_line.id).new({})
self.assertRecordValues(wizard.line_ids, [
# pylint: disable=C0326
{'flag': 'liquidity', 'account_id': liq_account_id, 'balance': 1000.0},
{'flag': 'auto_balance', 'account_id': rec_account_id, 'balance': -1000.0},
])
partner._increase_rank('supplier_rank', 1)
wizard = self.env['bank.rec.widget'].with_context(default_st_line_id=st_line.id).new({})
self.assertRecordValues(wizard.line_ids, [
# pylint: disable=C0326
{'flag': 'liquidity', 'account_id': liq_account_id, 'balance': 1000.0},
{'flag': 'auto_balance', 'account_id': pay_account_id, 'balance': -1000.0},
])
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",
)
inv_line = self._create_invoice_line(
'out_invoice',
partner_id=partner.id,
invoice_line_ids=[{'price_unit': 100.0, 'tax_ids': []}],
)
wizard = self.env['bank.rec.widget'].with_context(default_st_line_id=st_line.id).new({})
wizard._action_add_new_amls(inv_line)
wizard._action_validate()
# 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",
)
inv_line = self._create_invoice_line(
'out_invoice',
partner_id=partner.id,
invoice_line_ids=[{'price_unit': 100.0, 'tax_ids': []}],
)
wizard = self.env['bank.rec.widget'].with_context(default_st_line_id=st_line.id).new({})
wizard._action_add_new_amls(inv_line)
wizard._action_validate()
# Should not trigger the error.
self.env['res.partner.bank'].flush_model()
def test_validation_base_case(self):
st_line = self._create_st_line(
1000.0,
date='2017-01-01',
)
wizard = self.env['bank.rec.widget'].with_context(default_st_line_id=st_line.id).new({})
line = wizard.line_ids.filtered(lambda x: x.flag == 'auto_balance')
wizard._js_action_mount_line_in_edit(line.index)
line.account_id = self.account_revenue1
wizard._line_value_changed_account_id(line)
self.assertRecordValues(wizard.line_ids, [
# pylint: disable=C0326
{'flag': 'liquidity', 'amount_currency': 1000.0, 'currency_id': self.company_data['currency'].id, 'balance': 1000.0},
{'flag': 'manual', 'amount_currency': -1000.0, 'currency_id': self.company_data['currency'].id, 'balance': -1000.0},
])
self.assertRecordValues(wizard, [{'state': 'valid'}])
# The amount is the same, no message under the 'amount' field.
self.assert_form_extra_text_value(wizard, False)
wizard._action_validate()
self.assertRecordValues(st_line.line_ids, [
# pylint: disable=C0326
{'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_revenue1.id, 'amount_currency': -1000.0, 'currency_id': self.company_data['currency'].id, 'balance': -1000.0, 'reconciled': False},
])
self.assertRecordValues(wizard.line_ids, [
# pylint: disable=C0326
{'flag': 'liquidity', 'account_id': st_line.journal_id.default_account_id.id, 'amount_currency': 1000.0, 'currency_id': self.company_data['currency'].id, 'balance': 1000.0},
{'flag': 'aml', 'account_id': self.account_revenue1.id, 'amount_currency': -1000.0, 'currency_id': self.company_data['currency'].id, 'balance': -1000.0},
])
def test_validation_exchange_difference(self):
# 240.0 curr2 == 120.0 comp_curr
st_line = self._create_st_line(
120.0,
date='2017-01-01',
foreign_currency_id=self.currency_data['currency'].id,
amount_currency=240.0,
)
# 240.0 curr2 == 80.0 comp_curr
inv_line = self._create_invoice_line(
'out_invoice',
currency_id=self.currency_data['currency'].id,
invoice_date='2016-01-01',
invoice_line_ids=[{'price_unit': 240.0}],
)
wizard = self.env['bank.rec.widget'].with_context(default_st_line_id=st_line.id).new({})
wizard._action_add_new_amls(inv_line)
self.assertRecordValues(wizard.line_ids, [
# pylint: disable=C0326
{'flag': 'liquidity', 'amount_currency': 120.0, 'currency_id': self.company_data['currency'].id, 'balance': 120.0},
{'flag': 'new_aml', 'amount_currency': -240.0, 'currency_id': self.currency_data['currency'].id, 'balance': -80.0},
{'flag': 'exchange_diff', 'amount_currency': 0.0, 'currency_id': self.currency_data['currency'].id, 'balance': -40.0},
])
self.assertRecordValues(wizard, [{'state': 'valid'}])
wizard._action_validate()
# Check the statement line.
self.assertRecordValues(st_line.line_ids.sorted(), [
# pylint: disable=C0326
{'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': inv_line.account_id.id, 'amount_currency': -240.0, 'currency_id': self.currency_data['currency'].id, 'balance': -120.0, 'reconciled': True},
])
# Check the partials.
partials = st_line.line_ids.matched_debit_ids
exchange_move = partials.exchange_move_id
_liquidity_line, _suspense_line, other_line = st_line._seek_for_lines()
self.assertRecordValues(partials.sorted(), [
# pylint: disable=C0326
{
'amount': 40.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,
},
{
'amount': 80.0,
'debit_amount_currency': 240.0,
'credit_amount_currency': 240.0,
'debit_move_id': inv_line.id,
'credit_move_id': other_line.id,
'exchange_move_id': exchange_move.id,
},
])
# Check the exchange diff journal entry.
self.assertRecordValues(exchange_move.line_ids.sorted(), [
# pylint: disable=C0326
{'account_id': inv_line.account_id.id, 'amount_currency': 0.0, 'currency_id': self.currency_data['currency'].id, 'balance': 40.0, 'reconciled': True},
{'account_id': self.env.company.income_currency_exchange_account_id.id, 'amount_currency': 0.0, 'currency_id': self.currency_data['currency'].id, 'balance': -40.0, 'reconciled': False},
])
def test_validation_new_aml_same_foreign_currency(self):
income_exchange_account = self.env.company.income_currency_exchange_account_id
# 6000.0 curr2 == 1200.0 comp_curr (bank rate 5:1 instead of the odoo rate 4:1)
st_line = self._create_st_line(
1200.0,
date='2017-01-01',
foreign_currency_id=self.currency_data_2['currency'].id,
amount_currency=6000.0,
)
# 6000.0 curr2 == 1000.0 comp_curr (rate 6:1)
inv_line = self._create_invoice_line(
'out_invoice',
currency_id=self.currency_data_2['currency'].id,
invoice_date='2016-01-01',
invoice_line_ids=[{'price_unit': 6000.0}],
)
wizard = self.env['bank.rec.widget'].with_context(default_st_line_id=st_line.id).new({})
wizard._action_add_new_amls(inv_line)
self.assertRecordValues(wizard.line_ids, [
# pylint: disable=C0326
{'flag': 'liquidity', 'amount_currency': 1200.0, 'currency_id': self.company_data['currency'].id, 'balance': 1200.0},
{'flag': 'new_aml', 'amount_currency': -6000.0, 'currency_id': self.currency_data_2['currency'].id, 'balance': -1000.0},
{'flag': 'exchange_diff', 'amount_currency': 0.0, 'currency_id': self.currency_data_2['currency'].id, 'balance': -200.0},
])
self.assertRecordValues(wizard, [{'state': 'valid'}])
# The amount is the same, no message under the 'amount' field.
self.assert_form_extra_text_value(wizard, False)
wizard._action_validate()
self.assertRecordValues(st_line.line_ids, [
# pylint: disable=C0326
{'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': -6000.0, 'currency_id': self.currency_data_2['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, [
# pylint: disable=C0326
{'account_id': inv_line.account_id.id, 'amount_currency': 0.0, 'currency_id': self.currency_data_2['currency'].id, 'balance': 200.0, 'reconciled': True, 'date': fields.Date.from_string('2017-01-31')},
{'account_id': income_exchange_account.id, 'amount_currency': 0.0, 'currency_id': self.currency_data_2['currency'].id, 'balance': -200.0, 'reconciled': False, 'date': fields.Date.from_string('2017-01-31')},
])
# Reset the wizard.
wizard._js_action_reset()
self.assertRecordValues(wizard.line_ids, [
# pylint: disable=C0326
{'flag': 'liquidity', 'amount_currency': 1200.0, 'currency_id': self.company_data['currency'].id, 'balance': 1200.0},
{'flag': 'auto_balance', 'amount_currency': -6000.0, 'currency_id': self.currency_data_2['currency'].id, 'balance': -1200.0},
])
# Create the same invoice with a higher amount to check the partial flow.
# 9000.0 curr2 == 1500.0 comp_curr (rate 6:1)
inv_line = self._create_invoice_line(
'out_invoice',
currency_id=self.currency_data_2['currency'].id,
invoice_date='2016-01-01',
invoice_line_ids=[{'price_unit': 9000.0}],
)
wizard._action_add_new_amls(inv_line)
self.assertRecordValues(wizard.line_ids, [
# pylint: disable=C0326
{'flag': 'liquidity', 'amount_currency': 1200.0, 'currency_id': self.company_data['currency'].id, 'balance': 1200.0},
{'flag': 'new_aml', 'amount_currency': -6000.0, 'currency_id': self.currency_data_2['currency'].id, 'balance': -1000.0},
{'flag': 'exchange_diff', 'amount_currency': 0.0, 'currency_id': self.currency_data_2['currency'].id, 'balance': -200.0},
])
# Check the message under the 'amount' field.
line = wizard.line_ids.filtered(lambda x: x.flag == 'new_aml')
wizard._js_action_mount_line_in_edit(line.index)
self.assert_form_extra_text_value(
wizard,
r".+open amount of 9,000.000.+ reduced by 6,000.000.+ set the invoice as fully paid .",
)
self.assertRecordValues(line, [{
'suggestion_amount_currency': -9000.0,
'suggestion_balance': -1500.0,
}])
# Switch to a full reconciliation.
wizard._js_action_apply_line_suggestion(line.index)
self.assertRecordValues(wizard.line_ids, [
# pylint: disable=C0326
{'flag': 'liquidity', 'amount_currency': 1200.0, 'currency_id': self.company_data['currency'].id, 'balance': 1200.0},
{'flag': 'new_aml', 'amount_currency': -9000.0, 'currency_id': self.currency_data_2['currency'].id, 'balance': -1500.0},
{'flag': 'exchange_diff', 'amount_currency': 0.0, 'currency_id': self.currency_data_2['currency'].id, 'balance': -300.0},
{'flag': 'auto_balance', 'amount_currency': 3000.0, 'currency_id': self.currency_data_2['currency'].id, 'balance': 600.0},
])
# Check the message under the 'amount' field.
line = wizard.line_ids.filtered(lambda x: x.flag == 'new_aml')
wizard._js_action_mount_line_in_edit(line.index)
self.assert_form_extra_text_value(
wizard,
r".+open amount of 9,000.000.+ paid .+ record a partial payment .",
)
self.assertRecordValues(line, [{
'suggestion_amount_currency': -6000.0,
'suggestion_balance': -1000.0,
}])
# Switch back to a partial reconciliation.
wizard._js_action_apply_line_suggestion(line.index)
self.assertRecordValues(wizard, [{'state': 'valid'}])
# Reconcile
wizard._action_validate()
self.assertRecordValues(st_line.line_ids, [
# pylint: disable=C0326
{'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': -6000.0, 'currency_id': self.currency_data_2['currency'].id, 'balance': -1200.0, 'reconciled': True},
])
self.assertRecordValues(st_line, [{'is_reconciled': True}])
self.assertRecordValues(inv_line.move_id, [{
'payment_state': 'partial',
'amount_residual': 3000.0,
}])
self.assertRecordValues(inv_line.matched_credit_ids.exchange_move_id.line_ids, [
# pylint: disable=C0326
{'account_id': inv_line.account_id.id, 'amount_currency': 0.0, 'currency_id': self.currency_data_2['currency'].id, 'balance': 200.0, 'reconciled': True, 'date': fields.Date.from_string('2017-01-31')},
{'account_id': income_exchange_account.id, 'amount_currency': 0.0, 'currency_id': self.currency_data_2['currency'].id, 'balance': -200.0, 'reconciled': False, 'date': fields.Date.from_string('2017-01-31')},
])
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',
)
# 1800.0 comp_curr = 3600.0 foreign_curr in 2017 (rate 1:2)
inv_line = self._create_invoice_line(
'out_invoice',
currency_id=self.currency_data['currency'].id,
invoice_date='2017-01-01',
invoice_line_ids=[{'price_unit': 3600.0}],
)
wizard = self.env['bank.rec.widget'].with_context(default_st_line_id=st_line.id).new({})
wizard._action_add_new_amls(inv_line)
self.assertRecordValues(wizard.line_ids, [
# pylint: disable=C0326
{'flag': 'liquidity', 'amount_currency': 1200.0, 'currency_id': self.company_data['currency'].id, 'balance': 1200.0},
{'flag': 'new_aml', 'amount_currency': -3600.0, 'currency_id': self.currency_data['currency'].id, 'balance': -1800.0},
{'flag': 'exchange_diff', 'amount_currency': 0.0, 'currency_id': self.currency_data['currency'].id, 'balance': 600.0},
])
self.assertRecordValues(wizard, [{'state': 'valid'}])
wizard._action_validate()
self.assertRecordValues(st_line.line_ids, [
# pylint: disable=C0326
{'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.currency_data['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, [
# pylint: disable=C0326
{'account_id': inv_line.account_id.id, 'amount_currency': 0.0, 'currency_id': self.currency_data['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.currency_data['currency'].id, 'balance': 600.0, 'reconciled': False, 'date': fields.Date.from_string('2017-01-31')},
])
# Checks that the wizard still display the 3 initial lines
self.assertRecordValues(wizard.line_ids, [
# pylint: disable=C0326
{'flag': 'liquidity', 'amount_currency': 1200.0, 'currency_id': self.company_data['currency'].id, 'balance': 1200.0},
{'flag': 'aml', 'amount_currency': -3600.0, 'currency_id': self.currency_data['currency'].id, 'balance': -1800.0},
{'flag': 'aml', 'amount_currency': 0.0, 'currency_id': self.currency_data['currency'].id, 'balance': 600.0},
])
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',
)
# 1200.0 comp_curr = 3600.0 foreign_curr in 2016 (rate 1:3)
inv_line = self._create_invoice_line(
'out_invoice',
currency_id=self.currency_data['currency'].id,
invoice_date='2016-01-01',
invoice_line_ids=[{'price_unit': 3600.0}],
)
wizard = self.env['bank.rec.widget'].with_context(default_st_line_id=st_line.id).new({})
wizard._action_add_new_amls(inv_line)
self.assertRecordValues(wizard.line_ids, [
# pylint: disable=C0326
{'flag': 'liquidity', 'amount_currency': 1800.0, 'currency_id': self.company_data['currency'].id, 'balance': 1800.0},
{'flag': 'new_aml', 'amount_currency': -3600.0, 'currency_id': self.currency_data['currency'].id, 'balance': -1200.0},
{'flag': 'exchange_diff', 'amount_currency': 0.0, 'currency_id': self.currency_data['currency'].id, 'balance': -600.0},
])
self.assertRecordValues(wizard, [{'state': 'valid'}])
wizard._action_validate()
self.assertRecordValues(st_line.line_ids, [
# pylint: disable=C0326
{'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.currency_data['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, [
# pylint: disable=C0326
{'account_id': inv_line.account_id.id, 'amount_currency': 0.0, 'currency_id': self.currency_data['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.currency_data['currency'].id, 'balance': -600.0, 'reconciled': False, 'date': fields.Date.from_string('2017-01-31')},
])
# Checks that the wizard still display the 3 initial lines
self.assertRecordValues(wizard.line_ids, [
# pylint: disable=C0326
{'flag': 'liquidity', 'amount_currency': 1800.0, 'currency_id': self.company_data['currency'].id, 'balance': 1800.0},
{'flag': 'aml', 'amount_currency': -3600.0, 'currency_id': self.currency_data['currency'].id, 'balance': -1200.0},
{'flag': 'aml', 'amount_currency': 0.0, 'currency_id': self.currency_data['currency'].id, 'balance': -600.0},
])
def test_validation_income_exchange_difference_with_rounding(self):
# 1000.0 comp_curr = 3000.0 foreign_curr in 2016 (rate 1:3)
# However divided in 3 invoices + rounding we have 333.33333 ≃ 333.33 comp_curr = 1000.0 foreign_curr
# this implies that the full amount has been used in foreign_curr but there is 0.01 in comp_curr
st_line = self._create_st_line(
1000.0,
date='2016-01-01',
)
# 1500 comp_curr = 3000.0 foreign_curr in 2017 (rate 1:2)
inv_line_1 = self._create_invoice_line(
'out_invoice',
currency_id=self.currency_data['currency'].id,
invoice_date='2017-01-01',
invoice_line_ids=[{'price_unit': 1000.0}],
)
inv_line_2 = self._create_invoice_line(
'out_invoice',
currency_id=self.currency_data['currency'].id,
invoice_date='2017-01-01',
invoice_line_ids=[{'price_unit': 1000.0}],
)
inv_line_3 = self._create_invoice_line(
'out_invoice',
currency_id=self.currency_data['currency'].id,
invoice_date='2017-01-01',
invoice_line_ids=[{'price_unit': 1000.0}],
)
wizard = self.env['bank.rec.widget'].with_context(default_st_line_id=st_line.id).new({})
wizard._action_add_new_amls(inv_line_1 + inv_line_2 + inv_line_3)
self.assertRecordValues(wizard.line_ids, [
# pylint: disable=C0326
{'flag': 'liquidity', 'amount_currency': 1000.0, 'currency_id': self.company_data['currency'].id, 'balance': 1000.0},
{'flag': 'new_aml', 'amount_currency': -1000.0, 'currency_id': self.currency_data['currency'].id, 'balance': -500.0},
{'flag': 'exchange_diff', 'amount_currency': 0.0, 'currency_id': self.currency_data['currency'].id, 'balance': 166.67},
{'flag': 'new_aml', 'amount_currency': -1000.0, 'currency_id': self.currency_data['currency'].id, 'balance': -500.0},
{'flag': 'exchange_diff', 'amount_currency': 0.0, 'currency_id': self.currency_data['currency'].id, 'balance': 166.67},
{'flag': 'new_aml', 'amount_currency': -1000.0, 'currency_id': self.currency_data['currency'].id, 'balance': -500.0},
{'flag': 'exchange_diff', 'amount_currency': 0.0, 'currency_id': self.currency_data['currency'].id, 'balance': 166.67},
{'flag': 'auto_balance', 'amount_currency': -0.01, 'currency_id': self.company_data['currency'].id, 'balance': -0.01},
])
# Remove 0.01 cent in the balance of first exchange line
first_exchange_line = wizard.line_ids.filtered(lambda x: x.flag == 'exchange_diff')[:1]
wizard._js_action_mount_line_in_edit(first_exchange_line.index)
first_exchange_line.balance = 166.66
wizard._line_value_changed_balance(first_exchange_line)
# Every line balance so no 'auto_balance' is generated
self.assertRecordValues(wizard.line_ids, [
# pylint: disable=C0326
{'flag': 'liquidity', 'amount_currency': 1000.0, 'currency_id': self.company_data['currency'].id, 'balance': 1000.0},
{'flag': 'new_aml', 'amount_currency': -1000.0, 'currency_id': self.currency_data['currency'].id, 'balance': -500.0},
{'flag': 'exchange_diff', 'amount_currency': 0.0, 'currency_id': self.currency_data['currency'].id, 'balance': 166.66},
{'flag': 'new_aml', 'amount_currency': -1000.0, 'currency_id': self.currency_data['currency'].id, 'balance': -500.0},
{'flag': 'exchange_diff', 'amount_currency': 0.0, 'currency_id': self.currency_data['currency'].id, 'balance': 166.67},
{'flag': 'new_aml', 'amount_currency': -1000.0, 'currency_id': self.currency_data['currency'].id, 'balance': -500.0},
{'flag': 'exchange_diff', 'amount_currency': 0.0, 'currency_id': self.currency_data['currency'].id, 'balance': 166.67},
])
self.assertRecordValues(wizard, [{'state': 'valid'}])
wizard._action_validate()
# Check that the first line with exchange has -0.01 compared to others
self.assertRecordValues(st_line.line_ids, [
# pylint: disable=C0326
{'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': inv_line_1.account_id.id, 'amount_currency': -1000.0, 'currency_id': self.currency_data['currency'].id, 'balance': -333.34, 'reconciled': True},
{'account_id': inv_line_2.account_id.id, 'amount_currency': -1000.0, 'currency_id': self.currency_data['currency'].id, 'balance': -333.33, 'reconciled': True},
{'account_id': inv_line_3.account_id.id, 'amount_currency': -1000.0, 'currency_id': self.currency_data['currency'].id, 'balance': -333.33, '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'}])
def test_validation_exchange_diff_multiple(self):
income_exchange_account = self.env.company.income_currency_exchange_account_id
foreign_currency = self.setup_multi_currency_data(default_values={
'name': 'Diamond',
'symbol': '💎',
'currency_unit_label': 'Diamond',
'currency_subunit_label': 'Carbon',
}, rate2016=6.0, rate2017=5.0)['currency']
# 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,
)
# 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}],
)
wizard = self.env['bank.rec.widget'].with_context(default_st_line_id=st_line.id).new({})
wizard._action_add_new_amls(inv_line_1 + inv_line_2 + inv_line_3)
self.assertRecordValues(wizard.line_ids, [
# pylint: disable=C0326
{'flag': 'liquidity', 'amount_currency': 1200.0, 'currency_id': self.company_data['currency'].id, 'balance': 1200.0},
{'flag': 'new_aml', 'amount_currency': -1000.0, 'currency_id': foreign_currency.id, 'balance': -166.67},
{'flag': 'exchange_diff', 'amount_currency': 0.0, 'currency_id': foreign_currency.id, 'balance': -33.33},
{'flag': 'new_aml', 'amount_currency': -2000.0, 'currency_id': foreign_currency.id, 'balance': -400.0},
{'flag': 'new_aml', 'amount_currency': -3000.0, 'currency_id': foreign_currency.id, 'balance': -500.0},
{'flag': 'exchange_diff', 'amount_currency': 0.0, 'currency_id': foreign_currency.id, 'balance': -100.0},
])
self.assertRecordValues(wizard, [{'state': 'valid'}])
# The amount is the same, no message under the 'amount' field.
self.assert_form_extra_text_value(wizard, False)
wizard._action_validate()
self.assertRecordValues(st_line.line_ids, [
# pylint: disable=C0326
{'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, [
# pylint: disable=C0326
{'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_validation_foreign_curr_st_line_comp_curr_payment_partial_exchange_difference(self):
comp_curr = self.env.company.currency_id
foreign_curr = self.currency_data['currency']
st_line = self._create_st_line(
650.0,
date='2017-01-01',
foreign_currency_id=foreign_curr.id,
amount_currency=800,
)
payment = self.env['account.payment'].create({
'partner_id': self.partner_a.id,
'payment_type': 'inbound',
'partner_type': 'customer',
'date': '2017-01-01',
'amount': 725.0,
})
payment.action_post()
pay_line, _counterpart_lines, _writeoff_lines = payment._seek_for_lines()
wizard = self.env['bank.rec.widget'].with_context(default_st_line_id=st_line.id).new({})
wizard._action_add_new_amls(pay_line)
self.assertRecordValues(wizard.line_ids, [
# pylint: disable=C0326
{'flag': 'liquidity', 'amount_currency': 650.0, 'currency_id': comp_curr.id, 'balance': 650.0},
{'flag': 'new_aml', 'amount_currency': -650.0, 'currency_id': comp_curr.id, 'balance': -650.0},
])
# Switch to a full reconciliation.
line = wizard.line_ids.filtered(lambda x: x.flag == 'new_aml')
wizard._js_action_mount_line_in_edit(line.index)
wizard._js_action_apply_line_suggestion(line.index)
# 725 * 800 / 650 = 892.308
self.assertRecordValues(wizard.line_ids, [
# pylint: disable=C0326
{'flag': 'liquidity', 'amount_currency': 650.0, 'currency_id': comp_curr.id, 'balance': 650.0},
{'flag': 'new_aml', 'amount_currency': -725.0, 'currency_id': comp_curr.id, 'balance': -725.0},
{'flag': 'auto_balance', 'amount_currency': 92.308, 'currency_id': foreign_curr.id, 'balance': 75.0},
])
# Switch to a partial reconciliation.
wizard._js_action_apply_line_suggestion(line.index)
self.assertRecordValues(wizard.line_ids, [
# pylint: disable=C0326
{'flag': 'liquidity', 'amount_currency': 650.0, 'currency_id': comp_curr.id, 'balance': 650.0},
{'flag': 'new_aml', 'amount_currency': -650.0, 'currency_id': comp_curr.id, 'balance': -650.0},
])
wizard._action_validate()
self.assertRecordValues(pay_line, [{'amount_residual': 75.0}])
def test_validation_remove_exchange_difference(self):
""" Test the case when the foreign currency is missing on the statement line.
In that case, the user can remove the exchange difference in order to fully reconcile both items without additional
write-off/exchange difference.
"""
# 1200.0 comp_curr = 2400.0 foreign_curr in 2017 (rate 1:2)
st_line = self._create_st_line(
1200.0,
date='2017-01-01',
)
# 1200.0 comp_curr = 3600.0 foreign_curr in 2016 (rate 1:3)
inv_line = self._create_invoice_line(
'out_invoice',
currency_id=self.currency_data['currency'].id,
invoice_date='2016-01-01',
invoice_line_ids=[{'price_unit': 3600.0}],
)
wizard = self.env['bank.rec.widget'].with_context(default_st_line_id=st_line.id).new({})
wizard._action_add_new_amls(inv_line)
self.assertRecordValues(wizard.line_ids, [
# pylint: disable=C0326
{'flag': 'liquidity', 'amount_currency': 1200.0, 'currency_id': self.company_data['currency'].id, 'balance': 1200.0},
{'flag': 'new_aml', 'amount_currency': -2400.0, 'currency_id': self.currency_data['currency'].id, 'balance': -800.0},
{'flag': 'exchange_diff', 'amount_currency': 0.0, 'currency_id': self.currency_data['currency'].id, 'balance': -400.0},
])
self.assertRecordValues(wizard, [{'state': 'valid'}])
# Remove the partial.
line_index = wizard.line_ids.filtered(lambda x: x.flag == 'new_aml').index
wizard._js_action_mount_line_in_edit(line_index)
wizard._js_action_apply_line_suggestion(line_index)
self.assertRecordValues(wizard.line_ids, [
# pylint: disable=C0326
{'flag': 'liquidity', 'amount_currency': 1200.0, 'currency_id': self.company_data['currency'].id, 'balance': 1200.0},
{'flag': 'new_aml', 'amount_currency': -3600.0, 'currency_id': self.currency_data['currency'].id, 'balance': -1200.0},
{'flag': 'exchange_diff', 'amount_currency': 0.0, 'currency_id': self.currency_data['currency'].id, 'balance': -600.0},
{'flag': 'auto_balance', 'amount_currency': 600.0, 'currency_id': self.company_data['currency'].id, 'balance': 600.0},
])
exchange_diff_index = wizard.line_ids.filtered(lambda x: x.flag == 'exchange_diff').index
wizard._js_action_remove_line(exchange_diff_index)
self.assertRecordValues(wizard.line_ids, [
# pylint: disable=C0326
{'flag': 'liquidity', 'amount_currency': 1200.0, 'currency_id': self.company_data['currency'].id, 'balance': 1200.0},
{'flag': 'new_aml', 'amount_currency': -3600.0, 'currency_id': self.currency_data['currency'].id, 'balance': -1200.0},
])
wizard._action_validate()
self.assertRecordValues(st_line.line_ids, [
# pylint: disable=C0326
{'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.currency_data['currency'].id, 'balance': -1200.0, 'reconciled': True},
])
self.assertRecordValues(st_line, [{'is_reconciled': True}])
self.assertRecordValues(inv_line.move_id, [{'payment_state': 'paid'}])
def test_validation_new_aml_one_foreign_currency_on_st_line(self):
income_exchange_account = self.env.company.income_currency_exchange_account_id
# 4800.0 curr2 == 1200.0 comp_curr (rate 4:1)
st_line = self._create_st_line(
1200.0,
date='2017-01-01',
)
# 4800.0 curr2 in 2016 (rate 6:1)
inv_line = self._create_invoice_line(
'out_invoice',
invoice_date='2016-01-01',
currency_id=self.currency_data_2['currency'].id,
invoice_line_ids=[{'price_unit': 4800.0}],
)
wizard = self.env['bank.rec.widget'].with_context(default_st_line_id=st_line.id).new({})
wizard._action_add_new_amls(inv_line)
self.assertRecordValues(wizard.line_ids, [
# pylint: disable=C0326
{'flag': 'liquidity', 'amount_currency': 1200.0, 'currency_id': self.company_data['currency'].id, 'balance': 1200.0},
{'flag': 'new_aml', 'amount_currency': -4800.0, 'currency_id': self.currency_data_2['currency'].id, 'balance': -800.0},
{'flag': 'exchange_diff', 'amount_currency': 0.0, 'currency_id': self.currency_data_2['currency'].id, 'balance': -400.0},
])
self.assertRecordValues(wizard, [{'state': 'valid'}])
# The amount is the same, no message under the 'amount' field.
self.assert_form_extra_text_value(wizard, False)
wizard._action_validate()
self.assertRecordValues(st_line.line_ids, [
# pylint: disable=C0326
{'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': -4800.0, 'currency_id': self.currency_data_2['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, [
# pylint: disable=C0326
{'account_id': inv_line.account_id.id, 'amount_currency': 0.0, 'currency_id': self.currency_data_2['currency'].id, 'balance': 400.0, 'reconciled': True, 'date': fields.Date.from_string('2017-01-31')},
{'account_id': income_exchange_account.id, 'amount_currency': 0.0, 'currency_id': self.currency_data_2['currency'].id, 'balance': -400.0, 'reconciled': False, 'date': fields.Date.from_string('2017-01-31')},
])
# Checks that the wizard still display the 3 initial lines
self.assertRecordValues(wizard.line_ids, [
# pylint: disable=C0326
{'flag': 'liquidity', 'amount_currency': 1200.0, 'currency_id': self.company_data['currency'].id, 'balance': 1200.0},
{'flag': 'aml', 'amount_currency': -4800.0, 'currency_id': self.currency_data_2['currency'].id, 'balance': -800.0},
{'flag': 'aml', 'amount_currency': 0.0, 'currency_id': self.currency_data_2['currency'].id, 'balance': -400.0}, # represents the exchange diff
])
# Reset the wizard.
wizard._js_action_reset()
self.assertRecordValues(wizard.line_ids, [
# pylint: disable=C0326
{'flag': 'liquidity', 'amount_currency': 1200.0, 'currency_id': self.company_data['currency'].id, 'balance': 1200.0},
{'flag': 'auto_balance', 'amount_currency': -1200.0, 'currency_id': self.company_data['currency'].id, 'balance': -1200.0},
])
# Create the same invoice with a higher amount to check the partial flow.
# 4800.0 curr2 in 2016 (rate 6:1)
inv_line = self._create_invoice_line(
'out_invoice',
invoice_date='2016-01-01',
currency_id=self.currency_data_2['currency'].id,
invoice_line_ids=[{'price_unit': 9600.0}],
)
wizard._action_add_new_amls(inv_line)
self.assertRecordValues(wizard.line_ids, [
# pylint: disable=C0326
{'flag': 'liquidity', 'amount_currency': 1200.0, 'currency_id': self.company_data['currency'].id, 'balance': 1200.0},
{'flag': 'new_aml', 'amount_currency': -4800.0, 'currency_id': self.currency_data_2['currency'].id, 'balance': -800.0},
{'flag': 'exchange_diff', 'amount_currency': 0.0, 'currency_id': self.currency_data_2['currency'].id, 'balance': -400.0},
])
# Check the message under the 'amount' field.
line = wizard.line_ids.filtered(lambda x: x.flag == 'new_aml')
wizard._js_action_mount_line_in_edit(line.index)
self.assert_form_extra_text_value(
wizard,
r".+open amount of 9,600.000.+ reduced by 4,800.000.+ set the invoice as fully paid .",
)
self.assertRecordValues(line, [{
'suggestion_amount_currency': -9600.0,
'suggestion_balance': -1600.0,
}])
# Switch to a full reconciliation.
wizard._js_action_apply_line_suggestion(line.index)
self.assertRecordValues(wizard.line_ids, [
# pylint: disable=C0326
{'flag': 'liquidity', 'amount_currency': 1200.0, 'currency_id': self.company_data['currency'].id, 'balance': 1200.0},
{'flag': 'new_aml', 'amount_currency': -9600.0, 'currency_id': self.currency_data_2['currency'].id, 'balance': -1600.0},
{'flag': 'exchange_diff', 'amount_currency': 0.0, 'currency_id': self.currency_data_2['currency'].id, 'balance': -800.0},
{'flag': 'auto_balance', 'amount_currency': 1200.0, 'currency_id': self.company_data['currency'].id, 'balance': 1200.0},
])
# Check the message under the 'amount' field.
line = wizard.line_ids.filtered(lambda x: x.flag == 'new_aml')
wizard._js_action_mount_line_in_edit(line.index)
self.assert_form_extra_text_value(
wizard,
r".+open amount of 9,600.000.+ paid .+ record a partial payment .",
)
self.assertRecordValues(line, [{
'suggestion_amount_currency': -4800.0,
'suggestion_balance': -800.0,
}])
# Switch back to a partial reconciliation.
wizard._js_action_apply_line_suggestion(line.index)
self.assertRecordValues(wizard, [{'state': 'valid'}])
# Reconcile
wizard._action_validate()
self.assertRecordValues(st_line.line_ids, [
# pylint: disable=C0326
{'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': -4800.0, 'currency_id': self.currency_data_2['currency'].id, 'balance': -1200.0, 'reconciled': True},
])
self.assertRecordValues(st_line, [{'is_reconciled': True}])
self.assertRecordValues(inv_line.move_id, [{
'payment_state': 'partial',
'amount_residual': 4800.0,
}])
self.assertRecordValues(inv_line, [{
'amount_residual_currency': 4800.0,
'amount_residual': 800.0,
'reconciled': False,
}])
self.assertRecordValues(inv_line.matched_credit_ids.exchange_move_id.line_ids, [
# pylint: disable=C0326
{'account_id': inv_line.account_id.id, 'amount_currency': 0.0, 'currency_id': self.currency_data_2['currency'].id, 'balance': 400.0, 'reconciled': True, 'date': fields.Date.from_string('2017-01-31')},
{'account_id': income_exchange_account.id, 'amount_currency': 0.0, 'currency_id': self.currency_data_2['currency'].id, 'balance': -400.0, 'reconciled': False, 'date': fields.Date.from_string('2017-01-31')},
])
def test_validation_new_aml_one_foreign_currency_on_inv_line(self):
income_exchange_account = self.env.company.income_currency_exchange_account_id
# 1200.0 comp_curr is equals to 4800.0 curr2 in 2017 (rate 4:1)
st_line = self._create_st_line(
1200.0,
date='2017-01-01',
)
# 4800.0 curr2 == 800.0 comp_curr (rate 6:1)
inv_line = self._create_invoice_line(
'out_invoice',
currency_id=self.currency_data_2['currency'].id,
invoice_date='2016-01-01',
invoice_line_ids=[{'price_unit': 4800.0}],
)
wizard = self.env['bank.rec.widget'].with_context(default_st_line_id=st_line.id).new({})
wizard._action_add_new_amls(inv_line)
self.assertRecordValues(wizard.line_ids, [
# pylint: disable=C0326
{'flag': 'liquidity', 'amount_currency': 1200.0, 'currency_id': self.company_data['currency'].id, 'balance': 1200.0},
{'flag': 'new_aml', 'amount_currency': -4800.0, 'currency_id': self.currency_data_2['currency'].id, 'balance': -800.0},
{'flag': 'exchange_diff', 'amount_currency': 0.0, 'currency_id': self.currency_data_2['currency'].id, 'balance': -400.0},
])
self.assertRecordValues(wizard, [{'state': 'valid'}])
# The amount is the same, no message under the 'amount' field.
self.assert_form_extra_text_value(wizard, False)
# Remove the line to see if the exchange difference is well removed.
wizard._action_remove_new_amls(inv_line)
self.assertRecordValues(wizard.line_ids, [
# pylint: disable=C0326
{'flag': 'liquidity', 'amount_currency': 1200.0, 'currency_id': self.company_data['currency'].id, 'balance': 1200.0},
{'flag': 'auto_balance', 'amount_currency': -1200.0, 'currency_id': self.company_data['currency'].id, 'balance': -1200.0},
])
self.assertRecordValues(wizard, [{'state': 'invalid'}])
# Mount the line again and validate.
wizard._action_add_new_amls(inv_line)
wizard._action_validate()
self.assertRecordValues(st_line.line_ids, [
# pylint: disable=C0326
{'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': -4800.0, 'currency_id': self.currency_data_2['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, [
# pylint: disable=C0326
{'account_id': inv_line.account_id.id, 'amount_currency': 0.0, 'currency_id': self.currency_data_2['currency'].id, 'balance': 400.0, 'reconciled': True, 'date': fields.Date.from_string('2017-01-31')},
{'account_id': income_exchange_account.id, 'amount_currency': 0.0, 'currency_id': self.currency_data_2['currency'].id, 'balance': -400.0, 'reconciled': False, 'date': fields.Date.from_string('2017-01-31')},
])
# Reset the wizard.
wizard._js_action_reset()
self.assertRecordValues(wizard.line_ids, [
# pylint: disable=C0326
{'flag': 'liquidity', 'amount_currency': 1200.0, 'currency_id': self.company_data['currency'].id, 'balance': 1200.0},
{'flag': 'auto_balance', 'amount_currency': -1200.0, 'currency_id': self.company_data['currency'].id, 'balance': -1200.0},
])
# Create the same invoice with a higher amount to check the partial flow.
# 7200.0 curr2 == 1200.0 comp_curr (rate 6:1)
inv_line = self._create_invoice_line(
'out_invoice',
currency_id=self.currency_data_2['currency'].id,
invoice_date='2016-01-01',
invoice_line_ids=[{'price_unit': 7200.0}],
)
wizard._action_add_new_amls(inv_line)
self.assertRecordValues(wizard.line_ids, [
# pylint: disable=C0326
{'flag': 'liquidity', 'amount_currency': 1200.0, 'currency_id': self.company_data['currency'].id, 'balance': 1200.0},
{'flag': 'new_aml', 'amount_currency': -4800.0, 'currency_id': self.currency_data_2['currency'].id, 'balance': -800.0},
{'flag': 'exchange_diff', 'amount_currency': 0.0, 'currency_id': self.currency_data_2['currency'].id, 'balance': -400.0},
])
# Check the message under the 'amount' field.
line = wizard.line_ids.filtered(lambda x: x.flag == 'new_aml')
wizard._js_action_mount_line_in_edit(line.index)
self.assert_form_extra_text_value(
wizard,
r".+open amount of 7,200.000.+ reduced by 4,800.000.+ set the invoice as fully paid .",
)
self.assertRecordValues(line, [{
'suggestion_amount_currency': -7200.0,
'suggestion_balance': -1200.0,
}])
# Switch to a full reconciliation.
wizard._js_action_apply_line_suggestion(line.index)
self.assertRecordValues(wizard.line_ids, [
# pylint: disable=C0326
{'flag': 'liquidity', 'amount_currency': 1200.0, 'currency_id': self.company_data['currency'].id, 'balance': 1200.0},
{'flag': 'new_aml', 'amount_currency': -7200.0, 'currency_id': self.currency_data_2['currency'].id, 'balance': -1200.0},
{'flag': 'exchange_diff', 'amount_currency': 0.0, 'currency_id': self.currency_data_2['currency'].id, 'balance': -600.0},
{'flag': 'auto_balance', 'amount_currency': 600.0, 'currency_id': self.company_data['currency'].id, 'balance': 600.0},
])
# Check the message under the 'amount' field.
line = wizard.line_ids.filtered(lambda x: x.flag == 'new_aml')
wizard._js_action_mount_line_in_edit(line.index)
self.assert_form_extra_text_value(
wizard,
r".+open amount of 7,200.000.+ paid .+ record a partial payment .",
)
self.assertRecordValues(line, [{
'suggestion_amount_currency': -4800.0,
'suggestion_balance': -800.0,
}])
# Switch back to a partial reconciliation.
wizard._js_action_apply_line_suggestion(line.index)
self.assertRecordValues(wizard, [{'state': 'valid'}])
# Reconcile
wizard._action_validate()
self.assertRecordValues(st_line.line_ids, [
# pylint: disable=C0326
{'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': -4800.0, 'currency_id': self.currency_data_2['currency'].id, 'balance': -1200.0, 'reconciled': True},
])
self.assertRecordValues(st_line, [{'is_reconciled': True}])
self.assertRecordValues(inv_line.move_id, [{
'payment_state': 'partial',
'amount_residual': 2400.0,
}])
self.assertRecordValues(inv_line.matched_credit_ids.exchange_move_id.line_ids, [
# pylint: disable=C0326
{'account_id': inv_line.account_id.id, 'amount_currency': 0.0, 'currency_id': self.currency_data_2['currency'].id, 'balance': 400.0, 'reconciled': True, 'date': fields.Date.from_string('2017-01-31')},
{'account_id': income_exchange_account.id, 'amount_currency': 0.0, 'currency_id': self.currency_data_2['currency'].id, 'balance': -400.0, 'reconciled': False, 'date': fields.Date.from_string('2017-01-31')},
])
def test_validation_new_aml_multi_currencies(self):
# 6300.0 curr2 == 1800.0 comp_curr (bank rate 3.5:1 instead of the odoo rate 4:1)
st_line = self._create_st_line(
1800.0,
date='2017-01-01',
foreign_currency_id=self.currency_data_2['currency'].id,
amount_currency=6300.0,
)
# 21600.0 curr3 == 1800.0 comp_curr (rate 12:1)
inv_line = self._create_invoice_line(
'out_invoice',
currency_id=self.currency_data_3['currency'].id,
invoice_date='2016-01-01',
invoice_line_ids=[{'price_unit': 21600.0}],
)
wizard = self.env['bank.rec.widget'].with_context(default_st_line_id=st_line.id).new({})
wizard._action_add_new_amls(inv_line)
self.assertRecordValues(wizard.line_ids, [
# pylint: disable=C0326
{'flag': 'liquidity', 'amount_currency': 1800.0, 'currency_id': self.company_data['currency'].id, 'balance': 1800.0},
{'flag': 'new_aml', 'amount_currency': -21600.0, 'currency_id': self.currency_data_3['currency'].id, 'balance': -1800.0},
])
self.assertRecordValues(wizard, [{'state': 'valid'}])
# The amount is the same, no message under the 'amount' field.
self.assert_form_extra_text_value(wizard, False)
wizard._action_validate()
self.assertRecordValues(st_line.line_ids, [
# pylint: disable=C0326
{'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': -21600.0, 'currency_id': self.currency_data_3['currency'].id, 'balance': -1800.0, 'reconciled': True},
])
self.assertRecordValues(st_line, [{'is_reconciled': True}])
self.assertRecordValues(inv_line.move_id, [{'payment_state': 'paid'}])
# Reset the wizard.
wizard._js_action_reset()
self.assertRecordValues(wizard.line_ids, [
# pylint: disable=C0326
{'flag': 'liquidity', 'amount_currency': 1800.0, 'currency_id': self.company_data['currency'].id, 'balance': 1800.0},
{'flag': 'auto_balance', 'amount_currency': -6300.0, 'currency_id': self.currency_data_2['currency'].id, 'balance': -1800.0},
])
# Create the same invoice with a higher amount to check the partial flow.
# 32400.0 curr3 == 2700.0 comp_curr (rate 12:1)
inv_line = self._create_invoice_line(
'out_invoice',
currency_id=self.currency_data_3['currency'].id,
invoice_date='2016-01-01',
invoice_line_ids=[{'price_unit': 32400.0}],
)
wizard._action_add_new_amls(inv_line)
self.assertRecordValues(wizard.line_ids, [
# pylint: disable=C0326
{'flag': 'liquidity', 'amount_currency': 1800.0, 'currency_id': self.company_data['currency'].id, 'balance': 1800.0},
{'flag': 'new_aml', 'amount_currency': -21600.0, 'currency_id': self.currency_data_3['currency'].id, 'balance': -1800.0},
])
# Check the message under the 'amount' field.
line = wizard.line_ids.filtered(lambda x: x.flag == 'new_aml')
wizard._js_action_mount_line_in_edit(line.index)
self.assert_form_extra_text_value(
wizard,
r".+open amount of 32,400.000.+ reduced by 21,600.000.+ set the invoice as fully paid .",
)
self.assertRecordValues(line, [{
'suggestion_amount_currency': -32400.0,
'suggestion_balance': -2700.0,
}])
# Switch to a full reconciliation.
wizard._js_action_apply_line_suggestion(line.index)
self.assertRecordValues(wizard.line_ids, [
# pylint: disable=C0326
{'flag': 'liquidity', 'amount_currency': 1800.0, 'currency_id': self.company_data['currency'].id, 'balance': 1800.0},
{'flag': 'new_aml', 'amount_currency': -32400.0, 'currency_id': self.currency_data_3['currency'].id, 'balance': -2700.0},
{'flag': 'auto_balance', 'amount_currency': 3150.0, 'currency_id': self.currency_data_2['currency'].id, 'balance': 900.0},
])
# Check the message under the 'amount' field.
line = wizard.line_ids.filtered(lambda x: x.flag == 'new_aml')
wizard._js_action_mount_line_in_edit(line.index)
self.assert_form_extra_text_value(
wizard,
r".+open amount of 32,400.000.+ paid .+ record a partial payment .",
)
self.assertRecordValues(line, [{
'suggestion_amount_currency': -21600.0,
'suggestion_balance': -1800.0,
}])
# Switch back to a partial reconciliation.
wizard._js_action_apply_line_suggestion(line.index)
self.assertRecordValues(wizard, [{'state': 'valid'}])
# Reconcile
wizard._action_validate()
self.assertRecordValues(st_line.line_ids, [
# pylint: disable=C0326
{'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': -21600.0, 'currency_id': self.currency_data_3['currency'].id, 'balance': -1800.0, 'reconciled': True},
])
self.assertRecordValues(st_line, [{'is_reconciled': True}])
self.assertRecordValues(inv_line.move_id, [{
'payment_state': 'partial',
'amount_residual': 10800.0,
}])
def test_validation_new_aml_multi_currencies_exchange_diff_custom_rates(self):
self.company_data['default_journal_bank'].currency_id = self.currency_data['currency']
self.env['res.currency.rate'].create([
{
'name': '2017-02-01',
'rate': 1.0683,
'currency_id': self.currency_data['currency'].id,
'company_id': self.env.company.id,
},
{
'name': '2017-03-01',
'rate': 1.0812,
'currency_id': self.currency_data['currency'].id,
'company_id': self.env.company.id,
},
])
# 960.14 curr1 = 888.03 comp_curr
st_line = self._create_st_line(
-960.14,
date='2017-03-01',
)
# 112.7 curr1 == 105.49 comp_curr
inv_line1 = self._create_invoice_line(
'in_invoice',
currency_id=self.currency_data['currency'].id,
invoice_date='2017-02-01',
invoice_line_ids=[{'price_unit': 112.7}],
)
# 847.44 curr1 == 793.26 comp_curr
inv_line2 = self._create_invoice_line(
'in_invoice',
currency_id=self.currency_data['currency'].id,
invoice_date='2017-02-01',
invoice_line_ids=[{'price_unit': 847.44}],
)
wizard = self.env['bank.rec.widget'].with_context(default_st_line_id=st_line.id).new({})
wizard._action_add_new_amls(inv_line1)
wizard._action_add_new_amls(inv_line2)
self.assertRecordValues(wizard.line_ids, [
# pylint: disable=C0326
{'flag': 'liquidity', 'amount_currency': -960.14, 'balance': -888.03},
{'flag': 'new_aml', 'amount_currency': 112.7, 'balance': 105.49},
{'flag': 'exchange_diff', 'amount_currency': 0.0, 'balance': -1.25},
{'flag': 'new_aml', 'amount_currency': 847.44, 'balance': 793.26},
{'flag': 'exchange_diff', 'amount_currency': 0.0, 'balance': -9.47},
])
wizard._action_remove_new_amls(inv_line1 + inv_line2)
wizard._action_add_new_amls(inv_line2)
wizard._action_add_new_amls(inv_line1)
self.assertRecordValues(wizard.line_ids, [
# pylint: disable=C0326
{'flag': 'liquidity', 'amount_currency': -960.14, 'balance': -888.03},
{'flag': 'new_aml', 'amount_currency': 847.44, 'balance': 793.26},
{'flag': 'exchange_diff', 'amount_currency': 0.0, 'balance': -9.47},
{'flag': 'new_aml', 'amount_currency': 112.7, 'balance': 105.49},
{'flag': 'exchange_diff', 'amount_currency': 0.0, 'balance': -1.25},
])
def test_validation_with_partner(self):
partner = self.partner_a.copy()
st_line = self._create_st_line(1000.0, partner_id=self.partner_a.id)
# The wizard can be validated directly thanks to the receivable account set on the partner.
wizard = self.env['bank.rec.widget'].with_context(default_st_line_id=st_line.id).new({})
self.assertRecordValues(wizard, [{'state': 'valid'}])
# Validate and check the statement line.
wizard._action_validate()
self.assertRecordValues(st_line, [{'partner_id': self.partner_a.id}])
liquidity_line, _suspense_line, other_line = st_line._seek_for_lines()
account = self.partner_a.property_account_receivable_id
self.assertRecordValues(liquidity_line + other_line, [
# pylint: disable=C0326
{'account_id': liquidity_line.account_id.id, 'balance': 1000.0},
{'account_id': account.id, 'balance': -1000.0},
])
self.assertRecordValues(wizard, [{'state': 'reconciled'}])
# Match an invoice with a different partner.
wizard._js_action_reset()
inv_line = self._create_invoice_line(
'out_invoice',
partner_id=partner.id,
invoice_line_ids=[{'price_unit': 1000.0}],
)
wizard._action_add_new_amls(inv_line)
wizard._action_validate()
liquidity_line, suspense_line, other_line = st_line._seek_for_lines()
self.assertRecordValues(st_line, [{'partner_id': partner.id}])
self.assertRecordValues(st_line.move_id, [{'partner_id': partner.id}])
self.assertRecordValues(liquidity_line + other_line, [
# pylint: disable=C0326
{'account_id': liquidity_line.account_id.id, 'partner_id': partner.id, 'balance': 1000.0},
{'account_id': inv_line.account_id.id, 'partner_id': partner.id, 'balance': -1000.0},
])
self.assertRecordValues(wizard, [{'state': 'reconciled'}])
# Reset the wizard and match invoices with different partners.
wizard._js_action_reset()
partner1 = self.partner_a.copy()
inv_line1 = self._create_invoice_line(
'out_invoice',
partner_id=partner1.id,
invoice_line_ids=[{'price_unit': 300.0}],
)
partner2 = self.partner_a.copy()
inv_line2 = self._create_invoice_line(
'out_invoice',
partner_id=partner2.id,
invoice_line_ids=[{'price_unit': 300.0}],
)
wizard._action_add_new_amls(inv_line1 + inv_line2)
wizard._action_validate()
liquidity_line, _suspense_line, other_line = st_line._seek_for_lines()
self.assertRecordValues(st_line, [{'partner_id': False}])
self.assertRecordValues(st_line.move_id, [{'partner_id': False}])
self.assertRecordValues(liquidity_line + other_line, [
# pylint: disable=C0326
{'account_id': liquidity_line.account_id.id, 'partner_id': False, 'balance': 1000.0},
{'account_id': inv_line1.account_id.id, 'partner_id': partner1.id, 'balance': -300.0},
{'account_id': inv_line2.account_id.id, 'partner_id': partner2.id, 'balance': -300.0},
{'account_id': account.id, 'partner_id': False, 'balance': -400.0},
])
self.assertRecordValues(wizard, [{'state': 'reconciled'}])
# Clear the accounts set on the partner and reset the widget.
# The wizard should be invalid since we are not able to set an open balance.
partner.property_account_receivable_id = None
wizard._js_action_reset()
liquidity_line, suspense_line, other_line = st_line._seek_for_lines()
self.assertRecordValues(wizard.line_ids, [
# pylint: disable=C0326
{'flag': 'liquidity', 'account_id': liquidity_line.account_id.id},
{'flag': 'auto_balance', 'account_id': suspense_line.account_id.id},
])
self.assertRecordValues(wizard, [{'state': 'invalid'}])
def test_partner_receivable_payable_account(self):
self.partner_a.write({'customer_rank': 1, 'supplier_rank': 0}) # always receivable
self.partner_b.write({'customer_rank': 0, 'supplier_rank': 1}) # always payable
partner_c = self.partner_b.copy({'customer_rank': 3, 'supplier_rank': 2}) # no preference
positive_st_line = self._create_st_line(1000)
journal_account = positive_st_line.journal_id.default_account_id
wizard = self.env['bank.rec.widget'].with_context(default_st_line_id=positive_st_line.id).new({})
suspense_line = wizard.line_ids.filtered(lambda l: l.flag != "liquidity")
wizard._js_action_mount_line_in_edit(suspense_line.index)
suspense_line.partner_id = self.partner_a
wizard._line_value_changed_partner_id(suspense_line)
self.assertRecordValues(wizard.line_ids, [
# pylint: disable=C0326
{'partner_id': False, 'account_id': journal_account.id},
{'partner_id': self.partner_a.id, 'account_id': self.partner_a.property_account_receivable_id.id},
])
suspense_line.partner_id = self.partner_b
wizard._line_value_changed_partner_id(suspense_line)
self.assertRecordValues(wizard.line_ids, [
# pylint: disable=C0326
{'partner_id': False, 'account_id': journal_account.id},
{'partner_id': self.partner_b.id, 'account_id': self.partner_b.property_account_payable_id.id},
])
suspense_line.partner_id = partner_c
wizard._line_value_changed_partner_id(suspense_line)
self.assertRecordValues(wizard.line_ids, [
# pylint: disable=C0326
{'partner_id': False, 'account_id': journal_account.id},
{'partner_id': partner_c.id, 'account_id': partner_c.property_account_receivable_id.id},
])
negative_st_line = self._create_st_line(-1000)
wizard = self.env['bank.rec.widget'].with_context(default_st_line_id=negative_st_line.id).new({})
suspense_line = wizard.line_ids.filtered(lambda l: l.flag != "liquidity")
wizard._js_action_mount_line_in_edit(suspense_line.index)
suspense_line.partner_id = self.partner_a
wizard._line_value_changed_partner_id(suspense_line)
self.assertRecordValues(wizard.line_ids, [
# pylint: disable=C0326
{'partner_id': False, 'account_id': journal_account.id},
{'partner_id': self.partner_a.id, 'account_id': self.partner_a.property_account_receivable_id.id},
])
suspense_line.partner_id = self.partner_b
wizard._line_value_changed_partner_id(suspense_line)
self.assertRecordValues(wizard.line_ids, [
# pylint: disable=C0326
{'partner_id': False, 'account_id': journal_account.id},
{'partner_id': self.partner_b.id, 'account_id': self.partner_b.property_account_payable_id.id},
])
suspense_line.partner_id = partner_c
wizard._line_value_changed_partner_id(suspense_line)
self.assertRecordValues(wizard.line_ids, [
# pylint: disable=C0326
{'partner_id': False, 'account_id': journal_account.id},
{'partner_id': partner_c.id, 'account_id': partner_c.property_account_payable_id.id},
])
def test_validation_using_custom_account(self):
st_line = self._create_st_line(1000.0)
# By default, the wizard can't be validated directly due to the suspense account.
wizard = self.env['bank.rec.widget'].with_context(default_st_line_id=st_line.id).new({})
self.assertRecordValues(wizard, [{'state': 'invalid'}])
# Mount the auto-balance line in edit mode.
line = wizard.line_ids.filtered(lambda x: x.flag == 'auto_balance')
wizard._js_action_mount_line_in_edit(line.index)
liquidity_line, suspense_line, _other_lines = st_line._seek_for_lines()
self.assertRecordValues(line, [{
'account_id': suspense_line.account_id.id,
'balance': -1000.0,
}])
# Switch to a custom account.
account = self.env['account.account'].create({
'name': "test_validation_using_custom_account",
'code': "424242",
'account_type': "asset_current",
})
line.account_id = account
wizard._line_value_changed_account_id(line)
self.assertRecordValues(wizard.line_ids, [
# pylint: disable=C0326
{'flag': 'liquidity', 'account_id': liquidity_line.account_id.id, 'balance': 1000.0},
{'flag': 'manual', 'account_id': account.id, 'balance': -1000.0},
])
# The wizard can be validated.
self.assertRecordValues(wizard, [{'state': 'valid'}])
# Validate and check the statement line.
wizard._action_validate()
liquidity_line, _suspense_line, other_line = st_line._seek_for_lines()
self.assertRecordValues(liquidity_line + other_line, [
# pylint: disable=C0326
{'account_id': liquidity_line.account_id.id, 'balance': 1000.0},
{'account_id': account.id, 'balance': -1000.0},
])
self.assertRecordValues(wizard, [{'state': 'reconciled'}])
def test_validation_with_taxes(self):
st_line = self._create_st_line(1000.0)
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(4))
tax_21 = self.env['account.tax'].create({
'name': "tax_21",
'amount': 21,
'invoice_repartition_line_ids': [
Command.create({
'factor_percent': 100,
'repartition_type': 'base',
'tag_ids': [Command.set(tax_tags[0].ids)],
}),
Command.create({
'factor_percent': 100,
'repartition_type': 'tax',
'tag_ids': [Command.set(tax_tags[1].ids)],
}),
],
'refund_repartition_line_ids': [
Command.create({
'factor_percent': 100,
'repartition_type': 'base',
'tag_ids': [Command.set(tax_tags[2].ids)],
}),
Command.create({
'factor_percent': 100,
'repartition_type': 'tax',
'tag_ids': [Command.set(tax_tags[3].ids)],
}),
],
})
wizard = self.env['bank.rec.widget'].with_context(default_st_line_id=st_line.id).new({})
line = wizard.line_ids.filtered(lambda x: x.flag == 'auto_balance')
wizard._js_action_mount_line_in_edit(line.index)
line.tax_ids = [Command.link(tax_21.id)]
wizard._line_value_changed_tax_ids(line)
self.assertRecordValues(wizard.line_ids, [
# pylint: disable=C0326
{'flag': 'liquidity', 'balance': 1000.0, 'tax_tag_ids': []},
{'flag': 'manual', 'balance': -826.45, 'tax_tag_ids': tax_tags[0].ids},
{'flag': 'tax_line', 'balance': -173.55, 'tax_tag_ids': tax_tags[1].ids},
])
# Remove the tax directly.
line.tax_ids = [Command.clear()]
wizard._line_value_changed_tax_ids(line)
self.assertRecordValues(wizard.line_ids, [
# pylint: disable=C0326
{'flag': 'liquidity', 'balance': 1000.0, 'tax_tag_ids': []},
{'flag': 'manual', 'balance': -1000.0, 'tax_tag_ids': []},
])
# Edit the base line. The tax tags should be the refund ones.
line = wizard.line_ids.filtered(lambda x: x.flag == 'manual')
wizard._js_action_mount_line_in_edit(line.index)
line.tax_ids = [Command.link(tax_21.id)]
wizard._line_value_changed_tax_ids(line)
line.balance = 500.0
wizard._line_value_changed_balance(line)
self.assertRecordValues(wizard.line_ids, [
# pylint: disable=C0326
{'flag': 'liquidity', 'balance': 1000.0, 'tax_tag_ids': []},
{'flag': 'manual', 'balance': 500.0, 'tax_tag_ids': tax_tags[2].ids},
{'flag': 'tax_line', 'balance': 105.0, 'tax_tag_ids': tax_tags[3].ids},
{'flag': 'auto_balance', 'balance': -1605.0, 'tax_tag_ids': []},
])
# Edit the base line.
line = wizard.line_ids.filtered(lambda x: x.flag == 'manual')
wizard._js_action_mount_line_in_edit(line.index)
line.balance = -500.0
wizard._line_value_changed_balance(line)
self.assertRecordValues(wizard.line_ids, [
# pylint: disable=C0326
{'flag': 'liquidity', 'balance': 1000.0, 'tax_tag_ids': []},
{'flag': 'manual', 'balance': -500.0, 'tax_tag_ids': tax_tags[0].ids},
{'flag': 'tax_line', 'balance': -105.0, 'tax_tag_ids': tax_tags[1].ids},
{'flag': 'auto_balance', 'balance': -395.0, 'tax_tag_ids': []},
])
# Edit the tax line.
line = wizard.line_ids.filtered(lambda x: x.flag == 'tax_line')
wizard._js_action_mount_line_in_edit(line.index)
line.balance = -100.0
wizard._line_value_changed_balance(line)
self.assertRecordValues(wizard.line_ids, [
# pylint: disable=C0326
{'flag': 'liquidity', 'balance': 1000.0, 'tax_tag_ids': []},
{'flag': 'manual', 'balance': -500.0, 'tax_tag_ids': tax_tags[0].ids},
{'flag': 'tax_line', 'balance': -100.0, 'tax_tag_ids': tax_tags[1].ids},
{'flag': 'auto_balance', 'balance': -400.0, 'tax_tag_ids': []},
])
# Add a new tax.
tax_10 = self.env['account.tax'].create({
'name': "tax_10",
'amount': 10,
})
line = wizard.line_ids.filtered(lambda x: x.flag == 'manual')
wizard._js_action_mount_line_in_edit(line.index)
line.tax_ids = [Command.link(tax_10.id)]
wizard._line_value_changed_tax_ids(line)
self.assertRecordValues(wizard.line_ids, [
# pylint: disable=C0326
{'flag': 'liquidity', 'balance': 1000.0},
{'flag': 'manual', 'balance': -500.0},
{'flag': 'tax_line', 'balance': -105.0},
{'flag': 'tax_line', 'balance': -50.0},
{'flag': 'auto_balance', 'balance': -345.0},
])
# Remove the taxes.
line = wizard.line_ids.filtered(lambda x: x.flag == 'manual')
wizard._js_action_mount_line_in_edit(line.index)
line.tax_ids = [Command.clear()]
wizard._line_value_changed_tax_ids(line)
self.assertRecordValues(wizard.line_ids, [
# pylint: disable=C0326
{'flag': 'liquidity', 'balance': 1000.0},
{'flag': 'manual', 'balance': -500.0},
{'flag': 'auto_balance', 'balance': -500.0},
])
# Reset the amount.
line = wizard.line_ids.filtered(lambda x: x.flag == 'manual')
wizard._js_action_mount_line_in_edit(line.index)
line.balance = -1000.0
wizard._line_value_changed_balance(line)
self.assertRecordValues(wizard.line_ids, [
# pylint: disable=C0326
{'flag': 'liquidity', 'balance': 1000.0},
{'flag': 'manual', 'balance': -1000.0},
])
# Add taxes. We should be back into the "price included taxes" mode.
line = wizard.line_ids.filtered(lambda x: x.flag == 'manual')
wizard._js_action_mount_line_in_edit(line.index)
line.tax_ids = [Command.link(tax_21.id)]
wizard._line_value_changed_tax_ids(line)
self.assertRecordValues(wizard.line_ids, [
# pylint: disable=C0326
{'flag': 'liquidity', 'balance': 1000.0},
{'flag': 'manual', 'balance': -826.45},
{'flag': 'tax_line', 'balance': -173.55},
])
line = wizard.line_ids.filtered(lambda x: x.flag == 'manual')
wizard._js_action_mount_line_in_edit(line.index)
line.tax_ids = [Command.link(tax_10.id)]
wizard._line_value_changed_tax_ids(line)
self.assertRecordValues(wizard.line_ids, [
# pylint: disable=C0326
{'flag': 'liquidity', 'balance': 1000.0},
{'flag': 'manual', 'balance': -763.36},
{'flag': 'tax_line', 'balance': -160.31},
{'flag': 'tax_line', 'balance': -76.33},
])
# Changing the account should recompute the taxes but preserve the "price included taxes" mode.
line = wizard.line_ids.filtered(lambda x: x.flag == 'manual')
wizard._js_action_mount_line_in_edit(line.index)
line.account_id = self.account_revenue1
wizard._line_value_changed_account_id(line)
self.assertRecordValues(wizard.line_ids, [
# pylint: disable=C0326
{'flag': 'liquidity', 'balance': 1000.0},
{'flag': 'manual', 'balance': -763.36},
{'flag': 'tax_line', 'balance': -160.31},
{'flag': 'tax_line', 'balance': -76.33},
])
# The wizard can be validated.
self.assertRecordValues(wizard, [{'state': 'valid'}])
# Validate and check the statement line.
wizard._action_validate()
self.assertRecordValues(st_line.line_ids, [
# pylint: disable=C0326
{'balance': 1000.0},
{'balance': -763.36},
{'balance': -160.31},
{'balance': -76.33},
])
self.assertRecordValues(wizard, [{'state': 'reconciled'}])
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']
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': [
(0, 0, {
'repartition_type': 'base',
}),
(0, 0, {
'repartition_type': 'tax',
'account_id': tax_account.id,
}),
],
'refund_repartition_line_ids': [
(0, 0, {
'repartition_type': 'base',
}),
(0, 0, {
'repartition_type': 'tax',
'account_id': tax_account.id,
}),
],
})
st_line = self._create_st_line(120.0)
wizard = self.env['bank.rec.widget'].with_context(default_st_line_id=st_line.id).new({})
line = wizard.line_ids.filtered(lambda x: x.flag == 'auto_balance')
wizard._js_action_mount_line_in_edit(line.index)
line.account_id = self.account_revenue1
line.tax_ids = [Command.link(caba_tax.id)]
wizard._line_value_changed_tax_ids(line)
self.assertRecordValues(wizard.line_ids, [
# pylint: disable=C0326
{'flag': 'liquidity', 'balance': 120.0, 'account_id': st_line.journal_id.default_account_id.id},
{'flag': 'manual', 'balance': -100.0, 'account_id': self.account_revenue1.id},
{'flag': 'tax_line', 'balance': -20.0, 'account_id': tax_account.id},
])
self.assertRecordValues(wizard, [{'state': 'valid'}])
wizard._action_validate()
self.assertRecordValues(st_line.line_ids, [
# pylint: disable=C0326
{'balance': 120.0, 'tax_ids': [], 'tax_line_id': False, 'account_id': st_line.journal_id.default_account_id.id},
{'balance': -100.0, 'tax_ids': caba_tax.ids, 'tax_line_id': False, 'account_id': self.account_revenue1.id},
{'balance': -20.0, 'tax_ids': [], 'tax_line_id': caba_tax.id, 'account_id': tax_account.id},
])
self.assertRecordValues(wizard, [{'state': 'reconciled'}])
def test_validation_changed_default_account(self):
st_line = self._create_st_line(100.0, partner_id=self.partner_a.id)
original_journal_account_id = st_line.journal_id.default_account_id
# 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
wizard = self.env['bank.rec.widget'].with_context(default_st_line_id=st_line.id).new({})
self.assertRecordValues(wizard, [{'state': 'valid'}])
# Validate and check the statement line.
wizard._action_validate()
liquidity_line, _suspense_line, _other_line = st_line._seek_for_lines()
self.assertRecordValues(liquidity_line, [
{'account_id': original_journal_account_id.id, 'balance': 100.0},
])
self.assertRecordValues(wizard, [{'state': 'reconciled'}])
def test_apply_taxes_with_reco_model(self):
st_line = self._create_st_line(1000.0)
tax_21 = self.env['account.tax'].create({
'name': "tax_21",
'amount': 21,
})
reco_model = self.env['account.reconcile.model'].create({
'name': "test_apply_taxes_with_reco_model",
'rule_type': 'writeoff_button',
'line_ids': [Command.create({
'account_id': self.account_revenue1.id,
'tax_ids': [Command.set(tax_21.ids)],
})],
})
wizard = self.env['bank.rec.widget'].with_context(default_st_line_id=st_line.id).new({})
wizard._action_select_reconcile_model(reco_model)
self.assertRecordValues(wizard.line_ids, [
# pylint: disable=C0326
{'flag': 'liquidity', 'balance': 1000.0},
{'flag': 'manual', 'balance': -826.45},
{'flag': 'tax_line', 'balance': -173.55},
])
def test_percentage_st_line_with_reco_model(self):
journal_curr = self.currency_data['currency']
foreign_curr = self.currency_data_2['currency']
self.company_data['default_journal_bank'].currency_id = journal_curr
# Setup triple currency.
st_line = self._create_st_line(
1000.0,
date='2018-01-01',
foreign_currency_id=foreign_curr.id,
amount_currency=4000.0,
)
reco_model = self.env['account.reconcile.model'].create({
'name': "test_percentage_st_line_with_reco_model",
'rule_type': 'writeoff_button',
'line_ids': [
Command.create({
'amount_type': 'percentage_st_line',
'amount_string': str(percentage),
'label': str(i),
'account_id': self.account_revenue1.id,
})
for i, percentage in enumerate((74.0, 24.0, 12.0, -10.0))
],
})
wizard = self.env['bank.rec.widget'].with_context(default_st_line_id=st_line.id).new({})
wizard._action_select_reconcile_model(reco_model)
self.assertRecordValues(wizard.line_ids, [
{'flag': 'liquidity', 'currency_id': journal_curr.id, 'amount_currency': 1000.0, 'balance': 500.0},
{'flag': 'manual', 'currency_id': journal_curr.id, 'amount_currency': -740.0, 'balance': -370.0},
{'flag': 'manual', 'currency_id': journal_curr.id, 'amount_currency': -240.0, 'balance': -120.0},
{'flag': 'manual', 'currency_id': journal_curr.id, 'amount_currency': -120.0, 'balance': -60.0},
{'flag': 'manual', 'currency_id': journal_curr.id, 'amount_currency': 100.0, 'balance': 50.0},
])
def test_manual_edits_not_replaced(self):
""" 2 partial payments should keep the edited balance """
st_line = self._create_st_line(
1200.0,
date='2017-02-01',
)
inv_line_1 = self._create_invoice_line(
'out_invoice',
invoice_date='2016-01-01',
invoice_line_ids=[{'price_unit': 3000.0}],
)
inv_line_2 = self._create_invoice_line(
'out_invoice',
invoice_date='2017-01-01',
invoice_line_ids=[{'price_unit': 4000.0}],
)
wizard = self.env['bank.rec.widget'].with_context(default_st_line_id=st_line.id).new({})
wizard._action_add_new_amls(inv_line_1)
self.assertRecordValues(wizard.line_ids, [
# pylint: disable=C0326
{'flag': 'liquidity', 'balance': 1200.0},
{'flag': 'new_aml', 'balance':-1200.0},
])
line = wizard.line_ids.filtered(lambda x: x.flag == 'new_aml')
wizard._js_action_mount_line_in_edit(line.index)
line.balance = -600.0
wizard._line_value_changed_balance(line)
self.assertRecordValues(wizard.line_ids, [
# pylint: disable=C0326
{'flag': 'liquidity', 'balance': 1200.0},
{'flag': 'new_aml', 'balance': -600.0},
{'flag': 'auto_balance', 'balance': -600.0},
])
wizard._action_add_new_amls(inv_line_2)
self.assertRecordValues(wizard.line_ids, [
# pylint: disable=C0326
{'flag': 'liquidity', 'balance': 1200.0},
{'flag': 'new_aml', 'balance': -600.0},
{'flag': 'new_aml', 'balance': -600.0},
])
def test_manual_edits_not_replaced_multicurrency(self):
""" 2 partial payments should keep the edited amount_currency """
st_line = self._create_st_line(
1200.0,
date='2018-01-01',
foreign_currency_id=self.currency_data_2['currency'].id,
amount_currency=6000.0, # rate 5:1
)
inv_line_1 = self._create_invoice_line(
'out_invoice',
invoice_date='2016-01-01',
currency_id=self.currency_data_2['currency'].id,
invoice_line_ids=[{'price_unit': 6000.0}], # 1000 company curr (rate 6:1)
)
inv_line_2 = self._create_invoice_line(
'out_invoice',
invoice_date='2017-01-01',
currency_id=self.currency_data_2['currency'].id,
invoice_line_ids=[{'price_unit': 4000.0}], # 1000 company curr (rate 4:1)
)
wizard = self.env['bank.rec.widget'].with_context(default_st_line_id=st_line.id).new({})
wizard._action_add_new_amls(inv_line_1)
self.assertRecordValues(wizard.line_ids, [
# pylint: disable=C0326
{'flag': 'liquidity', 'amount_currency': 1200.0, 'balance': 1200.0},
{'flag': 'new_aml', 'amount_currency':-6000.0, 'balance':-1000.0},
{'flag': 'exchange_diff', 'amount_currency': 0.0, 'balance': -200.0},
])
line = wizard.line_ids.filtered(lambda x: x.flag == 'new_aml')
wizard._js_action_mount_line_in_edit(line.index)
line.amount_currency = -3000.0
wizard._line_value_changed_amount_currency(line)
self.assertRecordValues(wizard.line_ids, [
# pylint: disable=C0326
{'flag': 'liquidity', 'amount_currency': 1200.0, 'balance': 1200.0},
{'flag': 'new_aml', 'amount_currency':-3000.0, 'balance': -500.0},
{'flag': 'exchange_diff', 'amount_currency': 0.0, 'balance': -100.0},
{'flag': 'auto_balance', 'amount_currency':-3000.0, 'balance': -600.0},
])
wizard._action_add_new_amls(inv_line_2)
self.assertRecordValues(wizard.line_ids, [
# pylint: disable=C0326
{'flag': 'liquidity', 'amount_currency': 1200.0, 'balance': 1200.0},
{'flag': 'new_aml', 'amount_currency':-3000.0, 'balance': -500.0},
{'flag': 'exchange_diff', 'amount_currency': 0.0, 'balance': -100.0},
{'flag': 'new_aml', 'amount_currency':-3000.0, 'balance': -750.0},
{'flag': 'exchange_diff', 'amount_currency': 0.0, 'balance': 150.0},
])
def test_creating_manual_line_multi_currencies(self):
# 6300.0 curr2 == 1800.0 comp_curr (bank rate 3.5:1 instead of the odoo rate 4:1)
st_line = self._create_st_line(
1800.0,
date='2017-01-01',
foreign_currency_id=self.currency_data_2['currency'].id,
amount_currency=6300.0,
)
wizard = self.env['bank.rec.widget'].with_context(default_st_line_id=st_line.id).new({})
self.assertRecordValues(wizard.line_ids, [
# pylint: disable=C0326
{'flag': 'liquidity', 'amount_currency': 1800.0, 'currency_id': self.company_data['currency'].id, 'balance': 1800.0},
{'flag': 'auto_balance', 'amount_currency': -6300.0, 'currency_id': self.currency_data_2['currency'].id, 'balance': -1800.0},
])
# Custom balance.
line = wizard.line_ids.filtered(lambda x: x.flag == 'auto_balance')
wizard._js_action_mount_line_in_edit(line.index)
line.balance = -1500.0
wizard._line_value_changed_balance(line)
self.assertRecordValues(wizard.line_ids, [
# pylint: disable=C0326
{'flag': 'liquidity', 'amount_currency': 1800.0, 'currency_id': self.company_data['currency'].id, 'balance': 1800.0},
{'flag': 'manual', 'amount_currency': -6300.0, 'currency_id': self.currency_data_2['currency'].id, 'balance': -1500.0},
{'flag': 'auto_balance', 'amount_currency': 0.0, 'currency_id': self.currency_data_2['currency'].id, 'balance': -300.0},
])
# Custom amount_currency.
line = wizard.line_ids.filtered(lambda x: x.flag == 'manual')
wizard._js_action_mount_line_in_edit(line.index)
line.amount_currency = -4200.0
wizard._line_value_changed_amount_currency(line)
self.assertRecordValues(wizard.line_ids, [
# pylint: disable=C0326
{'flag': 'liquidity', 'amount_currency': 1800.0, 'currency_id': self.company_data['currency'].id, 'balance': 1800.0},
{'flag': 'manual', 'amount_currency': -4200.0, 'currency_id': self.currency_data_2['currency'].id, 'balance': -1200.0},
{'flag': 'auto_balance', 'amount_currency': -2100.0, 'currency_id': self.currency_data_2['currency'].id, 'balance': -600.0},
])
# Custom currency_id.
line = wizard.line_ids.filtered(lambda x: x.flag == 'manual')
wizard._js_action_mount_line_in_edit(line.index)
line.currency_id = self.currency_data['currency']
wizard._line_value_changed_currency_id(line)
self.assertRecordValues(wizard.line_ids, [
# pylint: disable=C0326
{'flag': 'liquidity', 'amount_currency': 1800.0, 'currency_id': self.company_data['currency'].id, 'balance': 1800.0},
{'flag': 'manual', 'amount_currency': -4200.0, 'currency_id': self.currency_data['currency'].id, 'balance': -2100.0},
{'flag': 'auto_balance', 'amount_currency': 1050.0, 'currency_id': self.currency_data_2['currency'].id, 'balance': 300.0},
])
# Custom balance.
line = wizard.line_ids.filtered(lambda x: x.flag == 'manual')
wizard._js_action_mount_line_in_edit(line.index)
line.balance = -1800.0
wizard._line_value_changed_balance(line)
self.assertRecordValues(wizard.line_ids, [
# pylint: disable=C0326
{'flag': 'liquidity', 'amount_currency': 1800.0, 'currency_id': self.company_data['currency'].id, 'balance': 1800.0},
{'flag': 'manual', 'amount_currency': -4200.0, 'currency_id': self.currency_data['currency'].id, 'balance': -1800.0},
])
def test_auto_reconcile_cron(self):
self.env['account.reconcile.model'].search([('company_id', '=', self.company_data['company'].id)]).unlink()
cron = self.env.ref('account_accountant.auto_reconcile_bank_statement_line')
self.env['ir.cron.trigger'].search([('cron_id', '=', cron.id)]).unlink()
st_line = self._create_st_line(1234.0, partner_id=self.partner_a.id, date='2017-01-01')
self.assertEqual(len(self.env['ir.cron.trigger'].search([('cron_id', '=', cron.id)])), 1)
self._create_invoice_line(
'out_invoice',
invoice_date='2017-01-01',
invoice_line_ids=[{'price_unit': 1234.0}],
)
rule = self.env['account.reconcile.model'].create({
'name': "test_auto_reconcile_cron",
'rule_type': 'writeoff_suggestion',
'auto_reconcile': False,
'line_ids': [Command.create({'account_id': self.account_revenue1.id})],
})
# The CRON is not doing anything since the model is not auto reconcile.
with freeze_time('2017-01-01'):
self.env['account.bank.statement.line']._cron_try_auto_reconcile_statement_lines()
self.assertRecordValues(st_line, [{'is_reconciled': False, 'cron_last_check': False}])
self.assertEqual(len(self.env['ir.cron.trigger'].search([('cron_id', '=', cron.id)])), 1)
rule.auto_reconcile = True
# The CRON don't consider old statement lines.
with freeze_time('2017-06-01'):
self.env['account.bank.statement.line']._cron_try_auto_reconcile_statement_lines()
self.assertRecordValues(st_line, [{'is_reconciled': False, 'cron_last_check': False}])
self.assertEqual(len(self.env['ir.cron.trigger'].search([('cron_id', '=', cron.id)])), 1)
# The CRON will auto-reconcile the line.
with freeze_time('2017-01-02'):
self.env['account.bank.statement.line']._cron_try_auto_reconcile_statement_lines()
self.assertRecordValues(st_line, [{'is_reconciled': True, 'cron_last_check': fields.Datetime.from_string('2017-01-02 00:00:00')}])
self.assertEqual(len(self.env['ir.cron.trigger'].search([('cron_id', '=', cron.id)])), 1)
st_line1 = self._create_st_line(1234.0, partner_id=self.partner_a.id, date='2018-01-01')
self.assertEqual(len(self.env['ir.cron.trigger'].search([('cron_id', '=', cron.id)])), 2)
self._create_invoice_line(
'out_invoice',
invoice_date='2018-01-01',
invoice_line_ids=[{'price_unit': 1234.0}],
)
st_line2 = self._create_st_line(1234.0, partner_id=self.partner_a.id, date='2018-01-01')
self.assertEqual(len(self.env['ir.cron.trigger'].search([('cron_id', '=', cron.id)])), 3)
self._create_invoice_line(
'out_invoice',
invoice_date='2018-01-01',
invoice_line_ids=[{'price_unit': 1234.0}],
)
# Simulate the cron already tried to process 'st_line1' before.
with freeze_time('2017-12-31'):
st_line1.cron_last_check = fields.Datetime.now()
# The statement line with no 'cron_last_check' must be processed before others.
with freeze_time('2018-01-02'):
self.env['account.bank.statement.line']._cron_try_auto_reconcile_statement_lines(batch_size=1)
self.assertRecordValues(st_line1 + st_line2, [
{'is_reconciled': False, 'cron_last_check': fields.Datetime.from_string('2017-12-31 00:00:00')},
{'is_reconciled': True, 'cron_last_check': fields.Datetime.from_string('2018-01-02 00:00:00')},
])
self.assertEqual(len(self.env['ir.cron.trigger'].search([('cron_id', '=', cron.id)])), 4)
with freeze_time('2018-01-03'):
self.env['account.bank.statement.line']._cron_try_auto_reconcile_statement_lines(batch_size=1)
self.assertRecordValues(st_line1, [{'is_reconciled': True, 'cron_last_check': fields.Datetime.from_string('2018-01-03 00:00:00')}])
self.assertEqual(len(self.env['ir.cron.trigger'].search([('cron_id', '=', cron.id)])), 4)
st_line3 = self._create_st_line(1234.0, date='2018-01-01')
self.assertEqual(len(self.env['ir.cron.trigger'].search([('cron_id', '=', cron.id)])), 5)
self._create_invoice_line(
'out_invoice',
invoice_date='2018-01-01',
invoice_line_ids=[{'price_unit': 1234.0}],
)
st_line4 = self._create_st_line(1234.0, date='2018-01-01')
self.assertEqual(len(self.env['ir.cron.trigger'].search([('cron_id', '=', cron.id)])), 6)
self._create_invoice_line(
'out_invoice',
invoice_date='2018-01-01',
invoice_line_ids=[{'price_unit': 1234.0}],
)
# Make sure the CRON is no longer applicable.
rule.match_partner = True
rule.match_partner_ids = [Command.set(self.partner_a.ids)]
with freeze_time('2018-01-01'):
self.env['account.bank.statement.line']._cron_try_auto_reconcile_statement_lines(batch_size=1)
self.assertRecordValues(st_line3 + st_line4, [
{'is_reconciled': False, 'cron_last_check': fields.Datetime.from_string('2018-01-01 00:00:00')},
{'is_reconciled': False, 'cron_last_check': False},
])
self.assertEqual(len(self.env['ir.cron.trigger'].search([('cron_id', '=', cron.id)])), 7)
# Make sure the statement lines are reconciled by the cron in the right order.
self.assertRecordValues(st_line3 + st_line4, [
{'is_reconciled': False, 'cron_last_check': fields.Datetime.from_string('2018-01-01 00:00:00')},
{'is_reconciled': False, 'cron_last_check': False},
])
# st_line4 is processed because cron_last_check is null.
with freeze_time('2018-01-02'):
self.env['account.bank.statement.line']._cron_try_auto_reconcile_statement_lines(batch_size=1)
self.assertRecordValues(st_line3 + st_line4, [
{'is_reconciled': False, 'cron_last_check': fields.Datetime.from_string('2018-01-01 00:00:00')},
{'is_reconciled': False, 'cron_last_check': fields.Datetime.from_string('2018-01-02 00:00:00')},
])
self.assertEqual(len(self.env['ir.cron.trigger'].search([('cron_id', '=', cron.id)])), 7)
# st_line3 is processed because it has the oldest cron_last_check.
with freeze_time('2018-01-03'):
self.env['account.bank.statement.line']._cron_try_auto_reconcile_statement_lines(batch_size=1)
self.assertRecordValues(st_line3 + st_line4, [
{'is_reconciled': False, 'cron_last_check': fields.Datetime.from_string('2018-01-03 00:00:00')},
{'is_reconciled': False, 'cron_last_check': fields.Datetime.from_string('2018-01-02 00:00:00')},
])
self.assertEqual(len(self.env['ir.cron.trigger'].search([('cron_id', '=', cron.id)])), 7)
def test_duplicate_amls_constraint(self):
st_line = self._create_st_line(1000.0)
inv_line = self._create_invoice_line(
'out_invoice',
invoice_line_ids=[{'price_unit': 1000.0}],
)
wizard = self.env['bank.rec.widget'].with_context(default_st_line_id=st_line.id).new({})
wizard._action_add_new_amls(inv_line)
self.assertTrue(len(wizard.line_ids), 2)
wizard._action_add_new_amls(inv_line)
self.assertTrue(len(wizard.line_ids), 2)
@freeze_time('2017-01-01')
def test_reconcile_model_with_payment_tolerance(self):
self.env['account.reconcile.model'].search([('company_id', '=', self.company_data['company'].id)]).unlink()
invoice_line = self._create_invoice_line(
'out_invoice',
invoice_date='2017-01-01',
invoice_line_ids=[{'price_unit': 1000.0}],
)
st_line = self._create_st_line(998.0, partner_id=self.partner_a.id, date='2017-01-01', payment_ref=invoice_line.move_id.name)
rule = self.env['account.reconcile.model'].create({
'name': "test_reconcile_model_with_payment_tolerance",
'rule_type': 'invoice_matching',
'allow_payment_tolerance': True,
'payment_tolerance_type': 'percentage',
'payment_tolerance_param': 2.0,
'line_ids': [Command.create({'account_id': self.account_revenue1.id})],
})
wizard = self.env['bank.rec.widget'].with_context(default_st_line_id=st_line.id).new({})
wizard._action_trigger_matching_rules()
self.assertRecordValues(wizard.line_ids, [
# pylint: disable=C0326
{'flag': 'liquidity', 'balance': 998.0, 'reconcile_model_id': False},
{'flag': 'new_aml', 'balance': -1000.0, 'reconcile_model_id': rule.id},
{'flag': 'manual', 'balance': 2.0, 'reconcile_model_id': rule.id},
])
def test_early_payment_included_multi_currency(self):
self.env['account.reconcile.model'].search([('company_id', '=', self.company_data['company'].id)]).unlink()
self.early_payment_term.early_pay_discount_computation = 'included'
income_exchange_account = self.env.company.income_currency_exchange_account_id
expense_exchange_account = self.env.company.expense_currency_exchange_account_id
inv_line1_with_epd = self._create_invoice_line(
'out_invoice',
currency_id=self.currency_data_2['currency'].id,
partner_id=self.partner_a.id,
invoice_payment_term_id=self.early_payment_term.id,
invoice_date='2016-12-01',
invoice_line_ids=[
{
'price_unit': 4800.0,
'account_id': self.account_revenue1.id,
'tax_ids': [Command.set(self.company_data['default_tax_sale'].ids)],
},
{
'price_unit': 9600.0,
'account_id': self.account_revenue2.id,
'tax_ids': [Command.set(self.company_data['default_tax_sale'].ids)],
},
],
)
inv_line1_with_epd_rec_lines = inv_line1_with_epd.move_id.line_ids\
.filtered(lambda x: x.account_type == 'asset_receivable')\
.sorted(lambda x: x.discount_date or x.date_maturity)
self.assertRecordValues(
inv_line1_with_epd_rec_lines,
[
{
'amount_currency': 16560.0,
'balance': 2760.0,
'discount_amount_currency': 14904.0,
'discount_balance': 2484.0,
'discount_date': fields.Date.from_string('2016-12-11'),
'date_maturity': fields.Date.from_string('2016-12-21'),
},
],
)
inv_line2_with_epd = self._create_invoice_line(
'out_invoice',
currency_id=self.currency_data_2['currency'].id,
partner_id=self.partner_a.id,
invoice_payment_term_id=self.early_payment_term.id,
invoice_date='2017-01-20',
invoice_line_ids=[
{
'price_unit': 480.0,
'account_id': self.account_revenue1.id,
'tax_ids': [Command.set(self.company_data['default_tax_sale'].ids)],
},
{
'price_unit': 960.0,
'account_id': self.account_revenue2.id,
'tax_ids': [Command.set(self.company_data['default_tax_sale'].ids)],
},
],
)
inv_line2_with_epd_rec_lines = inv_line2_with_epd.move_id.line_ids\
.filtered(lambda x: x.account_type == 'asset_receivable')\
.sorted(lambda x: x.discount_date or x.date_maturity)
self.assertRecordValues(
inv_line2_with_epd_rec_lines,
[
{
'amount_currency': 1656.0,
'balance': 414.0,
'discount_amount_currency': 1490.4,
'discount_balance': 372.6,
'discount_date': fields.Date.from_string('2017-01-30'),
'date_maturity': fields.Date.from_string('2017-02-09'),
},
],
)
# inv1: 16560.0 (no epd)
# inv2: 1490.4 (epd)
st_line = self._create_st_line(
4512.0, # instead of 4512.6 (rate 1:4)
date='2017-01-04',
foreign_currency_id=self.currency_data_2['currency'].id,
amount_currency=18050.4,
)
wizard = self.env['bank.rec.widget'].with_context(default_st_line_id=st_line.id).new({})
# Add all lines from the first invoice plus the first one from the second one.
wizard._action_add_new_amls(inv_line1_with_epd_rec_lines + inv_line2_with_epd_rec_lines)
liquidity_acc = st_line.journal_id.default_account_id
receivable_acc = self.company_data['default_account_receivable']
early_pay_acc = self.env.company.account_journal_early_pay_discount_loss_account_id
tax_acc = self.company_data['default_tax_sale'].invoice_repartition_line_ids.account_id
self.assertRecordValues(wizard.line_ids, [
# pylint: disable=C0326
{'flag': 'liquidity', 'amount_currency': 4512.0, 'balance': 4512.0, 'account_id': liquidity_acc.id},
{'flag': 'new_aml', 'amount_currency': -16560.0, 'balance': -2760.0, 'account_id': receivable_acc.id},
{'flag': 'exchange_diff', 'amount_currency': 0.0, 'balance': -1379.45, 'account_id': income_exchange_account.id},
{'flag': 'new_aml', 'amount_currency': -1656.0, 'balance': -414.0, 'account_id': receivable_acc.id},
{'flag': 'exchange_diff', 'amount_currency': 0.0, 'balance': 0.06, 'account_id': expense_exchange_account.id},
{'flag': 'early_payment', 'amount_currency': 144.0, 'balance': 36.0, 'account_id': early_pay_acc.id},
{'flag': 'early_payment', 'amount_currency': 21.6, 'balance': 5.4, 'account_id': tax_acc.id},
{'flag': 'early_payment', 'amount_currency': 0.0, 'balance': -0.01, 'account_id': income_exchange_account.id},
])
def test_early_payment_excluded_multi_currency(self):
self.env['account.reconcile.model'].search([('company_id', '=', self.company_data['company'].id)]).unlink()
self.early_payment_term.early_pay_discount_computation = 'excluded'
income_exchange_account = self.env.company.income_currency_exchange_account_id
expense_exchange_account = self.env.company.expense_currency_exchange_account_id
inv_line1_with_epd = self._create_invoice_line(
'out_invoice',
currency_id=self.currency_data_2['currency'].id,
partner_id=self.partner_a.id,
invoice_payment_term_id=self.early_payment_term.id,
invoice_date='2016-12-01',
invoice_line_ids=[
{
'price_unit': 4800.0,
'account_id': self.account_revenue1.id,
'tax_ids': [Command.set(self.company_data['default_tax_sale'].ids)],
},
{
'price_unit': 9600.0,
'account_id': self.account_revenue2.id,
'tax_ids': [Command.set(self.company_data['default_tax_sale'].ids)],
},
],
)
inv_line1_with_epd_rec_lines = inv_line1_with_epd.move_id.line_ids\
.filtered(lambda x: x.account_type == 'asset_receivable')\
.sorted(lambda x: x.discount_date or x.date_maturity)
self.assertRecordValues(
inv_line1_with_epd_rec_lines,
[
{
'amount_currency': 16560.0,
'balance': 2760.0,
'discount_amount_currency': 15120.0,
'discount_balance': 2520.0,
'discount_date': fields.Date.from_string('2016-12-11'),
'date_maturity': fields.Date.from_string('2016-12-21'),
},
],
)
inv_line2_with_epd = self._create_invoice_line(
'out_invoice',
currency_id=self.currency_data_2['currency'].id,
partner_id=self.partner_a.id,
invoice_payment_term_id=self.early_payment_term.id,
invoice_date='2017-01-20',
invoice_line_ids=[
{
'price_unit': 480.0,
'account_id': self.account_revenue1.id,
'tax_ids': [Command.set(self.company_data['default_tax_sale'].ids)],
},
{
'price_unit': 960.0,
'account_id': self.account_revenue2.id,
'tax_ids': [Command.set(self.company_data['default_tax_sale'].ids)],
},
],
)
inv_line2_with_epd_rec_lines = inv_line2_with_epd.move_id.line_ids\
.filtered(lambda x: x.account_type == 'asset_receivable')\
.sorted(lambda x: x.discount_date or x.date_maturity)
self.assertRecordValues(
inv_line2_with_epd_rec_lines,
[
{
'amount_currency': 1656.0,
'balance': 414.0,
'discount_amount_currency': 1512.0,
'discount_balance': 378.0,
'discount_date': fields.Date.from_string('2017-01-30'),
'date_maturity': fields.Date.from_string('2017-02-09'),
},
],
)
# inv1: 16560.0 (no epd)
# inv2: 1512.0 (epd)
st_line = self._create_st_line(
4515.0, # instead of 4518.0 (rate 1:4)
date='2017-01-04',
foreign_currency_id=self.currency_data_2['currency'].id,
amount_currency=18072.0,
)
wizard = self.env['bank.rec.widget'].with_context(default_st_line_id=st_line.id).new({})
# Add all lines from the first invoice plus the first one from the second one.
wizard._action_add_new_amls(inv_line1_with_epd_rec_lines + inv_line2_with_epd_rec_lines[:2])
liquidity_acc = st_line.journal_id.default_account_id
receivable_acc = self.company_data['default_account_receivable']
early_pay_acc = self.env.company.account_journal_early_pay_discount_loss_account_id
self.assertRecordValues(wizard.line_ids, [
# pylint: disable=C0326
{'flag': 'liquidity', 'amount_currency': 4515.0, 'balance': 4515.0, 'account_id': liquidity_acc.id},
{'flag': 'new_aml', 'amount_currency': -16560.0, 'balance': -2760.0, 'account_id': receivable_acc.id},
{'flag': 'exchange_diff', 'amount_currency': 0.0, 'balance': -1377.25, 'account_id': income_exchange_account.id},
{'flag': 'new_aml', 'amount_currency': -1656.0, 'balance': -414.0, 'account_id': receivable_acc.id},
{'flag': 'exchange_diff', 'amount_currency': 0.0, 'balance': 0.27, 'account_id': expense_exchange_account.id},
{'flag': 'early_payment', 'amount_currency': 144.0, 'balance': 36.0, 'account_id': early_pay_acc.id},
{'flag': 'early_payment', 'amount_currency': 0.0, 'balance': -0.02, 'account_id': income_exchange_account.id},
])
def test_early_payment_mixed_multi_currency(self):
self.env['account.reconcile.model'].search([('company_id', '=', self.company_data['company'].id)]).unlink()
self.early_payment_term.early_pay_discount_computation = 'mixed'
income_exchange_account = self.env.company.income_currency_exchange_account_id
expense_exchange_account = self.env.company.expense_currency_exchange_account_id
inv_line1_with_epd = self._create_invoice_line(
'out_invoice',
currency_id=self.currency_data_2['currency'].id,
partner_id=self.partner_a.id,
invoice_payment_term_id=self.early_payment_term.id,
invoice_date='2016-12-01',
invoice_line_ids=[
{
'price_unit': 4800.0,
'account_id': self.account_revenue1.id,
'tax_ids': [Command.set(self.company_data['default_tax_sale'].ids)],
},
{
'price_unit': 9600.0,
'account_id': self.account_revenue2.id,
'tax_ids': [Command.set(self.company_data['default_tax_sale'].ids)],
},
],
)
inv_line1_with_epd_rec_lines = inv_line1_with_epd.move_id.line_ids\
.filtered(lambda x: x.account_type == 'asset_receivable')\
.sorted(lambda x: x.discount_date or x.date_maturity)
self.assertRecordValues(
inv_line1_with_epd_rec_lines,
[
{
'amount_currency': 16344.0,
'balance': 2724.0,
'discount_amount_currency': 14904.0,
'discount_balance': 2484.0,
'discount_date': fields.Date.from_string('2016-12-11'),
'date_maturity': fields.Date.from_string('2016-12-21'),
},
],
)
inv_line2_with_epd = self._create_invoice_line(
'out_invoice',
currency_id=self.currency_data_2['currency'].id,
partner_id=self.partner_a.id,
invoice_payment_term_id=self.early_payment_term.id,
invoice_date='2017-01-20',
invoice_line_ids=[
{
'price_unit': 480.0,
'account_id': self.account_revenue1.id,
'tax_ids': [Command.set(self.company_data['default_tax_sale'].ids)],
},
{
'price_unit': 960.0,
'account_id': self.account_revenue2.id,
'tax_ids': [Command.set(self.company_data['default_tax_sale'].ids)],
},
],
)
inv_line2_with_epd_rec_lines = inv_line2_with_epd.move_id.line_ids\
.filtered(lambda x: x.account_type == 'asset_receivable')\
.sorted(lambda x: x.discount_date or x.date_maturity)
self.assertRecordValues(
inv_line2_with_epd_rec_lines,
[
{
'amount_currency': 1634.4,
'balance': 408.6,
'discount_amount_currency': 1490.4,
'discount_balance': 372.6,
'discount_date': fields.Date.from_string('2017-01-30'),
'date_maturity': fields.Date.from_string('2017-02-09'),
},
],
)
# inv1: 16344.0 (no epd)
# inv2: 1490.4 (epd)
st_line = self._create_st_line(
4458.0, # instead of 4458.6 (rate 1:4)
date='2017-01-04',
foreign_currency_id=self.currency_data_2['currency'].id,
amount_currency=17834.4,
)
wizard = self.env['bank.rec.widget'].with_context(default_st_line_id=st_line.id).new({})
# Add all lines from the first invoice plus the first one from the second one.
wizard._action_add_new_amls(inv_line1_with_epd_rec_lines + inv_line2_with_epd_rec_lines[:2])
liquidity_acc = st_line.journal_id.default_account_id
receivable_acc = self.company_data['default_account_receivable']
early_pay_acc = self.env.company.account_journal_early_pay_discount_loss_account_id
self.assertRecordValues(wizard.line_ids, [
# pylint: disable=C0326
{'flag': 'liquidity', 'amount_currency': 4458.0, 'balance': 4458.0, 'account_id': liquidity_acc.id},
{'flag': 'new_aml', 'amount_currency': -16344.0, 'balance': -2724.0, 'account_id': receivable_acc.id},
{'flag': 'exchange_diff', 'amount_currency': 0.0, 'balance': -1361.45, 'account_id': income_exchange_account.id},
{'flag': 'new_aml', 'amount_currency': -1634.4, 'balance': -408.6, 'account_id': receivable_acc.id},
{'flag': 'exchange_diff', 'amount_currency': 0.0, 'balance': 0.05, 'account_id': expense_exchange_account.id},
{'flag': 'early_payment', 'amount_currency': 144.0, 'balance': 36.0, 'account_id': early_pay_acc.id},
])
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': [
# pylint: disable=bad-whitespace
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': [
# pylint: disable=bad-whitespace
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',
)
wizard = self.env['bank.rec.widget'].with_context(default_st_line_id=st_line.id).new({})
wizard._action_add_new_amls(bill.line_ids.filtered(lambda x: x.account_type == 'liability_payable'))
wizard._action_validate()
self.assertRecordValues(st_line.line_ids.sorted('balance'), [
# pylint: disable=bad-whitespace
{'amount_currency': -980.0, 'tax_ids': [], 'tax_tag_ids': [], 'tax_tag_invert': False},
{'amount_currency': -20.0, 'tax_ids': intracomm_tax.ids, 'tax_tag_ids': tax_tags[3].ids, 'tax_tag_invert': True},
{'amount_currency': -4.0, 'tax_ids': [], 'tax_tag_ids': tax_tags[4].ids, 'tax_tag_invert': True},
{'amount_currency': 4.0, 'tax_ids': [], 'tax_tag_ids': tax_tags[5].ids, 'tax_tag_invert': True},
{'amount_currency': 1000.0, 'tax_ids': [], 'tax_tag_ids': [], 'tax_tag_invert': False},
])
def test_multi_currencies_with_custom_rate(self):
self.company_data['default_journal_bank'].currency_id = self.currency_data['currency']
st_line = self._create_st_line(1200.0) # rate 1:2
self.assertRecordValues(st_line.move_id.line_ids, [
# pylint: disable=C0326
{'amount_currency': 1200.0, 'balance': 600.0},
{'amount_currency': -1200.0, 'balance': -600.0},
])
# invoice with currency_data and rate 1:2
invoice_line1 = self._create_invoice_line(
'out_invoice',
currency_id=self.currency_data['currency'].id,
invoice_date='2017-01-01',
invoice_line_ids=[{'price_unit': 300.0}], # = 150 USD
)
# Remove all rates.
self.currency_data['rates'].unlink()
wizard = self.env['bank.rec.widget'].with_context(default_st_line_id=st_line.id).new({})
self.assertRecordValues(wizard.line_ids, [
# pylint: disable=C0326
{'flag': 'liquidity', 'amount_currency': 1200.0, 'balance': 600.0},
{'flag': 'auto_balance', 'amount_currency': -1200.0, 'balance': -600.0},
])
# invoice with currency_data_2 and rate 1:6
invoice_line2 = self._create_invoice_line(
'out_invoice',
currency_id=self.currency_data_2['currency'].id,
invoice_date='2016-01-01',
invoice_line_ids=[{'price_unit': 600.0}], # = 100 USD
)
# invoice with currency_data_2 and rate 1:4
invoice_line3 = self._create_invoice_line(
'out_invoice',
currency_id=self.currency_data_2['currency'].id,
invoice_date='2017-01-01',
invoice_line_ids=[{'price_unit': 400.0}], # = 100 USD
)
# Remove all rates.
self.currency_data_2['rates'].unlink()
# Ensure no conversion rate has been made.
wizard._action_add_new_amls(invoice_line1 + invoice_line2 + invoice_line3)
self.assertRecordValues(wizard.line_ids, [
# pylint: disable=C0326
{'flag': 'liquidity', 'amount_currency': 1200.0, 'balance': 600.0},
{'flag': 'new_aml', 'amount_currency': -300.0, 'balance': -150.0},
{'flag': 'new_aml', 'amount_currency': -600.0, 'balance': -100.0},
{'flag': 'new_aml', 'amount_currency': -400.0, 'balance': -100.0},
{'flag': 'auto_balance', 'amount_currency': -500.0, 'balance': -250.0},
])
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.currency_data['currency'].id,
amount_currency=3600.0,
)
inv1 = self._create_invoice_line(
'out_invoice',
currency_id=self.currency_data['currency'].id,
invoice_date='2016-01-01',
invoice_line_ids=[{'price_unit': 2400.0}],
)
inv2 = self._create_invoice_line(
'out_invoice',
currency_id=self.currency_data['currency'].id,
invoice_date='2016-01-01',
invoice_line_ids=[{'price_unit': 2400.0}],
)
refund = self._create_invoice_line(
'out_refund',
currency_id=self.currency_data['currency'].id,
invoice_date='2016-01-01',
invoice_line_ids=[{'price_unit': 1200.0}],
)
wizard = self.env['bank.rec.widget'].with_context(default_st_line_id=st_line.id).new({})
wizard._action_add_new_amls(inv1 + inv2)
self.assertRecordValues(wizard.line_ids, [
# pylint: disable=C0326
{'flag': 'liquidity', 'amount_currency': 1800.0, 'balance': 1800.0},
{'flag': 'new_aml', 'amount_currency': -2400.0, 'balance': -800.0},
{'flag': 'exchange_diff', 'amount_currency': 0.0, 'balance': -400.0},
{'flag': 'new_aml', 'amount_currency': -1200.0, 'balance': -400.0},
{'flag': 'exchange_diff', 'amount_currency': 0.0, 'balance': -200.0},
])
wizard._action_add_new_amls(refund)
self.assertRecordValues(wizard.line_ids, [
# pylint: disable=C0326
{'flag': 'liquidity', 'amount_currency': 1800.0, 'balance': 1800.0},
{'flag': 'new_aml', 'amount_currency': -2400.0, 'balance': -800.0},
{'flag': 'exchange_diff', 'amount_currency': 0.0, 'balance': -400.0},
{'flag': 'new_aml', 'amount_currency': -2400.0, 'balance': -800.0},
{'flag': 'exchange_diff', 'amount_currency': 0.0, 'balance': -400.0},
{'flag': 'new_aml', 'amount_currency': 1200.0, 'balance': 400.0},
{'flag': 'exchange_diff', 'amount_currency': 0.0, 'balance': 200.0},
])
def test_auto_reconcile_cron_with_time_limit(self):
self.env['account.reconcile.model'].search([('company_id', '=', self.company_data['company'].id)]).unlink()
cron = self.env.ref('account_accountant.auto_reconcile_bank_statement_line')
self.env['ir.cron.trigger'].search([('cron_id', '=', cron.id)]).unlink()
st_line1 = self._create_st_line(1234.0, partner_id=self.partner_a.id, date='2017-01-01')
self.assertEqual(len(self.env['ir.cron.trigger'].search([('cron_id', '=', cron.id)])), 1)
st_line2 = self._create_st_line(5678.0, partner_id=self.partner_a.id, date='2017-01-02')
self.assertEqual(len(self.env['ir.cron.trigger'].search([('cron_id', '=', cron.id)])), 2)
self._create_invoice_line(
'out_invoice',
invoice_date='2017-01-01',
invoice_line_ids=[{'price_unit': 1234.0}],
)
self._create_invoice_line(
'out_invoice',
invoice_date='2017-01-01',
invoice_line_ids=[{'price_unit': 5678.0}],
)
self.env['account.reconcile.model'].create({
'name': "test_auto_reconcile_cron_with_time_limit",
'rule_type': 'writeoff_suggestion',
'auto_reconcile': True,
'line_ids': [Command.create({'account_id': self.account_revenue1.id})],
})
with freeze_time('2017-01-01 00:00:00') as frozen_time:
def datetime_now_override():
frozen_time.tick()
return frozen_time()
with patch('odoo.fields.Datetime.now', side_effect=datetime_now_override):
# we simulate that the time limit is reached after first loop
self.env['account.bank.statement.line']._cron_try_auto_reconcile_statement_lines(limit_time=1)
# after first loop, only one statement should be reconciled
self.assertRecordValues(st_line1, [{'is_reconciled': True, 'cron_last_check': fields.Datetime.from_string('2017-01-01 00:00:01')}])
# the other one should be in queue for regular cron tigger
self.assertRecordValues(st_line2, [{'is_reconciled': False, 'cron_last_check': False}])
self.assertEqual(len(self.env['ir.cron.trigger'].search([('cron_id', '=', cron.id)])), 3)
def test_auto_reconcile_cron_with_provided_statements_lines(self):
self.env['account.reconcile.model'].search([('company_id', '=', self.company_data['company'].id)]).unlink()
st_line1 = self._create_st_line(1234.0, partner_id=self.partner_a.id, date='2017-01-01')
st_line2 = self._create_st_line(5678.0, partner_id=self.partner_a.id, date='2017-01-02')
self._create_invoice_line(
'out_invoice',
invoice_date='2017-01-01',
invoice_line_ids=[{'price_unit': 1234.0}],
)
self._create_invoice_line(
'out_invoice',
invoice_date='2017-01-01',
invoice_line_ids=[{'price_unit': 5678.0}],
)
self.env['account.reconcile.model'].create({
'name': "test_auto_reconcile_cron_with_time_limit",
'rule_type': 'writeoff_suggestion',
'auto_reconcile': True,
'line_ids': [Command.create({'account_id': self.account_revenue1.id})],
})
with freeze_time('2017-01-01 00:00:00'):
# we call auto reconcile on st_lines1 **only**
st_line1._cron_try_auto_reconcile_statement_lines()
self.assertRecordValues(st_line1, [{'is_reconciled': True, 'cron_last_check': fields.Datetime.from_string('2017-01-01 00:00:00')}])
self.assertRecordValues(st_line2, [{'is_reconciled': False, 'cron_last_check': False}])
@freeze_time('2019-01-01')
def test_button_apply_reco_model(self):
st_line = self._create_st_line(-1000.0, partner_id=self.partner_a.id)
inv_line = self._create_invoice_line(
'in_invoice',
invoice_date=st_line.date,
invoice_line_ids=[{'price_unit': 980.0}],
)
reco_model = self.env['account.reconcile.model'].create({
'name': "test_apply_taxes_with_reco_model",
'rule_type': 'writeoff_button',
'line_ids': [Command.create({
'account_id': self.account_revenue1.copy().id,
'label': 'Bank Fees'
})],
})
wizard = self.env['bank.rec.widget'].with_context(default_st_line_id=st_line.id).new({})
wizard._action_trigger_matching_rules()
self.assertRecordValues(wizard.line_ids, [
# pylint: disable=C0326
{'flag': 'liquidity', 'account_id': st_line.journal_id.default_account_id.id, 'balance': -1000.0},
{'flag': 'new_aml', 'account_id': inv_line.account_id.id, 'balance': 980.0},
{'flag': 'auto_balance', 'account_id': self.company_data['default_account_payable'].id, 'balance': 20.0},
])
wizard._action_select_reconcile_model(reco_model)
self.assertRecordValues(wizard.line_ids, [
# pylint: disable=C0326
{'flag': 'liquidity', 'account_id': st_line.journal_id.default_account_id.id, 'balance': -1000.0},
{'flag': 'new_aml', 'account_id': inv_line.account_id.id, 'balance': 980.0},
{'flag': 'manual', 'account_id': reco_model.line_ids[0].account_id.id, 'balance': 20.0},
])
def test_exchange_diff_on_partial_aml_multi_currency(self):
self.company_data['default_journal_bank'].currency_id = self.currency_data['currency']
st_line = self._create_st_line(-36000.0) # rate 1:2
inv_line = self._create_invoice_line(
'in_invoice',
invoice_date='2016-01-01', # rate 1:3
currency_id=self.currency_data['currency'].id,
invoice_line_ids=[{'price_unit': 38000.0}],
)
wizard = self.env['bank.rec.widget'].with_context(default_st_line_id=st_line.id).new({})
wizard._action_add_new_amls(inv_line)
self.assertRecordValues(wizard.line_ids, [
# pylint: disable=C0326
{'flag': 'liquidity', 'amount_currency': -36000.0, 'currency_id': self.currency_data['currency'].id, 'balance': -18000.0},
{'flag': 'new_aml', 'amount_currency': 36000.0, 'currency_id': self.currency_data['currency'].id, 'balance': 12000.0},
{'flag': 'exchange_diff', 'amount_currency': 0.0, 'currency_id': self.currency_data['currency'].id, 'balance': 6000.0},
])
def test_exchange_diff_on_partial_aml_multi_currency_close_amount(self):
self.currency_data['rates'].rate = 0.9839
self.company_data['default_journal_bank'].currency_id = self.currency_data['currency']
st_line = self._create_st_line(-37436.50)
self.assertRecordValues(st_line.line_ids, [
# pylint: disable=C0326
{'amount_currency': -37436.50, 'balance': -38049.09},
{'amount_currency': 37436.50, 'balance': 38049.09},
])
inv_line = self._create_invoice_line(
'in_invoice',
invoice_date=st_line.date,
currency_id=self.currency_data['currency'].id,
invoice_line_ids=[{'price_unit': 37436.52}],
)
self.assertRecordValues(inv_line, [{
'amount_currency': -37436.52,
'balance': -38049.11,
}])
wizard = self.env['bank.rec.widget'].with_context(default_st_line_id=st_line.id).new({})
wizard._action_add_new_amls(inv_line)
self.assertRecordValues(wizard.line_ids, [
# pylint: disable=C0326
{'flag': 'liquidity', 'amount_currency': -37436.50, 'currency_id': self.currency_data['currency'].id, 'balance': -38049.09},
{'flag': 'new_aml', 'amount_currency': 37436.50, 'currency_id': self.currency_data['currency'].id, 'balance': 38049.09},
])
def test_matching_zero_amount_misc_entry(self):
""" Check for division by zero with foreign currencies and some 0 making a broken rate. """
self.company_data['default_journal_bank'].currency_id = self.currency_data['currency']
st_line = self._create_st_line(0.0, amount_currency=10.0, foreign_currency_id=self.company_data['currency'].id)
entry = self.env['account.move'].create({
'date': '2019-01-01',
'line_ids': [
Command.create({
'account_id': self.company_data['default_account_receivable'].id,
'currency_id': self.currency_data['currency'].id,
'debit': 1.0,
'credit': 0.0,
}),
Command.create({
'account_id': self.company_data['default_account_revenue'].id,
'currency_id': self.currency_data['currency'].id,
'debit': 0.0,
'credit': 1.0,
}),
]
})
entry.action_post()
wizard = self.env['bank.rec.widget'].with_context(default_st_line_id=st_line.id).new({})
aml = entry.line_ids.filtered('debit')
wizard._action_add_new_amls(aml)
self.assertRecordValues(wizard.line_ids, [
# pylint: disable=C0326
{'flag': 'liquidity', 'balance': 10.0},
{'flag': 'new_aml', 'balance': -1.0},
{'flag': 'exchange_diff', 'balance': 1.0},
{'flag': 'auto_balance', 'balance': -10.0},
])
def test_amls_order_with_matching_amount(self):
""" AML's with a matching amount_residual should be displayed first when the order is not specified. """
foreign_st_line = self._create_st_line(
500.0,
date='2016-01-01',
foreign_currency_id=self.currency_data['currency'].id,
amount_currency=1500.0,
)
st_line = self._create_st_line(
66.66,
date='2016-01-01',
)
wizard = self.env['bank.rec.widget'].with_context(default_st_line_id=foreign_st_line.id).new({})
aml1_id = self._create_invoice_line(
'out_invoice',
invoice_date='2017-01-30',
invoice_line_ids=[{'price_unit': 1000.0}],
).id
aml2_id = self._create_invoice_line(
'out_invoice',
invoice_date='2017-01-29',
currency_id=self.currency_data['currency'].id,
invoice_line_ids=[{'price_unit': 1500.0}], # = 100 USD
).id
aml3_id = self._create_invoice_line(
'out_invoice',
invoice_date='2017-01-28',
invoice_line_ids=[{'price_unit': 500.0}],
).id
aml4_id = self._create_invoice_line(
'out_invoice',
invoice_date='2017-01-27',
invoice_line_ids=[{'price_unit': 55.55}], # = 55.550000000000004
).id
aml5_id = self._create_invoice_line(
'out_invoice',
invoice_date='2017-01-26',
invoice_line_ids=[{'price_unit': 66.66}],
).id
# Check the lines without the context key.
wizard._js_action_mount_st_line(foreign_st_line.id)
domain = wizard.return_todo_command['amls']['domain']
amls_list = self.env['account.move.line'].search_fetch(domain=domain, field_names=['id'])
self.assertEqual(
[x['id'] for x in amls_list],
[aml1_id, aml2_id, aml3_id, aml4_id, aml5_id],
)
# Check the lines with the context key.
suspense_line = wizard.line_ids.filtered(lambda l: l.flag == 'auto_balance')
amls_list = self.env['account.move.line']\
.with_context(preferred_aml_value=suspense_line.amount_currency * -1, preferred_aml_currency_id=suspense_line.currency_id.id)\
.search_fetch(domain=domain, field_names=['id'])
self.assertEqual(
[x['id'] for x in amls_list],
[aml2_id, aml1_id, aml3_id, aml4_id, aml5_id],
)
# Check the order with limits and offsets
amls_list = self.env['account.move.line']\
.with_context(preferred_aml_value=suspense_line.amount_currency * -1, preferred_aml_currency_id=suspense_line.currency_id.id)\
.search_fetch(domain=domain, field_names=['id'], limit=2)
self.assertEqual(
[x['id'] for x in amls_list],
[aml2_id, aml1_id],
)
amls_list = self.env['account.move.line']\
.with_context(preferred_aml_value=suspense_line.amount_currency * -1, preferred_aml_currency_id=suspense_line.currency_id.id)\
.search_fetch(domain=domain, field_names=['id'], offset=2, limit=3)
self.assertEqual(
[x['id'] for x in amls_list],
[aml3_id, aml4_id, aml5_id],
)
# Check rounding and new suspense line
wizard._js_action_mount_st_line(st_line.id)
suspense_line = wizard.line_ids.filtered(lambda l: l.flag == 'auto_balance')
amls_list = self.env['account.move.line']\
.with_context(preferred_aml_value=suspense_line.amount_currency * -1, preferred_aml_currency_id=suspense_line.currency_id.id)\
.search_fetch(domain=domain, field_names=['id'])
self.assertEqual(
[x['id'] for x in amls_list],
[aml5_id, aml1_id, aml2_id, aml3_id, aml4_id],
)
wizard._js_action_mount_line_in_edit(suspense_line.index)
suspense_line.balance = -11.11
wizard._line_value_changed_balance(suspense_line)
suspense_line = wizard.line_ids.filtered(lambda l: l.flag == 'auto_balance')
self.assertEqual(suspense_line.balance, -55.55)
self.env.cr.execute(f"""
UPDATE account_move_line SET amount_residual_currency = 55.550000001 WHERE id = {aml4_id};
""")
amls_list = self.env['account.move.line']\
.with_context(preferred_aml_value=55.550003, preferred_aml_currency_id=suspense_line.currency_id.id)\
.search_fetch(domain=domain, field_names=['id'])
self.assertEqual(
[x['id'] for x in amls_list],
[aml4_id, aml1_id, aml2_id, aml3_id, aml5_id],
)
# Check that context keys are not propagated
action = amls_list[0].action_open_business_doc()
self.assertFalse(action['context'].get('preferred_aml_value'))
@freeze_time('2023-12-25')
def test_analtyic_distribution_model_exchange_diff_line(self):
"""Test that the analytic distribution model is present on the exchange diff line."""
expense_exchange_account = self.env.company.expense_currency_exchange_account_id
analytic_plan = self.env['account.analytic.plan'].create({
'name': 'Plan 1',
'default_applicability': 'unavailable',
})
analytic_account_1 = self.env['account.analytic.account'].create({'name': 'Account 1', 'plan_id': analytic_plan.id})
analytic_account_2 = self.env['account.analytic.account'].create({'name': 'Account 1', 'plan_id': analytic_plan.id})
distribution_model = self.env['account.analytic.distribution.model'].create({
'account_prefix': expense_exchange_account.code,
'partner_id': self.partner_a.id,
'analytic_distribution': {analytic_account_1.id: 100},
})
# 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',
)
# 1800.0 comp_curr = 3600.0 foreign_curr in 2017 (rate 1:2)
inv_line = self._create_invoice_line(
'out_invoice',
currency_id=self.currency_data['currency'].id,
invoice_date='2017-01-01',
invoice_line_ids=[{'price_unit': 3600.0}],
)
wizard = self.env['bank.rec.widget'].with_context(default_st_line_id=st_line.id).new({})
wizard._action_add_new_amls(inv_line)
self.assertRecordValues(wizard.line_ids, [
# pylint: disable=C0326
{'flag': 'liquidity', 'amount_currency': 1200.0, 'currency_id': self.company_data['currency'].id, 'balance': 1200.0, 'analytic_distribution': False},
{'flag': 'new_aml', 'amount_currency': -3600.0, 'currency_id': self.currency_data['currency'].id, 'balance': -1800.0, 'analytic_distribution': False},
{'flag': 'exchange_diff', 'amount_currency': 0.0, 'currency_id': self.currency_data['currency'].id, 'balance': 600.0, 'analytic_distribution': distribution_model.analytic_distribution},
])
# Test that the analytic distribution is kept on the creation of the exchange diff move
new_distribution = {**distribution_model.analytic_distribution, str(analytic_account_2.id): 100}
line = wizard.line_ids.filtered(lambda x: x.flag == 'exchange_diff')
line.analytic_distribution = new_distribution
wizard._action_validate()
self.assertRecordValues(inv_line.matched_credit_ids.exchange_move_id.line_ids, [
{'analytic_distribution': False},
{'analytic_distribution': new_distribution},
])
def test_access_child_bank_with_user_set_on_child(self):
"""
Demo user with a Child Company as default company/allowed companies
should be able to access the Bank set on this same Child Company
"""
child_company = self.env['res.company'].create({
'name': 'Childest Company',
'parent_id': self.env.company.id,
})
child_bank_journal = self.env['account.journal'].create({
'name': 'Child Bank',
'type': 'bank',
'company_id': child_company.id,
})
self.user.write({
'company_ids': [Command.set(child_company.ids)],
'company_id': child_company.id,
'groups_id': [
Command.set(self.env.ref('account.group_account_user').ids),
]
})
res = self.env['bank.rec.widget'].with_user(self.user).collect_global_info_data(child_bank_journal.id)
self.assertTrue(res, "Journal should be accessible")
def test_collect_global_info_data_other_company_bank_journal_with_user_on_main_company(self):
""" The aim of this test is checking that a user who having
access to 2 companies will have values even when he's
calling collect_global_info_data function if
it's current company it's not the one on the journal
but is still available.
To do that, we add 2 companies to the user, and try to
call collect_global_info_data on the journal of the second
company, even if the main company it's the first one.
"""
self.user.write({
'company_ids': [Command.set((self.company_data['company'] + self.company_data_2['company']).ids)],
'company_id': self.company_data['company'].id,
})
result = self.env['bank.rec.widget'].with_user(self.user).collect_global_info_data(self.company_data_2['default_journal_bank'].id)
self.assertTrue(result['balance_amount'], "Balance amount shouldn't be False value")
def test_collect_global_info_data_non_existing_bank_journal(self):
""" The aim of this test is checking that we receive an empty
string when we call collect_global_info_data function
with a non-existing journal. This use case could happen
when we try to open the bank rec widget on a journal that
is not actually existing. As this function is callable by
rpc, this usecase could happen.
"""
result = self.env['bank.rec.widget'].with_user(self.user).collect_global_info_data(99999999)
self.assertEqual(result['balance_amount'], "", "If no value, the function should return an empty string")
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)
inv_line = self._create_invoice_line(
'out_invoice',
partner_id=partner.id,
invoice_line_ids=[{'price_unit': 100.0, 'tax_ids': []}],
)
wizard = self.env['bank.rec.widget'].with_context(default_st_line_id=st_line.id).new({})
wizard._action_add_new_amls(inv_line)
wizard._action_validate()
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_unreconciled_with_other_lines(self):
"""Test that other lines are shown in the widget if they exist."""
st_line = self._create_st_line(
1000.0,
date='2017-01-01',
)
# Edit the associated move to partially reconcile some of the suspense amount; i.e. we add another line
liquidity_line, suspense_line, other_line = st_line._seek_for_lines()
other_account = st_line.journal_id.company_id.default_cash_difference_income_account_id
self.assertFalse(other_line)
st_line.move_id.write({'line_ids': [
Command.create({'account_id': other_account.id, 'credit': 100.0}),
Command.update(suspense_line.id, {'credit': 900.0}),
]})
liquidity_line, suspense_line, other_line = st_line._seek_for_lines()
self.assertTrue(other_line)
# Check that the wizard displays the new line
wizard = self.env['bank.rec.widget'].with_context(default_st_line_id=st_line.id).new({})
self.assertRecordValues(wizard.line_ids, [
# pylint: disable=C0326
{'flag': 'liquidity', 'account_id': liquidity_line.account_id.id, 'amount_currency': 1000.0},
{'flag': 'aml', 'account_id': other_line.account_id.id, 'amount_currency': -100.0},
{'flag': 'auto_balance', 'account_id': suspense_line.account_id.id, 'amount_currency': -900.0},
])