From 8590c6a929b49db086b3a1ad64352b7d431e306a Mon Sep 17 00:00:00 2001 From: aliya Date: Tue, 21 Mar 2023 10:34:15 +0000 Subject: [PATCH] [IMP] account_edi_proxy_client,l10n_it_edi: remove edi format dependency Edi format is going to be removed eventually - `account_edi_proxy_client` should not depend on it and be more flexible in general. This commit: - Removes edi_format field from `account_edi_proxy_client.user` model. We still have to add them in `l10n_it_edi` as it still heavily relies on edi format for now. - Adds a way to have a unique edi user per company, proxy user, edi mode combination. This is a necessary step before adding peppol, which will also depend on the account_edi_proxy_client closes odoo/odoo#116059 Related: odoo/upgrade#4458 Signed-off-by: Laurent Smet --- .../account_edi_proxy_client/__manifest__.py | 4 +- .../models/__init__.py | 1 - .../models/account_edi_format.py | 27 ------- .../models/account_edi_proxy_user.py | 73 ++++++++++++++----- .../views/account_edi_proxy_user_views.xml | 3 +- addons/l10n_it_edi/models/__init__.py | 1 + .../l10n_it_edi/models/account_edi_format.py | 22 +++--- .../models/account_edi_proxy_user.py | 27 +++++++ .../l10n_it_edi/models/res_config_settings.py | 36 +++++---- addons/l10n_it_edi/tests/common.py | 1 - .../views/res_config_settings_views.xml | 1 + addons/l10n_it_stock_ddt/tests/test_ddt.py | 4 +- 12 files changed, 113 insertions(+), 87 deletions(-) delete mode 100644 addons/account_edi_proxy_client/models/account_edi_format.py create mode 100644 addons/l10n_it_edi/models/account_edi_proxy_user.py diff --git a/addons/account_edi_proxy_client/__manifest__.py b/addons/account_edi_proxy_client/__manifest__.py index 26a5f826e51..fba602c32c0 100644 --- a/addons/account_edi_proxy_client/__manifest__.py +++ b/addons/account_edi_proxy_client/__manifest__.py @@ -3,7 +3,7 @@ 'name': 'Proxy features for account_edi', 'description': """ This module adds generic features to register an Odoo DB on the proxy responsible for receiving data (via requests from web-services). -- An edi_proxy_user has a unique identification on a specific format (for example, the vat for Peppol) which +- An edi_proxy_user has a unique identification on a specific proxy type (e.g. l10n_it_edi, peppol) which allows to identify him when receiving a document addressed to him. It is linked to a specific company on a specific Odoo database. - Encryption features allows to decrypt all the user's data when receiving it from the proxy. @@ -11,7 +11,7 @@ Odoo database. """, 'version': '1.0', 'category': 'Accounting/Accounting', - 'depends': ['account_edi'], + 'depends': ['account'], 'external_dependencies': { 'python': ['cryptography'] }, diff --git a/addons/account_edi_proxy_client/models/__init__.py b/addons/account_edi_proxy_client/models/__init__.py index 0df13f7a38e..1158a9d60c7 100644 --- a/addons/account_edi_proxy_client/models/__init__.py +++ b/addons/account_edi_proxy_client/models/__init__.py @@ -1,3 +1,2 @@ -from . import account_edi_format from . import account_edi_proxy_user from . import res_company diff --git a/addons/account_edi_proxy_client/models/account_edi_format.py b/addons/account_edi_proxy_client/models/account_edi_format.py deleted file mode 100644 index 0dc8bd9abe0..00000000000 --- a/addons/account_edi_proxy_client/models/account_edi_format.py +++ /dev/null @@ -1,27 +0,0 @@ -# -*- coding: utf-8 -*- -from odoo import models - - -class AccountEdiFormat(models.Model): - _inherit = 'account.edi.format' - - # ------------------------------------------------------------------------- - # Helpers - # ------------------------------------------------------------------------- - - def _get_proxy_user(self, company): - '''Returns the proxy_user associated with this edi format. - ''' - self.ensure_one() - return company.account_edi_proxy_client_ids.filtered(lambda u: u.edi_format_id == self) - - # ------------------------------------------------------------------------- - # To override - # ------------------------------------------------------------------------- - - def _get_proxy_identification(self, company): - '''Returns the key that will identify company uniquely for this edi format (for example, the vat) - or raises a UserError (if the user didn't fill the related field). - TO OVERRIDE - ''' - return False diff --git a/addons/account_edi_proxy_client/models/account_edi_proxy_user.py b/addons/account_edi_proxy_client/models/account_edi_proxy_user.py index 2978a58895b..91d4b69ffcc 100644 --- a/addons/account_edi_proxy_client/models/account_edi_proxy_user.py +++ b/addons/account_edi_proxy_client/models/account_edi_proxy_user.py @@ -1,4 +1,4 @@ -from odoo import models, fields, _ +from odoo import api, models, fields, _ from odoo.exceptions import UserError from .account_edi_proxy_auth import OdooEdiProxyAuth @@ -17,9 +17,6 @@ import logging _logger = logging.getLogger(__name__) - -DEFAULT_SERVER_URL = 'https://l10n-it-edi.api.odoo.com' -DEFAULT_TEST_SERVER_URL = 'https://iap-services-test.odoo.com' TIMEOUT = 30 @@ -45,28 +42,63 @@ class AccountEdiProxyClientUser(models.Model): id_client = fields.Char(required=True) company_id = fields.Many2one('res.company', string='Company', required=True, default=lambda self: self.env.company) - edi_format_id = fields.Many2one('account.edi.format', required=True) - edi_format_code = fields.Char(related='edi_format_id.code', readonly=True) - edi_identification = fields.Char(required=True, help="The unique id that identifies this user for on the edi format, typically the vat") + edi_identification = fields.Char(required=True, help="The unique id that identifies this user, typically the vat") private_key = fields.Binary(required=True, attachment=False, groups="base.group_system", help="The key to encrypt all the user's data") private_key_filename = fields.Char(compute='_compute_private_key_filename') refresh_token = fields.Char(groups="base.group_system") + proxy_type = fields.Selection( + selection=[], + string="Proxy type", + compute='_compute_proxy_type', + readonly=False, + store=True, + ) + edi_mode = fields.Selection( + selection=[ + ('prod', 'Production mode'), + ('test', 'Test mode'), + ('demo', 'Demo mode'), + ], + string='EDI operating mode', + ) _sql_constraints = [ ('unique_id_client', 'unique(id_client)', 'This id_client is already used on another user.'), - ('unique_edi_identification_per_for', 'unique(edi_identification, edi_format_id)', 'This edi identification is already assigned to a user'), + ('unique_edi_identification', 'unique(edi_identification, proxy_type, edi_mode)', 'This edi identification is already assigned to a user'), ] + @api.depends('company_id') + def _compute_proxy_type(self): + for user in self: + user.proxy_type = False + def _compute_private_key_filename(self): for record in self: record.private_key_filename = f'{record.id_client}_{record.edi_identification}.key' - def _get_demo_state(self): - demo_state = self.env['ir.config_parameter'].sudo().get_param('account_edi_proxy_client.demo', False) - return 'prod' if demo_state in ['prod', False] else 'test' if demo_state == 'test' else 'demo' + def _get_proxy_urls(self): + # To extend + return {} - def _get_server_url(self): - return DEFAULT_TEST_SERVER_URL if self._get_demo_state() == 'test' else self.env['ir.config_parameter'].sudo().get_param('account_edi_proxy_client.edi_server_url', DEFAULT_SERVER_URL) + def _get_server_url(self, proxy_type=None, edi_mode=None): + proxy_type = proxy_type or self.proxy_type + edi_mode = edi_mode or self.edi_mode + proxy_urls = self._get_proxy_urls() + # letting this traceback in case of a KeyError, as that would mean something's wrong with the code + return proxy_urls[proxy_type][edi_mode] + + def _get_proxy_users(self, company, proxy_type): + '''Returns proxy users associated with the given company and proxy type. + ''' + return company.account_edi_proxy_client_ids.filtered(lambda u: u.proxy_type == proxy_type) + + def _get_proxy_identification(self, company): + '''Returns the key that will identify company uniquely + within a specific proxy type and edi operating mode. + or raises a UserError (if the user didn't fill the related field). + TO OVERRIDE + ''' + return False def _make_request(self, url, params=False): ''' Make a request to proxy and handle the generic elements of the reponse (errors, new refresh token). @@ -78,7 +110,7 @@ class AccountEdiProxyClientUser(models.Model): 'id': uuid.uuid4().hex, } - if self._get_demo_state() == 'demo': + if self.edi_mode == 'demo': # Last barrier : in case the demo mode is not handled by the caller, we block access. raise Exception("Can't access the proxy in demo mode") @@ -113,7 +145,7 @@ class AccountEdiProxyClientUser(models.Model): return response['result'] - def _register_proxy_user(self, company, edi_format, edi_identification): + def _register_proxy_user(self, company, proxy_type, edi_mode, edi_identification): ''' Generate the public_key/private_key that will be used to encrypt the file, send a request to the proxy to register the user with the public key and create the user with the private key. @@ -139,18 +171,18 @@ class AccountEdiProxyClientUser(models.Model): encoding=serialization.Encoding.PEM, format=serialization.PublicFormat.SubjectPublicKeyInfo ) - if self._get_demo_state() == 'demo': + if edi_mode == 'demo': # simulate registration response = {'id_client': f'demo{company.id}', 'refresh_token': 'demo'} else: try: # b64encode returns a bytestring, we need it as a string - response = self._make_request(self._get_server_url() + '/iap/account_edi/1/create_user', params={ + response = self._make_request(self._get_server_url(proxy_type, edi_mode) + '/iap/account_edi/2/create_user', params={ 'dbuuid': company.env['ir.config_parameter'].get_param('database.uuid'), 'company_id': company.id, - 'edi_format_code': edi_format.code, 'edi_identification': edi_identification, - 'public_key': base64.b64encode(public_pem).decode() + 'public_key': base64.b64encode(public_pem).decode(), + 'proxy_type': proxy_type, }) except AccountEdiProxyError as e: raise UserError(e.message) @@ -160,7 +192,8 @@ class AccountEdiProxyClientUser(models.Model): self.create({ 'id_client': response['id_client'], 'company_id': company.id, - 'edi_format_id': edi_format.id, + 'proxy_type': proxy_type, + 'edi_mode': edi_mode, 'edi_identification': edi_identification, 'private_key': base64.b64encode(private_pem), 'refresh_token': response['refresh_token'], diff --git a/addons/account_edi_proxy_client/views/account_edi_proxy_user_views.xml b/addons/account_edi_proxy_client/views/account_edi_proxy_user_views.xml index 20bdce2ec51..26cfc5d90aa 100644 --- a/addons/account_edi_proxy_client/views/account_edi_proxy_user_views.xml +++ b/addons/account_edi_proxy_client/views/account_edi_proxy_user_views.xml @@ -10,7 +10,7 @@ - + @@ -30,7 +30,6 @@ - diff --git a/addons/l10n_it_edi/models/__init__.py b/addons/l10n_it_edi/models/__init__.py index 52649027535..6804a7790d9 100644 --- a/addons/l10n_it_edi/models/__init__.py +++ b/addons/l10n_it_edi/models/__init__.py @@ -10,3 +10,4 @@ from . import account_edi_document from . import account_edi_format from . import ddt from . import ir_attachment +from . import account_edi_proxy_user diff --git a/addons/l10n_it_edi/models/account_edi_format.py b/addons/l10n_it_edi/models/account_edi_format.py index d6bc20e2e6f..daaf1272399 100644 --- a/addons/l10n_it_edi/models/account_edi_format.py +++ b/addons/l10n_it_edi/models/account_edi_format.py @@ -292,10 +292,10 @@ class AccountEdiFormat(models.Model): def _cron_receive_fattura_pa(self): ''' Check the proxy for incoming invoices for all companies. ''' - if self.env['account_edi_proxy_client.user']._get_demo_state() == 'demo': - return - - for proxy_user in self.env['account_edi_proxy_client.user'].search([('edi_format_code', '=', 'fattura_pa')]): + for proxy_user in self.env['account_edi_proxy_client.user'].search([ + ('proxy_type', '=', 'l10n_it_edi'), + ('edi_mode', '!=', 'demo'), + ]): self._receive_fattura_pa(proxy_user) def _receive_fattura_pa(self, proxy_user): @@ -831,7 +831,7 @@ class AccountEdiFormat(models.Model): res.extend(self._l10n_it_edi_check_invoice_configuration(move)) - if not self._get_proxy_user(move.company_id): + if not self.env['account_edi_proxy_client.user']._get_proxy_users(move.company_id, 'l10n_it_edi'): res.append(_("You must accept the terms and conditions in the settings to use FatturaPA.")) return res @@ -870,14 +870,14 @@ class AccountEdiFormat(models.Model): 'data': {'filename': filename, 'xml': base64.b64encode(xml.encode()).decode()}} company = invoices.company_id - proxy_user = self._get_proxy_user(company) + proxy_user = self.env['account_edi_proxy_client.user']._get_proxy_users(company, 'l10n_it_edi') if not proxy_user: # proxy user should exist, because there is a check in _check_move_configuration return {invoice: { 'error': _("You must accept the terms and conditions in the settings to use FatturaPA."), 'blocking_level': 'error'} for invoice in invoices} responses = {} - if proxy_user._get_demo_state() == 'demo': + if proxy_user.edi_mode == 'demo': responses = {i['data']['filename']: {'id_transaction': 'demo'} for i in to_send.values()} else: try: @@ -902,13 +902,13 @@ class AccountEdiFormat(models.Model): to_return = {} company = invoices.company_id - proxy_user = self._get_proxy_user(company) + proxy_user = self.env['account_edi_proxy_client.user']._get_proxy_users(company, 'l10n_it_edi') if not proxy_user: # proxy user should exist, because there is a check in _check_move_configuration return {invoice: { 'error': _("You must accept the terms and conditions in the settings to use FatturaPA."), 'blocking_level': 'error'} for invoice in invoices} - if proxy_user._get_demo_state() == 'demo': + if proxy_user.edi_mode == 'demo': # simulate success and bypass ack return {invoice: {'attachment': invoice.l10n_it_edi_attachment_id} for invoice in invoices} else: @@ -1049,11 +1049,7 @@ class AccountEdiFormat(models.Model): # ------------------------------------------------------------------------- # Proxy methods # ------------------------------------------------------------------------- - def _get_proxy_identification(self, company): - if self.code != 'fattura_pa': - return super()._get_proxy_identification() - if not company.l10n_it_codice_fiscale: raise UserError(_('Please fill your codice fiscale to be able to receive invoices from FatturaPA')) diff --git a/addons/l10n_it_edi/models/account_edi_proxy_user.py b/addons/l10n_it_edi/models/account_edi_proxy_user.py new file mode 100644 index 00000000000..cbea365aee1 --- /dev/null +++ b/addons/l10n_it_edi/models/account_edi_proxy_user.py @@ -0,0 +1,27 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +from odoo import fields, models + + +class AccountEdiProxyClientUser(models.Model): + _inherit = 'account_edi_proxy_client.user' + + proxy_type = fields.Selection( + selection_add=[('l10n_it_edi', 'Italian EDI')], + ) + + def _get_proxy_urls(self): + urls = super()._get_proxy_urls() + urls['l10n_it_edi'] = { + 'prod': 'https://l10n-it-edi.api.odoo.com', + 'test': 'https://iap-services-test.odoo.com', + } + return urls + + def _compute_proxy_type(self): + # Extends account_edi_proxy_client + super()._compute_proxy_type() + for user in self: + if user.company_id.country_code == 'IT': + user.proxy_type = 'l10n_it_edi' diff --git a/addons/l10n_it_edi/models/res_config_settings.py b/addons/l10n_it_edi/models/res_config_settings.py index adc5c66aefb..19dd7ee83fd 100644 --- a/addons/l10n_it_edi/models/res_config_settings.py +++ b/addons/l10n_it_edi/models/res_config_settings.py @@ -18,15 +18,19 @@ class ResConfigSettings(models.TransientModel): inverse='_set_l10n_it_edi_sdicoop_register_demo_mode', readonly=False) - def _create_proxy_user(self, company_id): + def _create_proxy_user(self, company_id, edi_mode): fattura_pa = self.env.ref('l10n_it_edi.edi_fatturaPA') edi_identification = fattura_pa._get_proxy_identification(company_id) - self.env['account_edi_proxy_client.user']._register_proxy_user(company_id, fattura_pa, edi_identification) + self.env['account_edi_proxy_client.user']._register_proxy_user(company_id, 'l10n_it_edi', edi_mode, edi_identification) @api.depends('company_id.account_edi_proxy_client_ids', 'company_id.account_edi_proxy_client_ids.active') def _compute_l10n_it_edi_sdicoop_demo_mode(self): for config in self: - config.l10n_it_edi_sdicoop_demo_mode = self.env['account_edi_proxy_client.user']._get_demo_state() + edi_user = self.env['account_edi_proxy_client.user'].search([ + ('company_id', '=', config.company_id.id), + ('proxy_type', '=', 'l10n_it_edi'), + ], limit=1) + config.l10n_it_edi_sdicoop_demo_mode = edi_user.edi_mode def _set_l10n_it_edi_sdicoop_demo_mode(self): for config in self: @@ -39,11 +43,10 @@ class ResConfigSettings(models.TransientModel): @api.depends('company_id.account_edi_proxy_client_ids', 'company_id.account_edi_proxy_client_ids.active') def _compute_l10n_it_edi_proxy_current_state(self): - fattura_pa = self.env.ref('l10n_it_edi.edi_fatturaPA') for config in self: proxy_user = config.company_id.account_edi_proxy_client_ids.search([ ('company_id', '=', config.company_id.id), - ('edi_format_id', '=', fattura_pa.id), + ('proxy_type', '=', 'l10n_it_edi'), ], limit=1) config.l10n_it_edi_proxy_current_state = 'inactive' if not proxy_user else 'demo' if proxy_user.id_client[:4] == 'demo' else 'active' @@ -54,24 +57,19 @@ class ResConfigSettings(models.TransientModel): self.l10n_it_edi_sdicoop_register = False def button_create_proxy_user(self): - # For now, only fattura_pa uses the proxy. - # To use it for more, we have to either make the activation of the proxy on a format basis - # or create a user per format here (but also when installing new formats) fattura_pa = self.env.ref('l10n_it_edi.edi_fatturaPA') edi_identification = fattura_pa._get_proxy_identification(self.company_id) if not edi_identification: return - self.env['account_edi_proxy_client.user']._register_proxy_user(self.company_id, fattura_pa, edi_identification) + self.env['account_edi_proxy_client.user']._register_proxy_user(self.company_id, 'l10n_it_edi', self.l10n_it_edi_sdicoop_demo_mode, edi_identification) def _set_l10n_it_edi_sdicoop_register_demo_mode(self): - - fattura_pa = self.env.ref('l10n_it_edi.edi_fatturaPA') for config in self: proxy_user = self.env['account_edi_proxy_client.user'].search([ ('company_id', '=', config.company_id.id), - ('edi_format_id', '=', fattura_pa.id) + ('proxy_type', '=', 'l10n_it_edi'), ], limit=1) real_proxy_users = self.env['account_edi_proxy_client.user'].sudo().search([ @@ -79,22 +77,22 @@ class ResConfigSettings(models.TransientModel): ]) # Update the config as per the selected radio button - previous_demo_state = proxy_user._get_demo_state() - self.env['ir.config_parameter'].set_param('account_edi_proxy_client.demo', config.l10n_it_edi_sdicoop_demo_mode) - + previous_demo_state = proxy_user.edi_mode + edi_mode = config.l10n_it_edi_sdicoop_demo_mode + self.env['ir.config_parameter'].set_param('account_edi_proxy_client.demo', edi_mode) # If the user is trying to change from a state in which they have a registered official or testing proxy client # to another state, we should stop them - if real_proxy_users and previous_demo_state != config.l10n_it_edi_sdicoop_demo_mode: + if real_proxy_users and previous_demo_state != edi_mode: raise UserError(_("The company has already registered with the service as 'Test' or 'Official', it cannot change.")) if config.l10n_it_edi_sdicoop_register: # There should only be one user at a time, if there are no users, register one if not proxy_user: - self._create_proxy_user(config.company_id) + self._create_proxy_user(config.company_id, edi_mode) return # If there is a demo user, and we are transitioning from demo to test or production, we should # delete all demo users and then create the new user. - elif proxy_user.id_client[:4] == 'demo' and config.l10n_it_edi_sdicoop_demo_mode != 'demo': + elif proxy_user.id_client[:4] == 'demo' and edi_mode != 'demo': self.env['account_edi_proxy_client.user'].search([('id_client', '=like', 'demo%')]).sudo().unlink() - self._create_proxy_user(config.company_id) + self._create_proxy_user(config.company_id, edi_mode) diff --git a/addons/l10n_it_edi/tests/common.py b/addons/l10n_it_edi/tests/common.py index 07beff11288..a5f11f2c7aa 100644 --- a/addons/l10n_it_edi/tests/common.py +++ b/addons/l10n_it_edi/tests/common.py @@ -86,7 +86,6 @@ class TestItEdi(AccountEdiTestCommon): cls.proxy_user = cls.env['account_edi_proxy_client.user'].create({ 'id_client': 'l10n_it_edi_test', 'company_id': cls.company.id, - 'edi_format_id': cls.edi_format.id, 'edi_identification': 'l10n_it_edi_test', 'private_key': 'l10n_it_edi_test', }) diff --git a/addons/l10n_it_edi/views/res_config_settings_views.xml b/addons/l10n_it_edi/views/res_config_settings_views.xml index 2df9f9b4035..179daffc318 100644 --- a/addons/l10n_it_edi/views/res_config_settings_views.xml +++ b/addons/l10n_it_edi/views/res_config_settings_views.xml @@ -14,6 +14,7 @@ Fattura Elettronica mode +
In demo mode Odoo will just simulate the sending of invoices to the government.
In test mode (experimental) Odoo will send the invoices to a non-production service. diff --git a/addons/l10n_it_stock_ddt/tests/test_ddt.py b/addons/l10n_it_stock_ddt/tests/test_ddt.py index 7bdac99b308..2354616ac0f 100644 --- a/addons/l10n_it_stock_ddt/tests/test_ddt.py +++ b/addons/l10n_it_stock_ddt/tests/test_ddt.py @@ -32,9 +32,9 @@ class TestDDT(TestSaleCommon): }) settings = cls.env['res.config.settings'].create({}) - if hasattr(settings, 'button_create_proxy_user'): + if hasattr(settings, '_create_proxy_user'): # Needed when `l10n_it_edi_sdiscoop` is installed - settings.button_create_proxy_user() + settings._create_proxy_user(cls.company_data['company'], 'demo') def test_ddt_flow(self):