348 lines
19 KiB
Python
348 lines
19 KiB
Python
# -*- coding: utf-8 -*-
|
|
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
|
from unittest.mock import MagicMock, patch
|
|
|
|
from odoo.addons.base.models.res_bank import sanitize_account_number
|
|
from odoo.addons.account_online_synchronization.tests.common import AccountOnlineSynchronizationCommon
|
|
from odoo.exceptions import RedirectWarning
|
|
from odoo.tests import tagged
|
|
from odoo import fields, Command
|
|
|
|
|
|
@tagged('post_install', '-at_install')
|
|
class TestSynchStatementCreation(AccountOnlineSynchronizationCommon):
|
|
|
|
@classmethod
|
|
def setUpClass(cls):
|
|
super().setUpClass()
|
|
|
|
cls.account = cls.env['account.account'].create({
|
|
'name': 'Fixed Asset Account',
|
|
'code': 'AA',
|
|
'account_type': 'asset_fixed',
|
|
})
|
|
|
|
# Tests
|
|
def test_creation_initial_sync_statement(self):
|
|
transactions = self._create_online_transactions(['2016-01-01', '2016-01-03'])
|
|
self.account_online_account.balance = 1000
|
|
self.BankStatementLine._online_sync_bank_statement(transactions, self.account_online_account)
|
|
statement_lines = self.env['account.bank.statement.line'].search([('journal_id', '=', self.euro_bank_journal.id)])
|
|
self.assertEqual(len(statement_lines), 2, "Should have created 2 statement lines for the transactions")
|
|
|
|
# Since there were no existing statement lines for the given journal, it should create an opening bank statement along one statement line.
|
|
statement = self.env['account.bank.statement'].search([('journal_id', '=', self.euro_bank_journal.id)])
|
|
self.assertEqual(len(statement.line_ids), 1, "Should have created an opening bank statement with exactly one statement line")
|
|
self.assertEqual(statement.balance_start, 980.0, "Opening Bank statement should have starting balance 980.0")
|
|
|
|
transactions = self._create_online_transactions(['2016-01-05'])
|
|
self.account_online_account.balance = 2000
|
|
self.BankStatementLine._online_sync_bank_statement(transactions, self.account_online_account)
|
|
created_st_lines = self.BankStatementLine.search([('journal_id', '=', self.euro_bank_journal.id)], order='internal_index asc')
|
|
# Verify that all imported bank transactions are converted into statement lines in chronological order and match the expected amounts and dates.
|
|
self.assertRecordValues(
|
|
created_st_lines,
|
|
[
|
|
{'date': fields.Date.from_string('2016-01-01'), 'amount': 10.0},
|
|
{'date': fields.Date.from_string('2016-01-03'), 'amount': 10.0},
|
|
{'date': fields.Date.from_string('2016-01-05'), 'amount': 10.0},
|
|
]
|
|
)
|
|
|
|
def test_creation_initial_sync_statement_bis(self):
|
|
transactions = self._create_online_transactions(['2016-01-01', '2016-01-03'])
|
|
self.account_online_account.balance = 20
|
|
self.BankStatementLine._online_sync_bank_statement(transactions, self.account_online_account)
|
|
# Since ending balance is 20$ and we only have 20$ of transactions and that it is the first statement
|
|
# it should NOT create a initial statement before this one
|
|
created_st_lines = self.BankStatementLine.search([('journal_id', '=', self.euro_bank_journal.id)], order='internal_index asc')
|
|
self.assertRecordValues(
|
|
created_st_lines,
|
|
[
|
|
{'date': fields.Date.from_string('2016-01-01'), 'amount': 10.0},
|
|
{'date': fields.Date.from_string('2016-01-03'), 'amount': 10.0},
|
|
]
|
|
)
|
|
|
|
def test_creation_initial_sync_statement_invert_sign(self):
|
|
self.account_online_account.balance = -20
|
|
self.account_online_account.inverse_transaction_sign = True
|
|
self.account_online_account.inverse_balance_sign = True
|
|
transactions = self._create_online_transactions(['2016-01-01', '2016-01-03'])
|
|
self.BankStatementLine._online_sync_bank_statement(transactions, self.account_online_account)
|
|
# Since ending balance is 1000$ and we only have 20$ of transactions and that it is the first statement
|
|
# it should create a statement before this one with the initial statement line
|
|
created_st_lines = self.BankStatementLine.search([('journal_id', '=', self.euro_bank_journal.id)], order='internal_index asc')
|
|
self.assertEqual(len(created_st_lines), 2, 'Should have created two bank statement lines for the synchronization')
|
|
transactions = self._create_online_transactions(['2016-01-05'])
|
|
self.account_online_account.balance = -30
|
|
self.BankStatementLine._online_sync_bank_statement(transactions, self.account_online_account)
|
|
created_st_lines = self.BankStatementLine.search([('journal_id', '=', self.euro_bank_journal.id)], order='internal_index asc')
|
|
self.assertRecordValues(
|
|
created_st_lines,
|
|
[
|
|
{'date': fields.Date.from_string('2016-01-01'), 'amount': -10.0},
|
|
{'date': fields.Date.from_string('2016-01-03'), 'amount': -10.0},
|
|
{'date': fields.Date.from_string('2016-01-05'), 'amount': -10.0},
|
|
]
|
|
)
|
|
|
|
@patch('odoo.addons.account_online_synchronization.models.account_online.AccountOnlineLink._fetch_transactions')
|
|
@patch('odoo.addons.account_online_synchronization.models.account_online.AccountOnlineLink._update_connection_status')
|
|
def test_automatic_journal_assignment(self, patched_update_connection_status, patched_fetch_transactions):
|
|
def create_online_account(name, link_id, iban, currency_id):
|
|
return self.env['account.online.account'].create({
|
|
'name': name,
|
|
'account_online_link_id': link_id,
|
|
'account_number': iban,
|
|
'currency_id' : currency_id,
|
|
})
|
|
|
|
def create_bank_account(account_number, partner_id):
|
|
return self.env['res.partner.bank'].create({
|
|
'acc_number': account_number,
|
|
'partner_id': partner_id,
|
|
})
|
|
|
|
def create_journal(name, journal_type, code, currency_id=False, bank_account_id=False):
|
|
return self.env['account.journal'].create({
|
|
'name': name,
|
|
'type': journal_type,
|
|
'code': code,
|
|
'currency_id': currency_id,
|
|
'bank_account_id': bank_account_id,
|
|
})
|
|
|
|
bank_account_1 = create_bank_account('BE48485444456727', self.company_data['company'].partner_id.id)
|
|
bank_account_2 = create_bank_account('BE23798242487491', self.company_data['company'].partner_id.id)
|
|
|
|
bank_journal_with_account_gol = create_journal('Bank with account', 'bank', 'BJWA1', self.other_currency.id)
|
|
bank_journal_with_account_usd = create_journal('Bank with account USD', 'bank', 'BJWA3', self.env.ref('base.USD').id, bank_account_2.id)
|
|
|
|
online_account_1 = create_online_account('OnlineAccount1', self.account_online_link.id, 'BE48485444456727', self.other_currency.id)
|
|
online_account_2 = create_online_account('OnlineAccount2', self.account_online_link.id, 'BE61954856342317', self.other_currency.id)
|
|
online_account_3 = create_online_account('OnlineAccount3', self.account_online_link.id, 'BE23798242487495', self.other_currency.id)
|
|
|
|
patched_fetch_transactions.return_value = True
|
|
patched_update_connection_status.return_value = {
|
|
'consent_expiring_date': None,
|
|
'is_payment_enabled': False,
|
|
'is_payment_activated': False,
|
|
}
|
|
|
|
account_link_journal_wizard = self.env['account.bank.selection'].create({'account_online_link_id': self.account_online_link.id})
|
|
account_link_journal_wizard.with_context(active_model='account.journal', active_id=bank_journal_with_account_gol.id).sync_now()
|
|
self.assertEqual(
|
|
online_account_1.id, bank_journal_with_account_gol.account_online_account_id.id,
|
|
"The wizard should have linked the online account to the journal with the same account."
|
|
)
|
|
self.assertEqual(bank_journal_with_account_gol.bank_account_id, bank_account_1, "Account should be set on the journal")
|
|
|
|
# Test with no context present, should create a new journal
|
|
previous_number = self.env['account.journal'].search_count([])
|
|
account_link_journal_wizard.selected_account = online_account_2
|
|
account_link_journal_wizard.sync_now()
|
|
actual_number = self.env['account.journal'].search_count([])
|
|
self.assertEqual(actual_number, previous_number+1, "should have created a new journal")
|
|
self.assertEqual(online_account_2.journal_ids.currency_id, self.other_currency)
|
|
self.assertEqual(online_account_2.journal_ids.bank_account_id.sanitized_acc_number, sanitize_account_number('BE61954856342317'))
|
|
|
|
# Test assigning to a journal in another currency
|
|
account_link_journal_wizard.selected_account = online_account_3
|
|
account_link_journal_wizard.with_context(active_model='account.journal', active_id=bank_journal_with_account_usd.id).sync_now()
|
|
self.assertEqual(online_account_3.id, bank_journal_with_account_usd.account_online_account_id.id)
|
|
self.assertEqual(bank_journal_with_account_usd.bank_account_id, bank_account_2, "Bank Account should not have changed")
|
|
self.assertEqual(bank_journal_with_account_usd.currency_id, self.other_currency, "Currency should have changed")
|
|
|
|
@patch('odoo.addons.account_online_synchronization.models.account_online.AccountOnlineLink._fetch_odoo_fin')
|
|
def test_fetch_transaction_date_start(self, patched_fetch):
|
|
""" This test verifies that the start_date params used when fetching transaction is correct """
|
|
patched_fetch.return_value = {'transactions': []}
|
|
# Since no transactions exists in db, we should fetch transactions without a starting_date
|
|
self.account_online_account._retrieve_transactions()
|
|
data = {
|
|
'start_date': False,
|
|
'account_id': False,
|
|
'last_transaction_identifier': False,
|
|
'currency_code': 'EUR',
|
|
'account_data': False,
|
|
'include_foreign_currency': True,
|
|
}
|
|
patched_fetch.assert_called_with('/proxy/v2/transactions/posted', data=data)
|
|
|
|
# No transaction exists in db but we have a value for last_sync on the online_account, we should use that date
|
|
self.account_online_account.last_sync = '2020-03-04'
|
|
data['start_date'] = '2020-03-04'
|
|
self.account_online_account._retrieve_transactions()
|
|
patched_fetch.assert_called_with('/proxy/v2/transactions/posted', data=data)
|
|
|
|
# We have transactions, we should use the date of the latest one instead of the last_sync date
|
|
transactions = self._create_online_transactions(['2016-01-01', '2016-01-03'])
|
|
self.BankStatementLine._online_sync_bank_statement(transactions, self.account_online_account)
|
|
self.account_online_account.last_sync = '2020-03-04'
|
|
data['start_date'] = '2016-01-03'
|
|
data['last_transaction_identifier'] = '2'
|
|
self.account_online_account._retrieve_transactions()
|
|
patched_fetch.assert_called_with('/proxy/v2/transactions/posted', data=data)
|
|
|
|
def test_multiple_transaction_identifier_fetched(self):
|
|
# Ensure that if we receive twice the same transaction within the same call, it won't be created twice
|
|
transactions = self._create_online_transactions(['2016-01-01', '2016-01-03'])
|
|
# Add first transactions to the list again
|
|
transactions.append(transactions[0])
|
|
self.BankStatementLine._online_sync_bank_statement(transactions, self.account_online_account)
|
|
bnk_stmt_lines = self.BankStatementLine.search([('online_transaction_identifier', '!=', False), ('journal_id', '=', self.euro_bank_journal.id)])
|
|
self.assertEqual(len(bnk_stmt_lines), 2, 'Should only have created two lines')
|
|
|
|
@patch('odoo.addons.account_online_synchronization.models.account_online.AccountOnlineLink._fetch_odoo_fin')
|
|
def test_fetch_transactions_reauth(self, patched_refresh):
|
|
patched_refresh.side_effect = [
|
|
{
|
|
'success': False,
|
|
'code': 300,
|
|
'data': {'mode': 'updateCredentials'},
|
|
},
|
|
{
|
|
'access_token': 'open_sesame',
|
|
},
|
|
]
|
|
self.account_online_account.account_online_link_id.state = 'connected'
|
|
res = self.account_online_account.account_online_link_id._fetch_transactions()
|
|
self.assertTrue('account_online_identifier' in res.get('params', {}).get('includeParam', {}))
|
|
|
|
def test_duplicate_transaction_date_amount_account(self):
|
|
""" This test verifies that the duplicate transaction wizard detects transactions with
|
|
same date, amount, account_number and currency
|
|
"""
|
|
# Create 2 groups of respectively 2 and 3 duplicate transactions
|
|
transactions = self._create_online_transactions([
|
|
'2024-01-01', '2024-01-01',
|
|
'2024-01-02', '2024-01-02', '2024-01-02',
|
|
])
|
|
bsls = self.BankStatementLine._online_sync_bank_statement(transactions, self.account_online_account)
|
|
self.env.flush_all() # _get_duplicate_transactions make sql request, must write to db
|
|
duplicate_transactions = self.euro_bank_journal._get_duplicate_transactions(
|
|
fields.Date.to_date('2000-01-01')
|
|
)
|
|
group_1 = bsls.filtered(lambda bsl: bsl.date == fields.Date.from_string('2024-01-01')).ids
|
|
group_2 = bsls.filtered(lambda bsl: bsl.date == fields.Date.from_string('2024-01-02')).ids
|
|
|
|
self.assertEqual(duplicate_transactions, [group_1, group_2])
|
|
|
|
# check has_duplicate_transactions
|
|
has_duplicate_transactions = self.euro_bank_journal._has_duplicate_transactions(
|
|
fields.Date.to_date('2000-01-01')
|
|
)
|
|
self.assertTrue(has_duplicate_transactions is True) # explicit check on bool type
|
|
|
|
def test_duplicate_transaction_online_transaction_identifier(self):
|
|
""" This test verifies that the duplicate transaction wizard detects transactions with
|
|
same online_transaction_identifier
|
|
"""
|
|
# Create transactions
|
|
transactions = self._create_online_transactions([
|
|
'2024-01-01', '2024-01-02', '2024-01-03', '2024-01-04', '2024-01-05'
|
|
])
|
|
bsls = self.BankStatementLine._online_sync_bank_statement(transactions, self.account_online_account)
|
|
|
|
group_1, group_2 = [], []
|
|
for bsl in bsls:
|
|
# have to update the online_transaction_identifier after to force duplicates
|
|
if bsl.payment_ref in ('transaction_1', 'transaction_2'):
|
|
group_1.append(bsl.id)
|
|
bsl.online_transaction_identifier = 'same_oti_1'
|
|
if bsl.payment_ref in ('transaction_3, transaction_4, transaction_5'):
|
|
group_2.append(bsl.id)
|
|
bsl.online_transaction_identifier = 'same_oti_2'
|
|
|
|
self.env.flush_all() # _get_duplicate_transactions make sql request, must write to db
|
|
duplicate_transactions = self.euro_bank_journal._get_duplicate_transactions(
|
|
fields.Date.to_date('2000-01-01')
|
|
)
|
|
self.assertEqual(duplicate_transactions, [group_1, group_2])
|
|
|
|
@patch('odoo.addons.account_online_synchronization.models.account_online.requests')
|
|
def test_fetch_receive_error_message(self, patched_request):
|
|
# We want to test that when we receive an error, a redirectWarning with the correct parameter is thrown
|
|
# However the method _log_information that we need to test for that is performing a rollback as it needs
|
|
# to save the message error on the record as well (so it rollback, save message, commit, raise error).
|
|
# So in order to test the method, we need to use a "test cursor".
|
|
mock_response = MagicMock()
|
|
mock_response.status_code = 200
|
|
mock_response.json.return_value = {
|
|
'error': {
|
|
'code': 400,
|
|
'message': 'Shit Happened',
|
|
'data': {
|
|
'exception_type': 'random',
|
|
'message': 'This kind of things can happen.',
|
|
'error_reference': 'abc123',
|
|
'provider_type': 'theonlyone',
|
|
'redirect_warning_url': 'odoo_support',
|
|
},
|
|
},
|
|
}
|
|
patched_request.post.return_value = mock_response
|
|
|
|
generated_url = 'https://www.odoo.com/help?stage=bank_sync&summary=Bank+sync+error+ref%3A+abc123+-+Provider%3A+theonlyone+-+Client+ID%3A+client_id_1&description=ClientID%3A+client_id_1%0AInstitution%3A+Test+Bank%0AError+Reference%3A+abc123%0AError+Message%3A+This+kind+of+things+can+happen.%0A'
|
|
return_act_url = {
|
|
'type': 'ir.actions.act_url',
|
|
'url': generated_url
|
|
}
|
|
body_generated_url = generated_url.replace('&', '&') #in post_message, & has been escaped to &
|
|
message_body = f"""<p>This kind of things can happen.
|
|
|
|
If you've already opened a ticket for this issue, don't report it again: a support agent will contact you shortly.<br>You can contact Odoo support <a href=\"{body_generated_url}\">Here</a></p>"""
|
|
|
|
# flush and clear everything for the new "transaction"
|
|
self.env.invalidate_all()
|
|
with self.enter_registry_test_mode(), self.env.registry.cursor() as test_cr:
|
|
test_env = self.env(cr=test_cr)
|
|
test_link_account = self.account_online_link.with_env(test_env)
|
|
test_link_account.state = 'connected'
|
|
|
|
# this hand-written self.assertRaises() does not roll back self.cr,
|
|
# which is necessary below to inspect the message being posted
|
|
try:
|
|
test_link_account._fetch_odoo_fin('/testthisurl')
|
|
except RedirectWarning as exception:
|
|
self.assertEqual(exception.args[0], "This kind of things can happen.\n\nIf you've already opened a ticket for this issue, don't report it again: a support agent will contact you shortly.")
|
|
self.assertEqual(exception.args[1], return_act_url)
|
|
self.assertEqual(exception.args[2], 'Report issue')
|
|
else:
|
|
self.fail("Expected RedirectWarning not raised")
|
|
self.assertEqual(test_link_account.message_ids[0].body, message_body)
|
|
|
|
def test_account_online_link_having_journal_ids(self):
|
|
""" This test verifies that the account online link object
|
|
has all the journal in the field journal_ids.
|
|
It's important to handle these journals because we need
|
|
them to add the consent expiring date.
|
|
"""
|
|
# Create a bank sync connection having 2 online accounts (with one journal connected for each account)
|
|
online_link = self.env['account.online.link'].create({
|
|
'name': 'My New Bank connection',
|
|
})
|
|
online_accounts = self.env['account.online.account'].create([
|
|
{
|
|
'name': 'Account 1',
|
|
'account_online_link_id': online_link.id,
|
|
'journal_ids': [Command.create({
|
|
'name': 'Account 1',
|
|
'code': 'BK1',
|
|
'type': 'bank',
|
|
})],
|
|
},
|
|
{
|
|
'name': 'Account 2',
|
|
'account_online_link_id': online_link.id,
|
|
'journal_ids': [Command.create({
|
|
'name': 'Account 2',
|
|
'code': 'BK2',
|
|
'type': 'bank',
|
|
})],
|
|
},
|
|
])
|
|
self.assertEqual(online_link.account_online_account_ids, online_accounts)
|
|
self.assertEqual(len(online_link.journal_ids), 2) # Our online link connections should have 2 journals.
|