diff --git a/l10n_fr_siret_lookup/README.rst b/l10n_fr_siret_lookup/README.rst new file mode 100644 index 0000000000..18f5f202e8 --- /dev/null +++ b/l10n_fr_siret_lookup/README.rst @@ -0,0 +1,168 @@ +============ +SIRET Lookup +============ + +.. + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! This file is generated by oca-gen-addon-readme !! + !! changes will be overwritten. !! + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! source digest: sha256:bffa20432b4af99db3ccb096295b332d731774e9d5a380627c1bfcaf64dcab0d + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + +.. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png + :target: https://odoo-community.org/page/development-status + :alt: Beta +.. |badge2| image:: https://img.shields.io/badge/licence-AGPL--3-blue.png + :target: http://www.gnu.org/licenses/agpl-3.0-standalone.html + :alt: License: AGPL-3 +.. |badge3| image:: https://img.shields.io/badge/github-OCA%2Fl10n--france-lightgray.png?logo=github + :target: https://github.com/OCA/l10n-france/tree/18.0/l10n_fr_siret_lookup + :alt: OCA/l10n-france +.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png + :target: https://translation.odoo-community.org/projects/l10n-france-18-0/l10n-france-18-0-l10n_fr_siret_lookup + :alt: Translate me on Weblate +.. |badge5| image:: https://img.shields.io/badge/runboat-Try%20me-875A7B.png + :target: https://runboat.odoo-community.org/builds?repo=OCA/l10n-france&target_branch=18.0 + :alt: Try me on Runboat + +|badge1| |badge2| |badge3| |badge4| |badge5| + +This modules updates partner fields via the SIRENE database +<`https://data.opendatasoft.com\\> >`__. +It uses the dataset *economicref-france-sirene-v3* of opendatasoft +<`https://public.opendatasoft.com/explore/dataset/economicref-france-sirene-v3/information/\\> >`__. + +It computes a theorical VAT number from the SIREN and then checks the +validity of the VAT number (depending on configuration) on +`VIES `__ (if invalid, the +VAT number is discarded). + +The module supports 2 scenarios: + +- update of an existing partner via the menu *Action > SIREN Lookup*, +- creation of a new partner: start by setting the VAT number field, the + SIREN field or SIRET field and Odoo will set the other fields. For + usability purposes, it also work when you write the VAT number, SIREN + or SIRET in the company name field. + +In the 2 scenarios, it will update the following fields: + +- Company Name +- Street +- Postal Code +- City +- Country +- SIREN and NIC (i.e. SIRET) +- VAT Number +- Language (creation scenario only) + +**Table of contents** + +.. contents:: + :local: + +Installation +============ + +It's recommended to install ``python-stdnum>=1.18`` for SIRET support. + +Configuration +============= + +You can use this module without configuration. + +However 2 configuration parameter can be adjusted in *Invoicing* > +Configuration > Settings : + +- **Verify VAT Numbers** (vat_check_vies field from base_vat module): + this parameter will define whether you want check computed VAT number + against EU VIES validation service +- **Force VAT Numbers during SIRET Lookups if VIES check times out or + is disabled** (force_vat_siret_lookup): this parameter allows to + force use of computed VAT number even if not checked agains EU VIES + validation service or if an Exception is raised by EU VIES validation + (for instance because of Timeout, which are quite frequent while + checking for FR VAT) + +The 2 above parameters are company dependent. + +*Note:* if EU VIES validation service reports that VAT number is +incorrect, the VAT field is emptied (even if Force... parameter is +ticked) + +Usage +===== + +To update an existing partner, go to the partner form view and click on +*Action > SIREN Lookup*. + +By default, the search field is filled with Company name. To get more +accurate results, you may want to add the City name where the company is +registered. Then click on *Lookup*. + +A list of companies is displayed. You may want to click on one in order +to see corresponding information or directly select company from list +view. Once a company is selected, the partner information is updated and +a message is logged in the chatter. + +Bug Tracker +=========== + +Bugs are tracked on `GitHub Issues `_. +In case of trouble, please check there if your issue has already been reported. +If you spotted it first, help us to smash it by providing a detailed and welcomed +`feedback `_. + +Do not contact contributors directly about support or help with technical issues. + +Credits +======= + +Authors +------- + +* Le Filament +* Akretion + +Contributors +------------ + +- Benjamin Rivier (https://le-filament.com) +- Remi Cazenave (https://le-filament.com) +- Alexis de Lattre + +Other credits +------------- + +The development of this module has been financially supported by: + +- Le Filament + +Maintainers +----------- + +This module is maintained by the OCA. + +.. image:: https://odoo-community.org/logo.png + :alt: Odoo Community Association + :target: https://odoo-community.org + +OCA, or the Odoo Community Association, is a nonprofit organization whose +mission is to support the collaborative development of Odoo features and +promote its widespread use. + +.. |maintainer-remi-filament| image:: https://github.com/remi-filament.png?size=40px + :target: https://github.com/remi-filament + :alt: remi-filament +.. |maintainer-alexis-via| image:: https://github.com/alexis-via.png?size=40px + :target: https://github.com/alexis-via + :alt: alexis-via + +Current `maintainers `__: + +|maintainer-remi-filament| |maintainer-alexis-via| + +This module is part of the `OCA/l10n-france `_ project on GitHub. + +You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute. diff --git a/l10n_fr_siret_lookup/__init__.py b/l10n_fr_siret_lookup/__init__.py new file mode 100644 index 0000000000..9b4296142f --- /dev/null +++ b/l10n_fr_siret_lookup/__init__.py @@ -0,0 +1,2 @@ +from . import models +from . import wizard diff --git a/l10n_fr_siret_lookup/__manifest__.py b/l10n_fr_siret_lookup/__manifest__.py new file mode 100644 index 0000000000..0e04c6ab4d --- /dev/null +++ b/l10n_fr_siret_lookup/__manifest__.py @@ -0,0 +1,26 @@ +# Copyright 2018-2022 Le Filament () +# Copyright 2021-2022 Akretion France (http://www.akretion.com/) +# @author: Alexis de Lattre +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). +{ + "name": "SIRET Lookup", + "summary": "Lookup partner via an API on the SIRENE directory", + "version": "18.0.1.0.0", + "category": "Partner", + "website": "https://github.com/OCA/l10n-france", + "author": "Le Filament, Akretion, Odoo Community Association (OCA)", + "maintainers": ["remi-filament", "alexis-via"], + "license": "AGPL-3", + "depends": [ + "base_vat", + "l10n_fr_siret", + ], + "external_dependencies": {"python": ["requests", "python-stdnum"]}, + "data": [ + "wizard/fr_siret_lookup_view.xml", + "views/res_partner.xml", + "views/res_config_settings_views.xml", + "security/ir.model.access.csv", + ], + "installable": True, +} diff --git a/l10n_fr_siret_lookup/i18n/es.po b/l10n_fr_siret_lookup/i18n/es.po new file mode 100644 index 0000000000..20c073f164 --- /dev/null +++ b/l10n_fr_siret_lookup/i18n/es.po @@ -0,0 +1,275 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * l10n_fr_siret_lookup +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 16.0\n" +"Report-Msgid-Bugs-To: \n" +"PO-Revision-Date: 2023-09-03 13:35+0000\n" +"Last-Translator: Ivorra78 \n" +"Language-Team: none\n" +"Language: es\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: nplurals=2; plural=n != 1;\n" +"X-Generator: Weblate 4.17\n" + +#. module: l10n_fr_siret_lookup +#: model:ir.model.fields,field_description:l10n_fr_siret_lookup.field_fr_siret_lookup_line__staff +msgid "# Staff" +msgstr "# Empleados" + +#. module: l10n_fr_siret_lookup +#: model:ir.model.fields,field_description:l10n_fr_siret_lookup.field_fr_siret_lookup_line__ape +msgid "APE Code" +msgstr "Código APE" + +#. module: l10n_fr_siret_lookup +#: model:ir.model.fields,field_description:l10n_fr_siret_lookup.field_fr_siret_lookup_line__ape_label +msgid "APE Label" +msgstr "Etiqueta APE" + +#. module: l10n_fr_siret_lookup +#: model:ir.model.fields,field_description:l10n_fr_siret_lookup.field_fr_siret_lookup_line__active +msgid "Active" +msgstr "Abierta" + +#. module: l10n_fr_siret_lookup +#: model_terms:ir.ui.view,arch_db:l10n_fr_siret_lookup.fr_siret_lookup_line_form +msgid "Address" +msgstr "Dirección" + +#. module: l10n_fr_siret_lookup +#: model:ir.model.fields,field_description:l10n_fr_siret_lookup.field_fr_siret_lookup_line__category +msgid "Category" +msgstr "Categoría" + +#. module: l10n_fr_siret_lookup +#: model:ir.model.fields,field_description:l10n_fr_siret_lookup.field_fr_siret_lookup_line__city +msgid "City" +msgstr "Ciudad" + +#. module: l10n_fr_siret_lookup +#: model_terms:ir.ui.view,arch_db:l10n_fr_siret_lookup.fr_siret_lookup_form +msgid "Close" +msgstr "Cerrar" + +#. module: l10n_fr_siret_lookup +#: model_terms:ir.ui.view,arch_db:l10n_fr_siret_lookup.fr_siret_lookup_line_form +msgid "Closed" +msgstr "Cerrada" + +#. module: l10n_fr_siret_lookup +#: model_terms:ir.ui.view,arch_db:l10n_fr_siret_lookup.fr_siret_lookup_line_form +msgid "Company" +msgstr "Companía" + +#. module: l10n_fr_siret_lookup +#: model_terms:ir.ui.view,arch_db:l10n_fr_siret_lookup.fr_siret_lookup_line_form +msgid "Company Infos" +msgstr "Información de la empresa" + +#. module: l10n_fr_siret_lookup +#: model:ir.model,name:l10n_fr_siret_lookup.model_fr_siret_lookup_line +msgid "Company Selection" +msgstr "Selección de empresa" + +#. module: l10n_fr_siret_lookup +#: model_terms:ir.ui.view,arch_db:l10n_fr_siret_lookup.fr_siret_lookup_form +#: model_terms:ir.ui.view,arch_db:l10n_fr_siret_lookup.fr_siret_lookup_line_form +msgid "Confirm selected company ?" +msgstr "¿Confirmar empresa seleccionada?" + +#. module: l10n_fr_siret_lookup +#: model:ir.model,name:l10n_fr_siret_lookup.model_res_partner +msgid "Contact" +msgstr "Contacto" + +#. module: l10n_fr_siret_lookup +#: model:ir.model.fields,field_description:l10n_fr_siret_lookup.field_fr_siret_lookup_line__country_id +msgid "Country" +msgstr "País" + +#. module: l10n_fr_siret_lookup +#: model:ir.model.fields,field_description:l10n_fr_siret_lookup.field_fr_siret_lookup__create_uid +#: model:ir.model.fields,field_description:l10n_fr_siret_lookup.field_fr_siret_lookup_line__create_uid +msgid "Created by" +msgstr "Creado por" + +#. module: l10n_fr_siret_lookup +#: model:ir.model.fields,field_description:l10n_fr_siret_lookup.field_fr_siret_lookup__create_date +#: model:ir.model.fields,field_description:l10n_fr_siret_lookup.field_fr_siret_lookup_line__create_date +msgid "Created on" +msgstr "Creado el" + +#. module: l10n_fr_siret_lookup +#: model:ir.model.fields,field_description:l10n_fr_siret_lookup.field_fr_siret_lookup_line__creation_date +msgid "Creation Date" +msgstr "Fecha de Creación" + +#. module: l10n_fr_siret_lookup +#: model:ir.model.fields,field_description:l10n_fr_siret_lookup.field_fr_siret_lookup__display_name +#: model:ir.model.fields,field_description:l10n_fr_siret_lookup.field_fr_siret_lookup_line__display_name +msgid "Display Name" +msgstr "Mostrar Nombre" + +#. module: l10n_fr_siret_lookup +#. odoo-python +#: code:addons/l10n_fr_siret_lookup/models/res_partner.py:0 +#, python-format +msgid "" +"Failed to query VIES.\n" +"Technical error: %s." +msgstr "" +"Error en la consulta VIES.\n" +"Error técnico: %s." + +#. module: l10n_fr_siret_lookup +#. odoo-python +#: code:addons/l10n_fr_siret_lookup/models/res_partner.py:0 +#, python-format +msgid "" +"Failure in the request on data.opendatasoft.com to create or update partner " +"from SIREN or SIRET. Technical error: %s." +msgstr "" +"Fallo en la petición en data.opendatasoft.com para crear o actualizar socio " +"desde SIREN o SIRET. Error técnico: %s." + +#. module: l10n_fr_siret_lookup +#: model:ir.model,name:l10n_fr_siret_lookup.model_fr_siret_lookup +msgid "Get values from companies" +msgstr "Obtener valores de las compañías" + +#. module: l10n_fr_siret_lookup +#: model:ir.model.fields,field_description:l10n_fr_siret_lookup.field_fr_siret_lookup__id +#: model:ir.model.fields,field_description:l10n_fr_siret_lookup.field_fr_siret_lookup_line__id +msgid "ID" +msgstr "ID(identificación)" + +#. module: l10n_fr_siret_lookup +#: model:ir.model.fields,field_description:l10n_fr_siret_lookup.field_fr_siret_lookup____last_update +#: model:ir.model.fields,field_description:l10n_fr_siret_lookup.field_fr_siret_lookup_line____last_update +msgid "Last Modified on" +msgstr "Última modificación el" + +#. module: l10n_fr_siret_lookup +#: model:ir.model.fields,field_description:l10n_fr_siret_lookup.field_fr_siret_lookup__write_uid +#: model:ir.model.fields,field_description:l10n_fr_siret_lookup.field_fr_siret_lookup_line__write_uid +msgid "Last Updated by" +msgstr "Actualizado por última vez por" + +#. module: l10n_fr_siret_lookup +#: model:ir.model.fields,field_description:l10n_fr_siret_lookup.field_fr_siret_lookup__write_date +#: model:ir.model.fields,field_description:l10n_fr_siret_lookup.field_fr_siret_lookup_line__write_date +msgid "Last Updated on" +msgstr "Última Actualización el" + +#. module: l10n_fr_siret_lookup +#: model_terms:ir.ui.view,arch_db:l10n_fr_siret_lookup.fr_siret_lookup_line_form +msgid "Legal Infos" +msgstr "Información legal" + +#. module: l10n_fr_siret_lookup +#: model:ir.model.fields,field_description:l10n_fr_siret_lookup.field_fr_siret_lookup_line__legal_type +msgid "Legal Type" +msgstr "Tipo legal" + +#. module: l10n_fr_siret_lookup +#: model_terms:ir.ui.view,arch_db:l10n_fr_siret_lookup.fr_siret_lookup_form +msgid "Lookup" +msgstr "Búsqueda" + +#. module: l10n_fr_siret_lookup +#: model:ir.model.fields,field_description:l10n_fr_siret_lookup.field_fr_siret_lookup_line__name +msgid "Name" +msgstr "Nombre" + +#. module: l10n_fr_siret_lookup +#: model:ir.model.fields,field_description:l10n_fr_siret_lookup.field_fr_siret_lookup__name +msgid "Name to Search" +msgstr "Nombre a buscar" + +#. module: l10n_fr_siret_lookup +#: model:ir.model.fields,field_description:l10n_fr_siret_lookup.field_fr_siret_lookup__partner_id +msgid "Partner" +msgstr "Socio" + +#. module: l10n_fr_siret_lookup +#. odoo-python +#: code:addons/l10n_fr_siret_lookup/wizard/fr_siret_lookup.py:0 +#, python-format +msgid "Partner '%s' is not a company. This action is not relevant." +msgstr "El socio '%s' no es una empresa. Esta acción no es relevante." + +#. module: l10n_fr_siret_lookup +#. odoo-python +#: code:addons/l10n_fr_siret_lookup/wizard/fr_siret_lookup.py:0 +#, python-format +msgid "Partner updated via the opendatasoft.com API." +msgstr "Socio actualizado a través de la API de opendatasoft.com." + +#. module: l10n_fr_siret_lookup +#: model:ir.model.fields,field_description:l10n_fr_siret_lookup.field_fr_siret_lookup__line_ids +msgid "Results" +msgstr "Resultados" + +#. module: l10n_fr_siret_lookup +#: model:ir.model.fields,field_description:l10n_fr_siret_lookup.field_fr_siret_lookup_line__siren +msgid "SIREN" +msgstr "SIREN" + +#. module: l10n_fr_siret_lookup +#: model:ir.actions.act_window,name:l10n_fr_siret_lookup.fr_siret_lookup_partner_action +msgid "SIREN Lookup" +msgstr "Búsqueda SIREN" + +#. module: l10n_fr_siret_lookup +#: model_terms:ir.ui.view,arch_db:l10n_fr_siret_lookup.fr_siret_lookup_form +msgid "SIREN Lookup via opendatasoft.com" +msgstr "Búsqueda SIREN vía opendatasoft.com" + +#. module: l10n_fr_siret_lookup +#: model:ir.actions.act_window,name:l10n_fr_siret_lookup.fr_siret_lookup_action +msgid "SIREN lookup via opendatasoft.com" +msgstr "Búsqueda SIREN vía opendatasoft.com" + +#. module: l10n_fr_siret_lookup +#: model:ir.model.fields,field_description:l10n_fr_siret_lookup.field_fr_siret_lookup_line__siret +msgid "SIRET" +msgstr "SIRET" + +#. module: l10n_fr_siret_lookup +#: model_terms:ir.ui.view,arch_db:l10n_fr_siret_lookup.fr_siret_lookup_form +msgid "Search Results" +msgstr "Resultados de búsqueda" + +#. module: l10n_fr_siret_lookup +#: model_terms:ir.ui.view,arch_db:l10n_fr_siret_lookup.fr_siret_lookup_form +#: model_terms:ir.ui.view,arch_db:l10n_fr_siret_lookup.fr_siret_lookup_line_form +msgid "Select" +msgstr "Seleccionar" + +#. module: l10n_fr_siret_lookup +#: model:ir.model.fields,field_description:l10n_fr_siret_lookup.field_fr_siret_lookup_line__street +msgid "Street" +msgstr "Calle" + +#. module: l10n_fr_siret_lookup +#. odoo-python +#: code:addons/l10n_fr_siret_lookup/models/res_partner.py:0 +#, python-format +msgid "The webservice data.opendatasoft.com returned an HTTP error code %s." +msgstr "" +"El servicio web data.opendatasoft.com ha devuelto un código de error HTTP %s." + +#. module: l10n_fr_siret_lookup +#: model:ir.model.fields,field_description:l10n_fr_siret_lookup.field_fr_siret_lookup_line__wizard_id +msgid "Wizard" +msgstr "Asistente" + +#. module: l10n_fr_siret_lookup +#: model:ir.model.fields,field_description:l10n_fr_siret_lookup.field_fr_siret_lookup_line__zip +msgid "Zip" +msgstr "CP" diff --git a/l10n_fr_siret_lookup/i18n/fr.po b/l10n_fr_siret_lookup/i18n/fr.po new file mode 100644 index 0000000000..e25c3c57d9 --- /dev/null +++ b/l10n_fr_siret_lookup/i18n/fr.po @@ -0,0 +1,275 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * l10n_fr_siret_lookup +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 16.0\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2025-01-06 10:09+0000\n" +"PO-Revision-Date: 2025-01-06 10:09+0000\n" +"Last-Translator: \n" +"Language-Team: \n" +"Language: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: \n" + +#. module: l10n_fr_siret_lookup +#: model:ir.model.fields,field_description:l10n_fr_siret_lookup.field_fr_siret_lookup_line__staff +msgid "# Staff" +msgstr "Nb Employés" + +#. module: l10n_fr_siret_lookup +#: model:ir.model.fields,field_description:l10n_fr_siret_lookup.field_fr_siret_lookup_line__ape +msgid "APE Code" +msgstr "Code NAF" + +#. module: l10n_fr_siret_lookup +#: model:ir.model.fields,field_description:l10n_fr_siret_lookup.field_fr_siret_lookup_line__ape_label +msgid "APE Label" +msgstr "Libellé NAF" + +#. module: l10n_fr_siret_lookup +#: model:ir.model.fields,field_description:l10n_fr_siret_lookup.field_fr_siret_lookup_line__active +msgid "Active" +msgstr "" + +#. module: l10n_fr_siret_lookup +#: model_terms:ir.ui.view,arch_db:l10n_fr_siret_lookup.fr_siret_lookup_line_form +msgid "Address" +msgstr "Adresse" + +#. module: l10n_fr_siret_lookup +#: model:ir.model.fields,field_description:l10n_fr_siret_lookup.field_fr_siret_lookup_line__category +msgid "Category" +msgstr "Catégorie" + +#. module: l10n_fr_siret_lookup +#: model:ir.model.fields,field_description:l10n_fr_siret_lookup.field_fr_siret_lookup_line__city +msgid "City" +msgstr "Ville" + +#. module: l10n_fr_siret_lookup +#: model_terms:ir.ui.view,arch_db:l10n_fr_siret_lookup.fr_siret_lookup_form +msgid "Close" +msgstr "Fermer" + +#. module: l10n_fr_siret_lookup +#: model_terms:ir.ui.view,arch_db:l10n_fr_siret_lookup.fr_siret_lookup_line_form +msgid "Closed" +msgstr "Fermée" + +#. module: l10n_fr_siret_lookup +#: model_terms:ir.ui.view,arch_db:l10n_fr_siret_lookup.fr_siret_lookup_line_form +msgid "Company" +msgstr "Société" + +#. module: l10n_fr_siret_lookup +#: model_terms:ir.ui.view,arch_db:l10n_fr_siret_lookup.fr_siret_lookup_line_form +msgid "Company Infos" +msgstr "Infos Entreprise" + +#. module: l10n_fr_siret_lookup +#: model:ir.model,name:l10n_fr_siret_lookup.model_fr_siret_lookup_line +msgid "Company Selection" +msgstr "Sélection Entreprise" + +#. module: l10n_fr_siret_lookup +#: model_terms:ir.ui.view,arch_db:l10n_fr_siret_lookup.fr_siret_lookup_form +#: model_terms:ir.ui.view,arch_db:l10n_fr_siret_lookup.fr_siret_lookup_line_form +msgid "Confirm selected company ?" +msgstr "Valider l'entreprise sélectionnée ?" + +#. module: l10n_fr_siret_lookup +#: model:ir.model,name:l10n_fr_siret_lookup.model_res_partner +msgid "Contact" +msgstr "" + +#. module: l10n_fr_siret_lookup +#: model:ir.model.fields,field_description:l10n_fr_siret_lookup.field_fr_siret_lookup_line__country_id +msgid "Country" +msgstr "Pays" + +#. module: l10n_fr_siret_lookup +#: model:ir.model.fields,field_description:l10n_fr_siret_lookup.field_fr_siret_lookup__create_uid +#: model:ir.model.fields,field_description:l10n_fr_siret_lookup.field_fr_siret_lookup_line__create_uid +msgid "Created by" +msgstr "Créé par" + +#. module: l10n_fr_siret_lookup +#: model:ir.model.fields,field_description:l10n_fr_siret_lookup.field_fr_siret_lookup__create_date +#: model:ir.model.fields,field_description:l10n_fr_siret_lookup.field_fr_siret_lookup_line__create_date +msgid "Created on" +msgstr "Créé le" + +#. module: l10n_fr_siret_lookup +#: model:ir.model.fields,field_description:l10n_fr_siret_lookup.field_fr_siret_lookup_line__creation_date +msgid "Creation Date" +msgstr "Date de création" + +#. module: l10n_fr_siret_lookup +#: model:ir.model.fields,field_description:l10n_fr_siret_lookup.field_fr_siret_lookup__display_name +#: model:ir.model.fields,field_description:l10n_fr_siret_lookup.field_fr_siret_lookup_line__display_name +msgid "Display Name" +msgstr "Nom affiché" + +#. module: l10n_fr_siret_lookup +#. odoo-python +#: code:addons/l10n_fr_siret_lookup/models/res_partner.py:0 +#, python-format +msgid "" +"Failed to query VIES.\n" +"Technical error: %s." +msgstr "" +"Erreur pendant l'interrogation du VIES.\n" +"Erreur technique : %s." + +#. module: l10n_fr_siret_lookup +#. odoo-python +#: code:addons/l10n_fr_siret_lookup/models/res_partner.py:0 +#, python-format +msgid "" +"Failure in the request on data.opendatasoft.com to create or update partner " +"from SIREN or SIRET. Technical error: %s." +msgstr "" +"Échec de la requête à l'API data.opendatasoft.com pour créer ou mettre à " +"jour le contact à partir du SIREN ou SIRET. Erreur technique : %s." + +#. module: l10n_fr_siret_lookup +#: model:ir.model,name:l10n_fr_siret_lookup.model_fr_siret_lookup +msgid "Get values from companies" +msgstr "Obtenir les informations sur les entreprises" + +#. module: l10n_fr_siret_lookup +#: model:ir.model.fields,field_description:l10n_fr_siret_lookup.field_fr_siret_lookup__id +#: model:ir.model.fields,field_description:l10n_fr_siret_lookup.field_fr_siret_lookup_line__id +msgid "ID" +msgstr "" + +#. module: l10n_fr_siret_lookup +#: model:ir.model.fields,field_description:l10n_fr_siret_lookup.field_fr_siret_lookup____last_update +#: model:ir.model.fields,field_description:l10n_fr_siret_lookup.field_fr_siret_lookup_line____last_update +msgid "Last Modified on" +msgstr "Dernière Modification le" + +#. module: l10n_fr_siret_lookup +#: model:ir.model.fields,field_description:l10n_fr_siret_lookup.field_fr_siret_lookup__write_uid +#: model:ir.model.fields,field_description:l10n_fr_siret_lookup.field_fr_siret_lookup_line__write_uid +msgid "Last Updated by" +msgstr "Dernière mise à jour par" + +#. module: l10n_fr_siret_lookup +#: model:ir.model.fields,field_description:l10n_fr_siret_lookup.field_fr_siret_lookup__write_date +#: model:ir.model.fields,field_description:l10n_fr_siret_lookup.field_fr_siret_lookup_line__write_date +msgid "Last Updated on" +msgstr "Dernière mise à jour le" + +#. module: l10n_fr_siret_lookup +#: model_terms:ir.ui.view,arch_db:l10n_fr_siret_lookup.fr_siret_lookup_line_form +msgid "Legal Infos" +msgstr "Infos Légales" + +#. module: l10n_fr_siret_lookup +#: model:ir.model.fields,field_description:l10n_fr_siret_lookup.field_fr_siret_lookup_line__legal_type +msgid "Legal Type" +msgstr "Forme Juridique" + +#. module: l10n_fr_siret_lookup +#: model_terms:ir.ui.view,arch_db:l10n_fr_siret_lookup.fr_siret_lookup_form +msgid "Lookup" +msgstr "Recherche" + +#. module: l10n_fr_siret_lookup +#: model:ir.model.fields,field_description:l10n_fr_siret_lookup.field_fr_siret_lookup_line__name +msgid "Name" +msgstr "Nom" + +#. module: l10n_fr_siret_lookup +#: model:ir.model.fields,field_description:l10n_fr_siret_lookup.field_fr_siret_lookup__name +msgid "Name to Search" +msgstr "Nom à rechercher" + +#. module: l10n_fr_siret_lookup +#: model:ir.model.fields,field_description:l10n_fr_siret_lookup.field_fr_siret_lookup__partner_id +msgid "Partner" +msgstr "Partenaire" + +#. module: l10n_fr_siret_lookup +#. odoo-python +#: code:addons/l10n_fr_siret_lookup/wizard/fr_siret_lookup.py:0 +#, python-format +msgid "Partner '%s' is not a company. This action is not relevant." +msgstr "" +"Le contact '%s' n'est pas une société. Cette action n'est pas applicable." + +#. module: l10n_fr_siret_lookup +#. odoo-python +#: code:addons/l10n_fr_siret_lookup/wizard/fr_siret_lookup.py:0 +#, python-format +msgid "Partner updated via the opendatasoft.com API." +msgstr "Contact mis à jour à partir de l'API opendatasoft.com." + +#. module: l10n_fr_siret_lookup +#: model:ir.model.fields,field_description:l10n_fr_siret_lookup.field_fr_siret_lookup__line_ids +msgid "Results" +msgstr "Résultats" + +#. module: l10n_fr_siret_lookup +#: model:ir.model.fields,field_description:l10n_fr_siret_lookup.field_fr_siret_lookup_line__siren +msgid "SIREN" +msgstr "" + +#. module: l10n_fr_siret_lookup +#: model:ir.actions.act_window,name:l10n_fr_siret_lookup.fr_siret_lookup_partner_action +msgid "SIREN Lookup" +msgstr "Recherche via l'API SIRENE" + +#. module: l10n_fr_siret_lookup +#: model_terms:ir.ui.view,arch_db:l10n_fr_siret_lookup.fr_siret_lookup_form +msgid "SIREN Lookup via opendatasoft.com" +msgstr "Recherche SIREN via opendatasoft.com" + +#. module: l10n_fr_siret_lookup +#: model:ir.actions.act_window,name:l10n_fr_siret_lookup.fr_siret_lookup_action +msgid "SIREN lookup via opendatasoft.com" +msgstr "Recherche SIREN via opendatasoft.com" + +#. module: l10n_fr_siret_lookup +#: model:ir.model.fields,field_description:l10n_fr_siret_lookup.field_fr_siret_lookup_line__siret +msgid "SIRET" +msgstr "" + +#. module: l10n_fr_siret_lookup +#: model_terms:ir.ui.view,arch_db:l10n_fr_siret_lookup.fr_siret_lookup_form +msgid "Search Results" +msgstr "Résultats de recherche" + +#. module: l10n_fr_siret_lookup +#: model_terms:ir.ui.view,arch_db:l10n_fr_siret_lookup.fr_siret_lookup_form +#: model_terms:ir.ui.view,arch_db:l10n_fr_siret_lookup.fr_siret_lookup_line_form +msgid "Select" +msgstr "Sélectionner" + +#. module: l10n_fr_siret_lookup +#: model:ir.model.fields,field_description:l10n_fr_siret_lookup.field_fr_siret_lookup_line__street +msgid "Street" +msgstr "Rue" + +#. module: l10n_fr_siret_lookup +#. odoo-python +#: code:addons/l10n_fr_siret_lookup/models/res_partner.py:0 +#, python-format +msgid "The webservice data.opendatasoft.com returned an HTTP error code %s." +msgstr "L'API data.opendatasoft.com a renvoyé le code d'erreur HTTP %s." + +#. module: l10n_fr_siret_lookup +#: model:ir.model.fields,field_description:l10n_fr_siret_lookup.field_fr_siret_lookup_line__wizard_id +msgid "Wizard" +msgstr "Assistant" + +#. module: l10n_fr_siret_lookup +#: model:ir.model.fields,field_description:l10n_fr_siret_lookup.field_fr_siret_lookup_line__zip +msgid "Zip" +msgstr "Code Postal" diff --git a/l10n_fr_siret_lookup/i18n/l10n_fr_siret_lookup.pot b/l10n_fr_siret_lookup/i18n/l10n_fr_siret_lookup.pot new file mode 100644 index 0000000000..c41ee43aff --- /dev/null +++ b/l10n_fr_siret_lookup/i18n/l10n_fr_siret_lookup.pot @@ -0,0 +1,267 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * l10n_fr_siret_lookup +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 16.0\n" +"Report-Msgid-Bugs-To: \n" +"Last-Translator: \n" +"Language-Team: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: \n" + +#. module: l10n_fr_siret_lookup +#: model:ir.model.fields,field_description:l10n_fr_siret_lookup.field_fr_siret_lookup_line__staff +msgid "# Staff" +msgstr "" + +#. module: l10n_fr_siret_lookup +#: model:ir.model.fields,field_description:l10n_fr_siret_lookup.field_fr_siret_lookup_line__ape +msgid "APE Code" +msgstr "" + +#. module: l10n_fr_siret_lookup +#: model:ir.model.fields,field_description:l10n_fr_siret_lookup.field_fr_siret_lookup_line__ape_label +msgid "APE Label" +msgstr "" + +#. module: l10n_fr_siret_lookup +#: model:ir.model.fields,field_description:l10n_fr_siret_lookup.field_fr_siret_lookup_line__active +msgid "Active" +msgstr "" + +#. module: l10n_fr_siret_lookup +#: model_terms:ir.ui.view,arch_db:l10n_fr_siret_lookup.fr_siret_lookup_line_form +msgid "Address" +msgstr "" + +#. module: l10n_fr_siret_lookup +#: model:ir.model.fields,field_description:l10n_fr_siret_lookup.field_fr_siret_lookup_line__category +msgid "Category" +msgstr "" + +#. module: l10n_fr_siret_lookup +#: model:ir.model.fields,field_description:l10n_fr_siret_lookup.field_fr_siret_lookup_line__city +msgid "City" +msgstr "" + +#. module: l10n_fr_siret_lookup +#: model_terms:ir.ui.view,arch_db:l10n_fr_siret_lookup.fr_siret_lookup_form +msgid "Close" +msgstr "" + +#. module: l10n_fr_siret_lookup +#: model_terms:ir.ui.view,arch_db:l10n_fr_siret_lookup.fr_siret_lookup_line_form +msgid "Closed" +msgstr "" + +#. module: l10n_fr_siret_lookup +#: model_terms:ir.ui.view,arch_db:l10n_fr_siret_lookup.fr_siret_lookup_line_form +msgid "Company" +msgstr "" + +#. module: l10n_fr_siret_lookup +#: model_terms:ir.ui.view,arch_db:l10n_fr_siret_lookup.fr_siret_lookup_line_form +msgid "Company Infos" +msgstr "" + +#. module: l10n_fr_siret_lookup +#: model:ir.model,name:l10n_fr_siret_lookup.model_fr_siret_lookup_line +msgid "Company Selection" +msgstr "" + +#. module: l10n_fr_siret_lookup +#: model_terms:ir.ui.view,arch_db:l10n_fr_siret_lookup.fr_siret_lookup_form +#: model_terms:ir.ui.view,arch_db:l10n_fr_siret_lookup.fr_siret_lookup_line_form +msgid "Confirm selected company ?" +msgstr "" + +#. module: l10n_fr_siret_lookup +#: model:ir.model,name:l10n_fr_siret_lookup.model_res_partner +msgid "Contact" +msgstr "" + +#. module: l10n_fr_siret_lookup +#: model:ir.model.fields,field_description:l10n_fr_siret_lookup.field_fr_siret_lookup_line__country_id +msgid "Country" +msgstr "" + +#. module: l10n_fr_siret_lookup +#: model:ir.model.fields,field_description:l10n_fr_siret_lookup.field_fr_siret_lookup__create_uid +#: model:ir.model.fields,field_description:l10n_fr_siret_lookup.field_fr_siret_lookup_line__create_uid +msgid "Created by" +msgstr "" + +#. module: l10n_fr_siret_lookup +#: model:ir.model.fields,field_description:l10n_fr_siret_lookup.field_fr_siret_lookup__create_date +#: model:ir.model.fields,field_description:l10n_fr_siret_lookup.field_fr_siret_lookup_line__create_date +msgid "Created on" +msgstr "" + +#. module: l10n_fr_siret_lookup +#: model:ir.model.fields,field_description:l10n_fr_siret_lookup.field_fr_siret_lookup_line__creation_date +msgid "Creation Date" +msgstr "" + +#. module: l10n_fr_siret_lookup +#: model:ir.model.fields,field_description:l10n_fr_siret_lookup.field_fr_siret_lookup__display_name +#: model:ir.model.fields,field_description:l10n_fr_siret_lookup.field_fr_siret_lookup_line__display_name +msgid "Display Name" +msgstr "" + +#. module: l10n_fr_siret_lookup +#. odoo-python +#: code:addons/l10n_fr_siret_lookup/models/res_partner.py:0 +#, python-format +msgid "" +"Failed to query VIES.\n" +"Technical error: %s." +msgstr "" + +#. module: l10n_fr_siret_lookup +#. odoo-python +#: code:addons/l10n_fr_siret_lookup/models/res_partner.py:0 +#, python-format +msgid "" +"Failure in the request on data.opendatasoft.com to create or update partner " +"from SIREN or SIRET. Technical error: %s." +msgstr "" + +#. module: l10n_fr_siret_lookup +#: model:ir.model,name:l10n_fr_siret_lookup.model_fr_siret_lookup +msgid "Get values from companies" +msgstr "" + +#. module: l10n_fr_siret_lookup +#: model:ir.model.fields,field_description:l10n_fr_siret_lookup.field_fr_siret_lookup__id +#: model:ir.model.fields,field_description:l10n_fr_siret_lookup.field_fr_siret_lookup_line__id +msgid "ID" +msgstr "" + +#. module: l10n_fr_siret_lookup +#: model:ir.model.fields,field_description:l10n_fr_siret_lookup.field_fr_siret_lookup____last_update +#: model:ir.model.fields,field_description:l10n_fr_siret_lookup.field_fr_siret_lookup_line____last_update +msgid "Last Modified on" +msgstr "" + +#. module: l10n_fr_siret_lookup +#: model:ir.model.fields,field_description:l10n_fr_siret_lookup.field_fr_siret_lookup__write_uid +#: model:ir.model.fields,field_description:l10n_fr_siret_lookup.field_fr_siret_lookup_line__write_uid +msgid "Last Updated by" +msgstr "" + +#. module: l10n_fr_siret_lookup +#: model:ir.model.fields,field_description:l10n_fr_siret_lookup.field_fr_siret_lookup__write_date +#: model:ir.model.fields,field_description:l10n_fr_siret_lookup.field_fr_siret_lookup_line__write_date +msgid "Last Updated on" +msgstr "" + +#. module: l10n_fr_siret_lookup +#: model_terms:ir.ui.view,arch_db:l10n_fr_siret_lookup.fr_siret_lookup_line_form +msgid "Legal Infos" +msgstr "" + +#. module: l10n_fr_siret_lookup +#: model:ir.model.fields,field_description:l10n_fr_siret_lookup.field_fr_siret_lookup_line__legal_type +msgid "Legal Type" +msgstr "" + +#. module: l10n_fr_siret_lookup +#: model_terms:ir.ui.view,arch_db:l10n_fr_siret_lookup.fr_siret_lookup_form +msgid "Lookup" +msgstr "" + +#. module: l10n_fr_siret_lookup +#: model:ir.model.fields,field_description:l10n_fr_siret_lookup.field_fr_siret_lookup_line__name +msgid "Name" +msgstr "" + +#. module: l10n_fr_siret_lookup +#: model:ir.model.fields,field_description:l10n_fr_siret_lookup.field_fr_siret_lookup__name +msgid "Name to Search" +msgstr "" + +#. module: l10n_fr_siret_lookup +#: model:ir.model.fields,field_description:l10n_fr_siret_lookup.field_fr_siret_lookup__partner_id +msgid "Partner" +msgstr "" + +#. module: l10n_fr_siret_lookup +#. odoo-python +#: code:addons/l10n_fr_siret_lookup/wizard/fr_siret_lookup.py:0 +#, python-format +msgid "Partner '%s' is not a company. This action is not relevant." +msgstr "" + +#. module: l10n_fr_siret_lookup +#. odoo-python +#: code:addons/l10n_fr_siret_lookup/wizard/fr_siret_lookup.py:0 +#, python-format +msgid "Partner updated via the opendatasoft.com API." +msgstr "" + +#. module: l10n_fr_siret_lookup +#: model:ir.model.fields,field_description:l10n_fr_siret_lookup.field_fr_siret_lookup__line_ids +msgid "Results" +msgstr "" + +#. module: l10n_fr_siret_lookup +#: model:ir.model.fields,field_description:l10n_fr_siret_lookup.field_fr_siret_lookup_line__siren +msgid "SIREN" +msgstr "" + +#. module: l10n_fr_siret_lookup +#: model:ir.actions.act_window,name:l10n_fr_siret_lookup.fr_siret_lookup_partner_action +msgid "SIREN Lookup" +msgstr "" + +#. module: l10n_fr_siret_lookup +#: model_terms:ir.ui.view,arch_db:l10n_fr_siret_lookup.fr_siret_lookup_form +msgid "SIREN Lookup via opendatasoft.com" +msgstr "" + +#. module: l10n_fr_siret_lookup +#: model:ir.actions.act_window,name:l10n_fr_siret_lookup.fr_siret_lookup_action +msgid "SIREN lookup via opendatasoft.com" +msgstr "" + +#. module: l10n_fr_siret_lookup +#: model:ir.model.fields,field_description:l10n_fr_siret_lookup.field_fr_siret_lookup_line__siret +msgid "SIRET" +msgstr "" + +#. module: l10n_fr_siret_lookup +#: model_terms:ir.ui.view,arch_db:l10n_fr_siret_lookup.fr_siret_lookup_form +msgid "Search Results" +msgstr "" + +#. module: l10n_fr_siret_lookup +#: model_terms:ir.ui.view,arch_db:l10n_fr_siret_lookup.fr_siret_lookup_form +#: model_terms:ir.ui.view,arch_db:l10n_fr_siret_lookup.fr_siret_lookup_line_form +msgid "Select" +msgstr "" + +#. module: l10n_fr_siret_lookup +#: model:ir.model.fields,field_description:l10n_fr_siret_lookup.field_fr_siret_lookup_line__street +msgid "Street" +msgstr "" + +#. module: l10n_fr_siret_lookup +#. odoo-python +#: code:addons/l10n_fr_siret_lookup/models/res_partner.py:0 +#, python-format +msgid "The webservice data.opendatasoft.com returned an HTTP error code %s." +msgstr "" + +#. module: l10n_fr_siret_lookup +#: model:ir.model.fields,field_description:l10n_fr_siret_lookup.field_fr_siret_lookup_line__wizard_id +msgid "Wizard" +msgstr "" + +#. module: l10n_fr_siret_lookup +#: model:ir.model.fields,field_description:l10n_fr_siret_lookup.field_fr_siret_lookup_line__zip +msgid "Zip" +msgstr "" diff --git a/l10n_fr_siret_lookup/models/__init__.py b/l10n_fr_siret_lookup/models/__init__.py new file mode 100644 index 0000000000..26e065c1b0 --- /dev/null +++ b/l10n_fr_siret_lookup/models/__init__.py @@ -0,0 +1,3 @@ +from . import res_company +from . import res_config_settings +from . import res_partner diff --git a/l10n_fr_siret_lookup/models/res_company.py b/l10n_fr_siret_lookup/models/res_company.py new file mode 100644 index 0000000000..dce1aa5462 --- /dev/null +++ b/l10n_fr_siret_lookup/models/res_company.py @@ -0,0 +1,14 @@ +# Copyright 2025 Le Filament (https://le-filament.com) +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). + +from odoo import fields, models + + +class ResCompany(models.Model): + _inherit = "res.company" + + force_vat_siret_lookup = fields.Boolean( + string="Force VAT Numbers during SIRET Lookups if VIES check times out " + "or is disabled", + default=False, + ) diff --git a/l10n_fr_siret_lookup/models/res_config_settings.py b/l10n_fr_siret_lookup/models/res_config_settings.py new file mode 100644 index 0000000000..4539764470 --- /dev/null +++ b/l10n_fr_siret_lookup/models/res_config_settings.py @@ -0,0 +1,15 @@ +# Copyright 2025 Le Filament (https://le-filament.com) +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). + +from odoo import fields, models + + +class ResConfigSettings(models.TransientModel): + _inherit = "res.config.settings" + + force_vat_siret_lookup = fields.Boolean( + related="company_id.force_vat_siret_lookup", + readonly=False, + string="Force VAT Numbers during SIRET Lookups if VIES check times out " + "or is disabled", + ) diff --git a/l10n_fr_siret_lookup/models/res_partner.py b/l10n_fr_siret_lookup/models/res_partner.py new file mode 100644 index 0000000000..19398bb910 --- /dev/null +++ b/l10n_fr_siret_lookup/models/res_partner.py @@ -0,0 +1,326 @@ +# Copyright 2018-2022 Le Filament () +# Copyright 2021-2022 Akretion France (http://www.akretion.com/) +# @author: Alexis de Lattre +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). + +import logging + +import requests + +from odoo import api, models +from odoo.exceptions import UserError + +logger = logging.getLogger(__name__) +try: + from stdnum.eu.vat import check_vies + from stdnum.fr.siren import is_valid as siren_is_valid + from stdnum.fr.siren import to_tva as siren_to_vat + from stdnum.fr.siret import is_valid as siret_is_valid +except ImportError: + logger.debug("Cannot import stdnum") + +TIMEOUT = 5 + + +class ResPartner(models.Model): + _inherit = "res.partner" + + @api.model + def _opendatasoft_fields_list(self): + return [ + "datefermetureunitelegale", + "datefermetureetablissement", + "denominationunitelegale", + "l1_adressage_unitelegale", + "adresseetablissement", + "codepostaletablissement", + "libellecommuneetablissement", + "siren", + "nic", + "codedepartementetablissement", + # for the wizard + "siret", + "categorieentreprise", + "datecreationunitelegale", + "activiteprincipaleunitelegale", + "divisionunitelegale", + "naturejuridiqueunitelegale", + "trancheeffectifsunitelegale", + "etatadministratifetablissement", + ] + + @api.model + def _opendatasoft_get_raw_data( + self, query, raise_if_fail=False, exclude_dead=False, rows=10 + ): + assert isinstance(query, str) + assert isinstance(rows, int) and rows > 0 + url = "https://data.opendatasoft.com/api/records/1.0/search/" + params = { + "dataset": "economicref-france-sirene-v3@public", + "q": query, + "rows": rows, + "fields": ",".join(self._opendatasoft_fields_list()), + } + # It seems that datefermetureetablissement and datefermetureunitelegale + # often have a value for a SIRET that is still open + # For example, SIRET 55208131766522 (siège social d'EDF) + # has datefermetureetablissement=2009-02-22 + # and datefermetureunitelegale=2018-12-01 !!! + # So I now set exclude_dead=False by default + if exclude_dead: + params["q"] += ( + " AND #null(datefermetureetablissement)" + " AND #null(datefermetureunitelegale)" + ) + try: + logger.info("Sending query to https://data.opendatasoft.com/api") + logger.debug(f"url={url} params={params}") + res = requests.get(url, params=params, timeout=TIMEOUT) + if res.status_code in (200, 201): + res_json = res.json() + # from pprint import pprint + # pprint(res_json) + return res_json + else: + logger.warning( + f"HTTP error {res.status_code} returned by " + "GET on data.opendatasoft.com/api" + ) + if raise_if_fail: + raise UserError( + self.env._( + "The webservice data.opendatasoft.com " + f"returned an HTTP error code {res.status_code}." + ) + ) + except Exception as e: + logger.warning(f"Failure in the GET request on data.opendatasoft.com: {e}") + if raise_if_fail: + raise UserError( + self.env._( + "Failure in the request on data.opendatasoft.com " + "to create or update partner from SIREN or SIRET. " + f"Technical error: {e}." + ) + ) from e + return False + + @api.model + def _opendatasoft_parse_record(self, raw_record, exclude_dead=False): + res = False + if raw_record and isinstance(raw_record, dict): + if exclude_dead and raw_record.get("datefermetureunitelegale"): + return res + if exclude_dead and raw_record.get("datefermetureetablissement"): + return res + res = { + "name": raw_record.get("denominationunitelegale") + or raw_record.get("l1_adressage_unitelegale"), + "street": raw_record.get("adresseetablissement"), + "city": raw_record.get("libellecommuneetablissement"), + "siren": raw_record.get("siren") and str(raw_record["siren"]) or False, + "nic": raw_record.get("nic"), + } + # In feb 2022, they changed codepostaletablissement and + # codedepartementetablissement from string to integer + # So I now want to support both, it case they change it back ! + if raw_record.get("codepostaletablissement"): + res["zip"] = raw_record["codepostaletablissement"] + if isinstance(res["zip"], int): + res["zip"] = str(res["zip"]) + res["zip"] = res["zip"].zfill(5) + + # I don't use "codedepartementetablissement" to compute + # the country, because it is not always set, in particular + # for partners in Corsica + if res.get("zip"): + res["country_id"] = self._opendatasoft_compute_country(res["zip"]) + # set lang to French if installed + fr_lang = self.env["res.lang"].search([("code", "=", "fr_FR")]) + if fr_lang: + res["lang"] = "fr_FR" + if res.get("siren"): + vat, vies_valid = self._siren2vat_vies(res["siren"]) + res["vat"] = vat + res["vies_valid"] = vies_valid + return res + + @api.model + def _opendatasoft_compute_country(self, zipcode): + domtom2xmlid = { + "971": "gp", + "972": "mq", + "973": "gf", + "974": "re", + "975": "pm", # Saint Pierre and Miquelon + "976": "yt", # Mayotte + "977": "bl", # Saint-Barthélemy + "978": "mf", # Saint-Martin + "986": "wf", # Wallis-et-Futuna + "987": "pf", # Polynésie française + "988": "nc", # Nouvelle calédonie + } + country_id = self.env.ref("base.fr").id + if ( + isinstance(zipcode, str) + and len(zipcode) == 5 + and zipcode[:3] in domtom2xmlid + ): + country_xmlid = f"base.{domtom2xmlid[zipcode[:3]]}" + country_id = self.env.ref(country_xmlid).id + return country_id + + @api.model + def _siren2vat_vies(self, siren, raise_if_fail=False): + """ + Function checking VAT number generated from SIREN + Returns 2 values : + - char: VAT number (or None if not valid / not tested and not forced) + - bool: vies_valid (if validated by VIES server) + """ + vat = f"FR{siren_to_vat(siren)}" + # Default return empty values + empty_res = False, False + # If we do not want to check VIES server + if not self.env.company.vat_check_vies: + # If we still want to use computed value without verification + if self.env.company.force_vat_siret_lookup: + return vat, False + else: + return empty_res + + logger.info(f"VIES check of VAT {vat}") + vies_res = False + try: + vies_res = check_vies(vat, timeout=TIMEOUT) + logger.debug(f"VIES answer vies_res.valid={vies_res['valid']}") + except Exception as e: + logger.warning(f"VIES query failed: {e}") + if not self.env.company.vat_check_vies and raise_if_fail: + raise UserError( + self.env._(f"Failed to query VIES.\nTechnical error: {e}.") + ) from e + # If exception is raised but we still want to force computed value + # We return vat number and vies_valid = False + elif self.env.company.force_vat_siret_lookup: + return vat, False + return empty_res + # If VIES validates vat we return the VAT value and vies_valid = True + # Otherwise we return False / False + if vies_res and vies_res["valid"]: + return vat, True + return empty_res + + @api.model + def _opendatasoft_get_first_result(self, query, raise_if_fail=False): + res_json = self._opendatasoft_get_raw_data(query, raise_if_fail=raise_if_fail) + if res_json and "records" in res_json: + if len(res_json["records"]) > 0: + raw_record = res_json["records"][0].get("fields") + if raw_record: + return self._opendatasoft_parse_record(raw_record) + else: + logger.warning("The query on opendatasoft.com returned 0 records") + return False + + @api.model + def _opendatasoft_get_from_siren(self, siren): + if siren and siren_is_valid(siren): + vals = self._opendatasoft_get_first_result( + f"siren:{siren} AND etablissementsiege:oui", + ) + if vals and vals.get("siren") == siren: + return vals + return False + + @api.model + def _opendatasoft_get_from_siret(self, siret): + if siret and siret_is_valid(siret): + vals = self._opendatasoft_get_first_result(f"siret:{siret}") + if vals and vals.get("siren") and vals.get("nic"): + vals_siret = vals["siren"] + vals["nic"] + if vals_siret == siret: + return vals + return False + + @api.onchange("siren") + def siren_onchange(self): + if ( + self.siren + and siren_is_valid(self.siren) + and not self.name + and self.is_company + and not self.parent_id + ): + if self.nic: + # We only execute the query if the full SIRET is OK + vals = False + if siret_is_valid(self.siren + self.nic): + siret = self.siren + self.nic + vals = self._opendatasoft_get_from_siret(siret) + else: + vals = self._opendatasoft_get_from_siren(self.siren) + if vals: + self.update(vals) + + @api.onchange("siret") + def siret_onchange(self): + if ( + self.siret + and siret_is_valid(self.siret) + and not self.name + and self.is_company + and not self.parent_id + ): + vals = self._opendatasoft_get_from_siret(self.siret) + if vals: + self.update(vals) + + @api.onchange("vat") + def vat_onchange(self): + if ( + self.vat + and not self.name + and not self.siren + and not self.siret + and self.is_company + and not self.parent_id + ): + vat = self.vat.replace(" ", "").upper() + if vat and vat.startswith("FR") and len(vat) == 13: + siren = vat[4:] + if siren_is_valid(siren): + vals = self._opendatasoft_get_from_siren(siren) + if vals: + self.update(vals) + + @api.onchange("name") + def siren_siret_vat_in_name_onchange(self): + if ( + self.name + and self.is_company + and not self.parent_id + and not self.siren + and not self.nic + and not self.siret + and not self.street + and not self.city + and not self.zip + ): + name = self.name.replace(" ", "") + if name: + vals = False + if len(name) == 9 and name.isdigit() and siren_is_valid(name): + vals = self._opendatasoft_get_from_siren(name) + elif len(name) == 14 and name.isdigit() and siret_is_valid(name): + vals = self._opendatasoft_get_from_siret(name) + elif ( + len(name) == 13 + and name[:2] == "FR" + and name[2:].isdigit() + and siren_is_valid(name[4:]) + ): + vals = self._opendatasoft_get_from_siren(name[4:]) + if vals: + self.update(vals) diff --git a/l10n_fr_siret_lookup/pyproject.toml b/l10n_fr_siret_lookup/pyproject.toml new file mode 100644 index 0000000000..4231d0cccb --- /dev/null +++ b/l10n_fr_siret_lookup/pyproject.toml @@ -0,0 +1,3 @@ +[build-system] +requires = ["whool"] +build-backend = "whool.buildapi" diff --git a/l10n_fr_siret_lookup/readme/CONFIGURE.md b/l10n_fr_siret_lookup/readme/CONFIGURE.md new file mode 100644 index 0000000000..b9c8fce795 --- /dev/null +++ b/l10n_fr_siret_lookup/readme/CONFIGURE.md @@ -0,0 +1,9 @@ +You can use this module without configuration. + +However 2 configuration parameter can be adjusted in *Invoicing* > Configuration > Settings : +- **Verify VAT Numbers** (vat_check_vies field from base_vat module): this parameter will define whether you want check computed VAT number against EU VIES validation service +- **Force VAT Numbers during SIRET Lookups if VIES check times out or is disabled** (force_vat_siret_lookup): this parameter allows to force use of computed VAT number even if not checked agains EU VIES validation service or if an Exception is raised by EU VIES validation (for instance because of Timeout, which are quite frequent while checking for FR VAT) + +The 2 above parameters are company dependent. + +*Note:* if EU VIES validation service reports that VAT number is incorrect, the VAT field is emptied (even if Force... parameter is ticked) diff --git a/l10n_fr_siret_lookup/readme/CONTRIBUTORS.md b/l10n_fr_siret_lookup/readme/CONTRIBUTORS.md new file mode 100644 index 0000000000..5b9de08914 --- /dev/null +++ b/l10n_fr_siret_lookup/readme/CONTRIBUTORS.md @@ -0,0 +1,3 @@ +- Benjamin Rivier \ () +- Remi Cazenave \ () +- Alexis de Lattre \<\> diff --git a/l10n_fr_siret_lookup/readme/CREDITS.md b/l10n_fr_siret_lookup/readme/CREDITS.md new file mode 100644 index 0000000000..bc221931d5 --- /dev/null +++ b/l10n_fr_siret_lookup/readme/CREDITS.md @@ -0,0 +1,3 @@ +The development of this module has been financially supported by: + +- Le Filament diff --git a/l10n_fr_siret_lookup/readme/DESCRIPTION.md b/l10n_fr_siret_lookup/readme/DESCRIPTION.md new file mode 100644 index 0000000000..c90ba3e4ad --- /dev/null +++ b/l10n_fr_siret_lookup/readme/DESCRIPTION.md @@ -0,0 +1,28 @@ +This modules updates partner fields via the SIRENE database +\. It uses the dataset +*economicref-france-sirene-v3* of opendatasoft +\. + +It computes a theorical VAT number from the SIREN and then checks the +validity of the VAT number (depending on configuration) on +[VIES](https://ec.europa.eu/taxation_customs/vies/) (if invalid, the VAT +number is discarded). + +The module supports 2 scenarios: + +- update of an existing partner via the menu *Action \> SIREN Lookup*, +- creation of a new partner: start by setting the VAT number field, the + SIREN field or SIRET field and Odoo will set the other fields. For + usability purposes, it also work when you write the VAT number, SIREN + or SIRET in the company name field. + +In the 2 scenarios, it will update the following fields: + +- Company Name +- Street +- Postal Code +- City +- Country +- SIREN and NIC (i.e. SIRET) +- VAT Number +- Language (creation scenario only) diff --git a/l10n_fr_siret_lookup/readme/INSTALL.md b/l10n_fr_siret_lookup/readme/INSTALL.md new file mode 100644 index 0000000000..382ab5b455 --- /dev/null +++ b/l10n_fr_siret_lookup/readme/INSTALL.md @@ -0,0 +1 @@ +It's recommended to install `python-stdnum>=1.18` for SIRET support. diff --git a/l10n_fr_siret_lookup/readme/USAGE.md b/l10n_fr_siret_lookup/readme/USAGE.md new file mode 100644 index 0000000000..c6622a19e1 --- /dev/null +++ b/l10n_fr_siret_lookup/readme/USAGE.md @@ -0,0 +1,11 @@ +To update an existing partner, go to the partner form view and click on +*Action \> SIREN Lookup*. + +By default, the search field is filled with Company name. To get more +accurate results, you may want to add the City name where the company is +registered. Then click on *Lookup*. + +A list of companies is displayed. You may want to click on one in order +to see corresponding information or directly select company from list +view. Once a company is selected, the partner information is updated and +a message is logged in the chatter. diff --git a/l10n_fr_siret_lookup/security/ir.model.access.csv b/l10n_fr_siret_lookup/security/ir.model.access.csv new file mode 100644 index 0000000000..2a47e44867 --- /dev/null +++ b/l10n_fr_siret_lookup/security/ir.model.access.csv @@ -0,0 +1,3 @@ +id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink +access_fr_siret_lookup,Full access on fr.siret.lookup to partner manager,model_fr_siret_lookup,base.group_partner_manager,1,1,1,1 +access_fr_siret_lookup_line,Full access on fr.siret.lookup.line to partner manager,model_fr_siret_lookup_line,base.group_partner_manager,1,1,1,1 diff --git a/l10n_fr_siret_lookup/static/description/icon.png b/l10n_fr_siret_lookup/static/description/icon.png new file mode 100644 index 0000000000..3a0328b516 Binary files /dev/null and b/l10n_fr_siret_lookup/static/description/icon.png differ diff --git a/l10n_fr_siret_lookup/static/description/index.html b/l10n_fr_siret_lookup/static/description/index.html new file mode 100644 index 0000000000..68a87fab16 --- /dev/null +++ b/l10n_fr_siret_lookup/static/description/index.html @@ -0,0 +1,502 @@ + + + + + +SIRET Lookup + + + +
+

