diff --git a/addons/account_peppol/models/account_edi_proxy_user.py b/addons/account_peppol/models/account_edi_proxy_user.py index 9106775cb10..8edfcf55a1d 100644 --- a/addons/account_peppol/models/account_edi_proxy_user.py +++ b/addons/account_peppol/models/account_edi_proxy_user.py @@ -3,7 +3,7 @@ import logging -from odoo import _, fields, models, tools +from odoo import _, fields, models, modules, tools from odoo.exceptions import UserError from odoo.addons.account_edi_proxy_client.models.account_edi_proxy_user import AccountEdiProxyError @@ -34,7 +34,17 @@ class AccountEdiProxyClientUser(models.Model): and not self.active and not self.company_id.account_edi_proxy_client_ids.filtered(lambda u: u.proxy_type == 'peppol') ): - self.company_id.account_peppol_proxy_state = 'not_registered' + self.company_id.write({ + 'account_peppol_proxy_state': 'not_registered', + 'is_account_peppol_participant': False, + }) + self.env['ir.config_parameter'].set_param( + f'account_peppol.migration_key_{self.company_id.id}', + False + ) + # commit the above changes before raising below + if not tools.config['test_enable'] and not modules.module.current_test: + self.env.cr.commit() raise AccountEdiProxyError(e.code, e.message) return result diff --git a/addons/account_peppol/models/res_config_settings.py b/addons/account_peppol/models/res_config_settings.py index e459781679a..6a078fe992e 100644 --- a/addons/account_peppol/models/res_config_settings.py +++ b/addons/account_peppol/models/res_config_settings.py @@ -27,7 +27,12 @@ class ResConfigSettings(models.TransientModel): string="Warning", compute="_compute_account_peppol_endpoint_warning", ) - account_peppol_migration_key = fields.Char(related='account_peppol_edi_user.peppol_migration_key', readonly=False) + # to be changed in master to be a related field on res_company + account_peppol_migration_key = fields.Char( + compute="_compute_account_peppol_migration_key", + inverse="_inverse_account_peppol_migration_key", + readonly=False, + ) account_peppol_phone_number = fields.Char(related='company_id.account_peppol_phone_number', readonly=False) account_peppol_proxy_state = fields.Selection(related='company_id.account_peppol_proxy_state', readonly=False) account_peppol_purchase_journal_id = fields.Many2one(related='company_id.peppol_purchase_journal_id', readonly=False) @@ -54,7 +59,7 @@ class ResConfigSettings(models.TransientModel): def _call_peppol_proxy(self, endpoint, params=None, edi_user=None): if not edi_user: - edi_user = self.company_id.account_edi_proxy_client_ids[0] + edi_user = self.company_id.account_edi_proxy_client_ids.filtered(lambda u: u.proxy_type == 'peppol') params = params or {} try: @@ -103,6 +108,20 @@ class ResConfigSettings(models.TransientModel): config.account_peppol_endpoint_warning = _("The endpoint number might not be correct. " "Please check if you entered the right identification number.") + @api.depends('company_id') + def _compute_account_peppol_migration_key(self): + for config in self: + config.account_peppol_migration_key = self.env['ir.config_parameter'].get_param( + f'account_peppol.migration_key_{config.company_id.id}' + ) + + def _inverse_account_peppol_migration_key(self): + for config in self: + self.env['ir.config_parameter'].set_param( + f'account_peppol.migration_key_{config.company_id.id}', + config.account_peppol_migration_key + ) + # ------------------------------------------------------------------------- # BUSINESS ACTIONS # ------------------------------------------------------------------------- diff --git a/addons/account_peppol/tests/test_peppol_participant.py b/addons/account_peppol/tests/test_peppol_participant.py index 5c27e685861..106e86f5eb6 100644 --- a/addons/account_peppol/tests/test_peppol_participant.py +++ b/addons/account_peppol/tests/test_peppol_participant.py @@ -5,7 +5,7 @@ from freezegun import freeze_time from unittest.mock import Mock, patch from psycopg2 import IntegrityError -from odoo.exceptions import ValidationError +from odoo.exceptions import ValidationError, UserError from odoo.tests.common import tagged, TransactionCase from odoo.tools import mute_logger @@ -18,26 +18,64 @@ PDF_FILE_PATH = 'account_peppol/tests/assets/peppol_identification_test.pdf' @tagged('-at_install', 'post_install') class TestPeppolParticipant(TransactionCase): + def _get_participant_vals(self): + return { + 'is_account_peppol_participant': True, + 'account_peppol_eas': '9925', + 'account_peppol_endpoint': '0000000000', + 'account_peppol_phone_number': '+32483123456', + } + @contextmanager - def _patch_peppol_requests(self, reject=False): + def _patch_peppol_requests(self, reject=False, migrate_to=False, migrated_away=False): + responses = { + '/peppol/1/participant_status': { + 'result': { + 'peppol_state': 'active' if not reject else 'rejected', + } + }, + '/peppol/1/activate_participant': {'result': {}}, + '/account_edi/2/create_user': { + 'result': { + 'id_client': ID_CLIENT, + 'refresh_token': FAKE_UUID, + } + }, + '/peppol/1/send_verification_code': {'result': {}}, + '/peppol/1/update_user': {'result': {}}, + '/peppol/1/verify_phone_number': {'result': {}}, + '/peppol/1/migrate_peppol_registration': { + 'result': { + 'migration_key': 'test_key', + } + }, + } + def _mocked_post(url, *args, **kwargs): response = Mock() response.status_code = 200 - if url.endswith('api/peppol/1/participant_status'): - response.json = lambda: {'result': { - 'peppol_state': 'active' if not reject else 'rejected', - }} - elif url.endswith('/api/peppol/1/activate_participant'): - response.json = lambda: {'result': {}} - elif url.endswith('/iap/account_edi/2/create_user'): - response.json = lambda: {'result': { - 'id_client': ID_CLIENT, 'refresh_token': FAKE_UUID}} - elif url.endswith('/api/peppol/1/send_verification_code')\ - or url.endswith('/api/peppol/1/update_user')\ - or url.endswith('/api/peppol/1/verify_phone_number'): - response.json = lambda: {'result': {}} - else: + if ( + url.endswith('/api/peppol/1/activate_participant') + and migrate_to + and not kwargs['json']['params']['migration_key'] + ): + raise UserError('No migration key was provided') + + if migrated_away: + response.json = lambda: { + 'result': { + 'proxy_error': { + 'code': 'no_such_user', + 'message': 'The user does not exist on the proxy', + } + } + } + return response + + url = url.split('/api')[1] if 'iap' not in url else url.split('/iap')[1] + if url not in responses: raise Exception(f'Unexpected request: {url}') + response.json = lambda: responses[url] return response @@ -46,8 +84,7 @@ class TestPeppolParticipant(TransactionCase): def test_create_participant_missing_data(self): # creating a participant without eas/endpoint/document should not be possible - settings = self.env['res.config.settings'].create({}) - settings.write({ + settings = self.env['res.config.settings'].create({ 'is_account_peppol_participant': True, 'account_peppol_eas': False, 'account_peppol_endpoint': False, @@ -60,16 +97,8 @@ class TestPeppolParticipant(TransactionCase): # the account_peppol_proxy_state should correctly change to pending # then the account_peppol_proxy_state should change success # after checking participant status - settings = self.env['res.config.settings'].create({}) - settings.write({ - 'is_account_peppol_participant': True, - 'account_peppol_eas': '9925', - 'account_peppol_endpoint': '0000000000', - 'account_peppol_phone_number': '+32483123456', - }) company = self.env.company - - settings.execute() + settings = self.env['res.config.settings'].create(self._get_participant_vals()) with self._patch_peppol_requests(): settings.button_create_peppol_proxy_user() @@ -85,16 +114,8 @@ class TestPeppolParticipant(TransactionCase): def test_create_reject_participant(self): # the account_peppol_proxy_state should change to rejected # if we reject the participant - settings = self.env['res.config.settings'].create({}) - settings.write({ - 'is_account_peppol_participant': True, - 'account_peppol_eas': '9925', - 'account_peppol_endpoint': '0000000000', - 'account_peppol_phone_number': '+32483123456', - }) company = self.env.company - - settings.execute() + settings = self.env['res.config.settings'].create(self._get_participant_vals()) with self._patch_peppol_requests(reject=True): settings.button_create_peppol_proxy_user() @@ -105,18 +126,60 @@ class TestPeppolParticipant(TransactionCase): @mute_logger('odoo.sql_db') def test_create_duplicate_participant(self): # should not be possible to create a duplicate participant - settings = self.env['res.config.settings'].create({}) - settings.write({ - 'is_account_peppol_participant': True, - 'account_peppol_eas': '9925', - 'account_peppol_endpoint': '0000000000', - 'account_peppol_phone_number': '+32483123456', - }) - - settings.execute() + settings = self.env['res.config.settings'].create(self._get_participant_vals()) with self._patch_peppol_requests(): settings.button_create_peppol_proxy_user() with self.assertRaises(IntegrityError), self.cr.savepoint(): settings.account_peppol_proxy_state = 'not_registered' settings.button_create_peppol_proxy_user() + + def test_save_migration_key(self): + # migration key should be saved + settings = self.env['res.config.settings']\ + .create({ + **self._get_participant_vals(), + 'account_peppol_migration_key': 'helloo', + }) + + with self._patch_peppol_requests(migrate_to=True): + settings.button_create_peppol_proxy_user() + self.assertEqual(self.env.company.account_peppol_proxy_state, 'not_verified') + self.assertFalse(settings.account_peppol_migration_key) # the key should be reset once we've used it + + def test_migrate_away_participant(self): + # a participant should be able to request a migration key + settings = self.env['res.config.settings'].create(self._get_participant_vals()) + + with self._patch_peppol_requests(): + self.assertFalse(settings.account_peppol_migration_key) + settings.button_create_peppol_proxy_user() + settings.account_peppol_proxy_state = 'active' + settings.button_migrate_peppol_registration() + self.assertEqual(self.env.company.account_peppol_proxy_state, 'active') + self.assertEqual(settings.account_peppol_migration_key, 'test_key') + + def test_reset_participant(self): + # once a participant has migrated away, they should be reset + settings = self.env['res.config.settings'].create(self._get_participant_vals()) + + with self._patch_peppol_requests(): + settings.button_create_peppol_proxy_user() + settings.account_peppol_proxy_state = 'active' + settings.button_migrate_peppol_registration() + + with self._patch_peppol_requests(migrated_away=True): + try: + settings.button_update_peppol_user_data() + except UserError: + settings.execute() + self.assertRecordValues( + settings, [{ + 'account_peppol_migration_key': False, + 'is_account_peppol_participant': False, + 'account_peppol_proxy_state': 'not_registered', + }] + ) + self.assertFalse(self.env.company.account_edi_proxy_client_ids.filtered(lambda u: u.proxy_type == 'peppol')) + else: + raise ValidationError('A UserError should be raised.')