SIRET Lookup

+ + +

Beta License: AGPL-3 OCA/l10n-france Translate me on Weblate Try me on Runboat

+

This modules updates partner fields via the SIRENE database +<https://data.opendatasoft.com\>. +It uses the dataset economicref-france-sirene-v3 of opendatasoft +<https://public.opendatasoft.com/explore/dataset/economicref-france-sirene-v3/information/\>.

+

It computes a theorical VAT number from the SIREN and then checks the +validity of the VAT number (depending on configuration) on +VIES (if invalid, the +VAT number is discarded).

+

The module supports 2 scenarios:

+
    +
  • update of an existing partner via the menu Action > SIREN Lookup,
  • +
  • creation of a new partner: start by setting the VAT number field, the +SIREN field or SIRET field and Odoo will set the other fields. For +usability purposes, it also work when you write the VAT number, SIREN +or SIRET in the company name field.
  • +
+

In the 2 scenarios, it will update the following fields:

+
    +
  • Company Name
  • +
  • Street
  • +
  • Postal Code
  • +
  • City
  • +
  • Country
  • +
  • SIREN and NIC (i.e. SIRET)
  • +
  • VAT Number
  • +
  • Language (creation scenario only)
  • +
+

Table of contents

+ +
+

Installation

+

It’s recommended to install python-stdnum>=1.18 for SIRET support.

+
+
+

Configuration

+

You can use this module without configuration.

+

However 2 configuration parameter can be adjusted in Invoicing > +Configuration > Settings :

+
    +
  • Verify VAT Numbers (vat_check_vies field from base_vat module): +this parameter will define whether you want check computed VAT number +against EU VIES validation service
  • +
  • Force VAT Numbers during SIRET Lookups if VIES check times out or +is disabled (force_vat_siret_lookup): this parameter allows to +force use of computed VAT number even if not checked agains EU VIES +validation service or if an Exception is raised by EU VIES validation +(for instance because of Timeout, which are quite frequent while +checking for FR VAT)
  • +
+

The 2 above parameters are company dependent.

+

Note: if EU VIES validation service reports that VAT number is +incorrect, the VAT field is emptied (even if Force… parameter is +ticked)

+
+
+

Usage

+

To update an existing partner, go to the partner form view and click on +Action > SIREN Lookup.

+

By default, the search field is filled with Company name. To get more +accurate results, you may want to add the City name where the company is +registered. Then click on Lookup.

+

A list of companies is displayed. You may want to click on one in order +to see corresponding information or directly select company from list +view. Once a company is selected, the partner information is updated and +a message is logged in the chatter.

+
+
+

Bug Tracker

+

Bugs are tracked on GitHub Issues. +In case of trouble, please check there if your issue has already been reported. +If you spotted it first, help us to smash it by providing a detailed and welcomed +feedback.

+

Do not contact contributors directly about support or help with technical issues.

+
+
+

Credits

+
+

Authors

+
    +
  • Le Filament
  • +
  • Akretion
  • +
+
+
+

Contributors

+ +
+
+

Other credits

+

The development of this module has been financially supported by:

+
    +
  • Le Filament
  • +
+
+
+

Maintainers

+

This module is maintained by the OCA.

+ +Odoo Community Association + +

OCA, or the Odoo Community Association, is a nonprofit organization whose +mission is to support the collaborative development of Odoo features and +promote its widespread use.

+

Current maintainers:

+

remi-filament alexis-via

+

This module is part of the OCA/l10n-france project on GitHub.

+

You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.

+
+
+
+ + diff --git a/l10n_fr_siret_lookup/tests/__init__.py b/l10n_fr_siret_lookup/tests/__init__.py new file mode 100644 index 0000000000..bc42cafe73 --- /dev/null +++ b/l10n_fr_siret_lookup/tests/__init__.py @@ -0,0 +1 @@ +from . import test_validate_siret_lookup diff --git a/l10n_fr_siret_lookup/tests/test_validate_siret_lookup.py b/l10n_fr_siret_lookup/tests/test_validate_siret_lookup.py new file mode 100644 index 0000000000..2998d02f43 --- /dev/null +++ b/l10n_fr_siret_lookup/tests/test_validate_siret_lookup.py @@ -0,0 +1,322 @@ +# Copyright 2025 Le Filament (https://le-filament.com) +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). + +import logging +from unittest.mock import patch + +from odoo.tests import Form +from odoo.tests.common import TransactionCase + + +class TestStructure(TransactionCase): + @classmethod + def setUpClass(cls): + def check_vies(vat_number, timeout=10): + if cls.vies_timeout: + raise Exception("Timeout") + return {"valid": vat_number == "FR58944716448"} + + super().setUpClass() + cls.env.user.company_id.vat_check_vies = True + cls.vies_timeout = False + cls.env.user.company_id.force_vat_siret_lookup = False + cls._vies_check_func = check_vies + cls._partner_odoo_fr_vals = { + "name": "ODOO FR", + "street": "3087 Rue DE LA GARE", + "zip": "59299", + "city": "BOESCHEPE", + "country_id": cls.env.ref("base.fr"), + "lang": "fr_FR", + "siret": "94471644800013", + "nic": "00013", + "siren": "944716448", + "vat": "FR58944716448", + "vies_valid": True, + } + cls._partner_oca_fr_vals = { + "name": "ODOO COMMUNITY ASSOCIATION FRANCE", + "street": "920 ROUTE DE L'ISLE SUR SORGUE", + "zip": "84250", + "city": "LE THOR", + "country_id": cls.env.ref("base.fr"), + "lang": "fr_FR", + "siret": "99151642800018", + "nic": "00018", + "siren": "991516428", + "vat": "FR64991516428", + "vies_valid": False, + } + cls.env["res.lang"]._activate_lang("fr_FR") + + def test_lookup(self): + # Mock opendatasoft API call + def mock_api_call( + self, query, raise_if_fail=False, exclude_dead=False, rows=10 + ): + if query.startswith("siren:944716448") or query.startswith( + "siret:94471644800013" + ): + return { + "nhits": 1, + "parameters": { + "dataset": ["economicref-france-sirene-v3@public"], + "q": "siren:944716448 AND etablissementsiege:oui", + "rows": 10, + "start": 0, + "format": "json", + "timezone": "UTC", + "fields": [ + "datefermetureunitelegale", + "datefermetureetablissement", + "denominationunitelegale", + "l1_adressage_unitelegale", + "adresseetablissement", + "codepostaletablissement", + "libellecommuneetablissement", + "siren", + "nic", + "codedepartementetablissement", + "siret", + "categorieentreprise", + "datecreationunitelegale", + "activiteprincipaleunitelegale", + "divisionunitelegale", + "naturejuridiqueunitelegale", + "trancheeffectifsunitelegale", + "etatadministratifetablissement", + ], + }, + "records": [ + { + "datasetid": "economicref-france-sirene-v3@public", + "recordid": "97596c5f8d966090f36dcdf55083cd1839cf14d5", + "fields": { + "libellecommuneetablissement": "BOESCHEPE", + "codepostaletablissement": "59299", + "adresseetablissement": "3087 Rue DE LA GARE", + "siret": "94471644800013", + "nic": "00013", + "siren": "944716448", + "naturejuridiqueunitelegale": "Soci\u00e9t\u00e9 \u00e0" + " responsabilit\u00e9 limit\u00e9e" + "(sans autre indication)", + "divisionunitelegale": "\u00c9dition de logiciels", + "etatadministratifetablissement": "Actif", + "codedepartementetablissement": "59", + "datecreationunitelegale": "2025-05-16", + "activiteprincipaleunitelegale": "58.29C", + "denominationunitelegale": "ODOO FR", + }, + "record_timestamp": "2025-07-07T07:38:00Z", + } + ], + } + elif query.startswith("siren:991516428") or query.startswith( + "siret:99151642800018" + ): + return { + "nhits": 1, + "parameters": { + "dataset": ["economicref-france-sirene-v3@public"], + "q": "siren:991516428 AND etablissementsiege:oui", + "rows": 10, + "start": 0, + "format": "json", + "timezone": "UTC", + "fields": [ + "datefermetureunitelegale", + "datefermetureetablissement", + "denominationunitelegale", + "l1_adressage_unitelegale", + "adresseetablissement", + "codepostaletablissement", + "libellecommuneetablissement", + "siren", + "nic", + "codedepartementetablissement", + "siret", + "categorieentreprise", + "datecreationunitelegale", + "activiteprincipaleunitelegale", + "divisionunitelegale", + "naturejuridiqueunitelegale", + "trancheeffectifsunitelegale", + "etatadministratifetablissement", + ], + }, + "records": [ + { + "datasetid": "economicref-france-sirene-v3@public", + "recordid": "97596c5f8d966090f36dcdf55083cd1839cf14d6", + "fields": { + "libellecommuneetablissement": "LE THOR", + "codepostaletablissement": "84250", + "adresseetablissement": "920 ROUTE DE L'ISLE SUR " + "SORGUE", + "siret": "99151642800018", + "nic": "00018", + "siren": "991516428", + "naturejuridiqueunitelegale": "Association déclarée", + "divisionunitelegale": "Services d'information", + "etatadministratifetablissement": "Actif", + "codedepartementetablissement": "84", + "datecreationunitelegale": "2025-08-14", + "activiteprincipaleunitelegale": "63.99C", + "denominationunitelegale": "ODOO COMMUNITY ASSOCIATION " + "FRANCE", + }, + "record_timestamp": "2025-07-07T07:38:00Z", + } + ], + } + else: + return {} + + # Using mocked functions for API call to opendatasoft and VIES + with ( + patch( + "odoo.addons.l10n_fr_siret_lookup.models.res_partner.check_vies", + type(self)._vies_check_func, + ), + patch.object( + type(self.env["res.partner"]), + "_opendatasoft_get_raw_data", + mock_api_call, + ), + ): + # For each of the 2 partners (one with valid VAT, one with invalid VAT) + for vals in [self._partner_odoo_fr_vals, self._partner_oca_fr_vals]: + # We test the various on change : + # - Setting SIREN, SIRET or VAT in "name" field + # - Setting SIREN in "siren" field + # - Setting SIRET in "siret" field + # - Setting VAT in "vat" field + for form_input, field in [ + ("name", "siren"), + ("name", "siret"), + ("name", "vat"), + ("siren", "siren"), + ("siret", "siret"), + ("vat", "vat"), + ]: + with ( + Form(self.env["res.partner"]) as partner_form, + ( + self.assertLogs(level=logging.WARNING) + if self.vies_timeout + else self.assertNoLogs(level=logging.WARNING) + ) as logs, + ): + # First we set company type so that name becomes readwrite + partner_form.company_type = "company" + # Set the field value in form_input + partner_form[form_input] = vals[field] + + # Catch warning on VIES timeout only + if self.vies_timeout: + self.assertEqual(len(logs.records), 1) + self.assertEqual(logs.records[0].levelno, logging.WARNING) + + # Check all values wrt dict stored in test + for value in vals: + # Compare strings + if isinstance(vals[value], str): + # Specific test for "vat" field + # In any of the following cases "vat" should be false : + # - if we check VIES, VAT is invalid AND no timeout + # - if we check VIES, timeout and VAT is not forced + # - if we do not check VIES and VAT is not forced + if value == "vat" and ( + ( + self.env.user.company_id.vat_check_vies + and not vals["vies_valid"] + and not self.vies_timeout + ) + or ( + self.env.user.company_id.vat_check_vies + and self.vies_timeout + and not self.env.user.company_id.force_vat_siret_lookup # noqa: E501 + ) + or ( + not self.env.user.company_id.vat_check_vies + and not self.env.user.company_id.force_vat_siret_lookup # noqa: E501 + ) + ): + self.assertFalse( + partner_form[value], + f"{value} was invalid and therefore not " + "filled in name", + ) + continue + # Otherwise it should be the "vat" value from dict + self.assertEqual( + partner_form[value], + vals[value], + f"{value} was detected from {field} " + "filled in name", + ) + # Specific test for vies_valid + elif value == "vies_valid": + # It should be true only if we check VIES + # and get a valid response + if ( + self.env.user.company_id.vat_check_vies + and vals["vies_valid"] + and not self.vies_timeout + ): + self.assertTrue( + partner_form[value], "VIES verification is OK" + ) + # In any other case we expect it to be False + else: + self.assertFalse( + partner_form.vies_valid, "vies_valid is False" + ) + + +class TestStructureNoVIES(TestStructure): + allow_inherited_tests_method = True + + @classmethod + def setUpClass(cls): + super().setUpClass() + cls.env.user.company_id.vat_check_vies = False + + +class TestStructureVIESForce(TestStructure): + allow_inherited_tests_method = True + + @classmethod + def setUpClass(cls): + super().setUpClass() + cls.env.user.company_id.force_vat_siret_lookup = True + + +class TestStructureVIESTimeoutNoForce(TestStructure): + allow_inherited_tests_method = True + + @classmethod + def setUpClass(cls): + super().setUpClass() + cls.vies_timeout = True + + +class TestStructureVIESTimeoutForce(TestStructure): + allow_inherited_tests_method = True + + @classmethod + def setUpClass(cls): + super().setUpClass() + cls.vies_timeout = True + cls.env.user.company_id.force_vat_siret_lookup = True + + +class TestStructureNoVIESForce(TestStructure): + allow_inherited_tests_method = True + + @classmethod + def setUpClass(cls): + super().setUpClass() + cls.env.user.company_id.vat_check_vies = False + cls.env.user.company_id.force_vat_siret_lookup = True diff --git a/l10n_fr_siret_lookup/views/res_config_settings_views.xml b/l10n_fr_siret_lookup/views/res_config_settings_views.xml new file mode 100644 index 0000000000..b9e6265963 --- /dev/null +++ b/l10n_fr_siret_lookup/views/res_config_settings_views.xml @@ -0,0 +1,20 @@ + + + + res.config.settings.view.form.inherit.fr.siret.lookup + res.config.settings + + + + + + + + + + diff --git a/l10n_fr_siret_lookup/views/res_partner.xml b/l10n_fr_siret_lookup/views/res_partner.xml new file mode 100644 index 0000000000..ba1cd8219e --- /dev/null +++ b/l10n_fr_siret_lookup/views/res_partner.xml @@ -0,0 +1,12 @@ + + + + SIREN Lookup + fr.siret.lookup + form + new + + + form + + diff --git a/l10n_fr_siret_lookup/wizard/__init__.py b/l10n_fr_siret_lookup/wizard/__init__.py new file mode 100644 index 0000000000..af1ed13a33 --- /dev/null +++ b/l10n_fr_siret_lookup/wizard/__init__.py @@ -0,0 +1 @@ +from . import fr_siret_lookup diff --git a/l10n_fr_siret_lookup/wizard/fr_siret_lookup.py b/l10n_fr_siret_lookup/wizard/fr_siret_lookup.py new file mode 100644 index 0000000000..f4e6f9ad2f --- /dev/null +++ b/l10n_fr_siret_lookup/wizard/fr_siret_lookup.py @@ -0,0 +1,141 @@ +# Copyright 2018-2022 Le Filament () +# Copyright 2021-2022 Akretion France (http://www.akretion.com/) +# @author: Alexis de Lattre +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). + +from odoo import api, fields, models +from odoo.exceptions import UserError + + +class FrSiretLookup(models.TransientModel): + _name = "fr.siret.lookup" + _description = "Get values from companies" + + name = fields.Char(string="Name to Search", required=True) + line_ids = fields.One2many( + "fr.siret.lookup.line", "wizard_id", string="Results", readonly=True + ) + partner_id = fields.Many2one("res.partner", readonly=True, required=True) + + @api.model + def default_get(self, fields_list): + res = super().default_get(fields_list) + if ( + self.env.context.get("active_id") + and self.env.context.get("active_model") == "res.partner" + ): + partner = self.env["res.partner"].browse(self.env.context["active_id"]) + if not partner.is_company: + raise UserError( + self.env._( + f"Partner {partner.display_name} is not a company. " + "This action is not relevant." + ) + % partner.display_name + ) + res.update( + { + "name": partner.name, + "partner_id": partner.id, + } + ) + return res + + # Action + @api.model + def _prepare_partner_from_data(self, data): + country_id = zipcode = False + zipcode = data.get("codepostaletablissement") + if isinstance(zipcode, int): + zipcode = str(zipcode) + if zipcode: + zipcode = zipcode.zfill(5) + country_id = self.env["res.partner"]._opendatasoft_compute_country(zipcode) + return { + "name": data.get("denominationunitelegale") + or data.get("l1_adressage_unitelegale"), + "street": data.get("adresseetablissement"), + "zip": zipcode, + "city": data.get("libellecommuneetablissement"), + "country_id": country_id, + "siren": data.get("siren") and str(data["siren"]) or False, + "siret": data.get("siret") and str(data["siret"]) or False, + "category": data.get("categorieentreprise"), + "creation_date": data.get("datecreationunitelegale"), + "ape": data.get("activiteprincipaleunitelegale"), + "ape_label": data.get("divisionunitelegale"), + "legal_type": data.get("naturejuridiqueunitelegale"), + "staff": data.get("trancheeffectifsunitelegale", 0), + "active": data.get("etatadministratifetablissement") == "Actif", + } + + def get_lines(self): + self.ensure_one() + self.line_ids.unlink() + # Get request + res_json = self.env["res.partner"]._opendatasoft_get_raw_data( + self.name, raise_if_fail=True, rows=30 + ) + # Fill new company lines + companies_vals = [] + for company in res_json["records"]: + res = self._prepare_partner_from_data(company["fields"]) + companies_vals.append((0, 0, res)) + self.line_ids = companies_vals + current_context = dict(self.env.context) + current_context["active_test"] = False + return { + "context": current_context, + "view_mode": "form", + "res_model": self._name, + "res_id": self.id, + "view_id": False, + "type": "ir.actions.act_window", + "target": "new", + } + + +class FrSiretLookupLine(models.TransientModel): + _name = "fr.siret.lookup.line" + _description = "Company Selection" + + wizard_id = fields.Many2one("fr.siret.lookup", string="Wizard", ondelete="cascade") + name = fields.Char() + street = fields.Char() + zip = fields.Char() + city = fields.Char() + country_id = fields.Many2one("res.country", string="Country") + legal_type = fields.Char() + siren = fields.Char("SIREN") + siret = fields.Char("SIRET") + ape = fields.Char("APE Code") + ape_label = fields.Char("APE Label") + creation_date = fields.Date() + staff = fields.Char("# Staff") + category = fields.Char() + active = fields.Boolean() + + def _prepare_partner_values(self): + self.ensure_one() + vat, vies_valid = self.env["res.partner"]._siren2vat_vies( + self.siren, raise_if_fail=True + ) + vals = { + "name": self.name, + "street": self.street, + "zip": self.zip, + "city": self.city, + "country_id": self.country_id.id or False, + "siret": self.siret, + "vat": vat, + "vies_valid": vies_valid, + } + return vals + + def update_partner(self): + self.ensure_one() + partner = self.wizard_id.partner_id + partner.write(self._prepare_partner_values()) + partner.message_post( + body=self.env._("Partner updated via the opendatasoft.com API.") + ) diff --git a/l10n_fr_siret_lookup/wizard/fr_siret_lookup_view.xml b/l10n_fr_siret_lookup/wizard/fr_siret_lookup_view.xml new file mode 100644 index 0000000000..9171914827 --- /dev/null +++ b/l10n_fr_siret_lookup/wizard/fr_siret_lookup_view.xml @@ -0,0 +1,106 @@ + + + + + fr.siret.lookup + +
+ + + +