From 1bf259023cb53cc8b7dd0c64be1225cc8b02cb54 Mon Sep 17 00:00:00 2001 From: juancarlosonate-tecnativa Date: Tue, 29 Jul 2025 11:06:53 +0200 Subject: [PATCH 1/3] [ADD] product_eprel TT57294 --- product_eprel/README.rst | 101 ++++ product_eprel/__init__.py | 1 + product_eprel/__manifest__.py | 23 + product_eprel/data/eprel_cron.xml | 15 + .../data/eprel_product_categories.xml | 144 ++++++ product_eprel/demo/product_template.xml | 22 + product_eprel/i18n/es.po | 208 ++++++++ product_eprel/i18n/product_eprel.pot | 205 ++++++++ product_eprel/models/__init__.py | 6 + product_eprel/models/product_category.py | 11 + .../models/product_category_eprel.py | 11 + product_eprel/models/product_product.py | 11 + product_eprel/models/product_template.py | 121 +++++ product_eprel/models/res_company.py | 9 + product_eprel/models/res_config_settings.py | 9 + product_eprel/readme/CONFIGURE.md | 2 + product_eprel/readme/CONTRIBUTORS.md | 2 + product_eprel/readme/DESCRIPTION.md | 3 + product_eprel/readme/USAGE.md | 8 + product_eprel/security/ir.model.access.csv | 3 + product_eprel/static/description/index.html | 452 ++++++++++++++++++ product_eprel/tests/__init__.py | 1 + product_eprel/tests/test_product_eprel.py | 54 +++ .../views/product_category_views.xml | 13 + .../views/product_template_views.xml | 29 ++ .../views/res_config_settings_views.xml | 29 ++ 26 files changed, 1493 insertions(+) create mode 100644 product_eprel/README.rst create mode 100644 product_eprel/__init__.py create mode 100644 product_eprel/__manifest__.py create mode 100644 product_eprel/data/eprel_cron.xml create mode 100644 product_eprel/data/eprel_product_categories.xml create mode 100644 product_eprel/demo/product_template.xml create mode 100644 product_eprel/i18n/es.po create mode 100644 product_eprel/i18n/product_eprel.pot create mode 100644 product_eprel/models/__init__.py create mode 100644 product_eprel/models/product_category.py create mode 100644 product_eprel/models/product_category_eprel.py create mode 100644 product_eprel/models/product_product.py create mode 100644 product_eprel/models/product_template.py create mode 100644 product_eprel/models/res_company.py create mode 100644 product_eprel/models/res_config_settings.py create mode 100644 product_eprel/readme/CONFIGURE.md create mode 100644 product_eprel/readme/CONTRIBUTORS.md create mode 100644 product_eprel/readme/DESCRIPTION.md create mode 100644 product_eprel/readme/USAGE.md create mode 100644 product_eprel/security/ir.model.access.csv create mode 100644 product_eprel/static/description/index.html create mode 100644 product_eprel/tests/__init__.py create mode 100644 product_eprel/tests/test_product_eprel.py create mode 100644 product_eprel/views/product_category_views.xml create mode 100644 product_eprel/views/product_template_views.xml create mode 100644 product_eprel/views/res_config_settings_views.xml diff --git a/product_eprel/README.rst b/product_eprel/README.rst new file mode 100644 index 00000000000..f45d5fd3c21 --- /dev/null +++ b/product_eprel/README.rst @@ -0,0 +1,101 @@ +============= +Product EPREL +============= + +.. + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! This file is generated by oca-gen-addon-readme !! + !! changes will be overwritten. !! + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! source digest: sha256:63acb92eb94cf81305149bf8e5f5b5e8199b65070f2088042db63610b188e6bd + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + +.. |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%2Fproduct--attribute-lightgray.png?logo=github + :target: https://github.com/OCA/product-attribute/tree/14.0/product_eprel + :alt: OCA/product-attribute +.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png + :target: https://translation.odoo-community.org/projects/product-attribute-14-0/product-attribute-14-0-product_eprel + :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/product-attribute&target_branch=14.0 + :alt: Try me on Runboat + +|badge1| |badge2| |badge3| |badge4| |badge5| + +This module connects Odoo with the EPREL API to fetch energy labels and +fiches for products that have a model identifier and an EPREL product +category. It retrieves the EPREL registration number and builds URLs. + +**Table of contents** + +.. contents:: + :local: + +Configuration +============= + +To configure this module, go to Settings > General Settings. Under the +Integrations section, enter your EPREL API key. + +Usage +===== + +- In the **EPREL** page of the product form you will find: + + - Model Identifier (required). + - URLs for product fiche, energy label, and energy arrow. + +- Product categories must have a valid **EPREL Category**. +- Usage options: + + 1. Click **Get EPREL data** in the product form. + 2. Enable the scheduled action **EPREL: Sync Product Data** in + *Settings > Technical > Scheduled Actions*. + +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 +------- + +* Tecnativa + +Contributors +------------ + +- `Tecnativa `__ + + - Juan Carlos Oñate + +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. + +This module is part of the `OCA/product-attribute `_ project on GitHub. + +You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute. diff --git a/product_eprel/__init__.py b/product_eprel/__init__.py new file mode 100644 index 00000000000..0650744f6bc --- /dev/null +++ b/product_eprel/__init__.py @@ -0,0 +1 @@ +from . import models diff --git a/product_eprel/__manifest__.py b/product_eprel/__manifest__.py new file mode 100644 index 00000000000..784491fa51a --- /dev/null +++ b/product_eprel/__manifest__.py @@ -0,0 +1,23 @@ +# Copyright 2025 Juan Carlos Oñate - Tecnativa +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). +{ + "name": "Product EPREL", + "summary": "Manage EPREL model identifiers and energy label data for products.", + "version": "14.0.1.0.0", + "author": "Tecnativa, Odoo Community Association (OCA)", + "website": "https://github.com/OCA/product-attribute", + "license": "AGPL-3", + "depends": ["product"], + "data": [ + "security/ir.model.access.csv", + "views/product_category_views.xml", + "views/product_template_views.xml", + "views/res_config_settings_views.xml", + "data/eprel_cron.xml", + "data/eprel_product_categories.xml", + ], + "demo": [ + "demo/product_template.xml", + ], + "installable": True, +} diff --git a/product_eprel/data/eprel_cron.xml b/product_eprel/data/eprel_cron.xml new file mode 100644 index 00000000000..ffcab32bee8 --- /dev/null +++ b/product_eprel/data/eprel_cron.xml @@ -0,0 +1,15 @@ + + + + + EPREL: Sync Product Data + + code + model._get_eprel_registration_number() + 1 + months + -1 + + + + diff --git a/product_eprel/data/eprel_product_categories.xml b/product_eprel/data/eprel_product_categories.xml new file mode 100644 index 00000000000..556fb21adef --- /dev/null +++ b/product_eprel/data/eprel_product_categories.xml @@ -0,0 +1,144 @@ + + + + + Electronic displays + electronicdisplays + + + Household washing machines + washingmachines2019 + + + Household washer-dryers + washerdriers2019 + + + Light sources + lightsources + + + Refrigerating appliances + refrigeratingappliances2019 + + + Household dishwashers + dishwashers2019 + + + Refrigerating appliances with a direct sales function + refrigeratingappliancesdirectsalesfunction + + + Tyres + tyres + + + Smartphones and slate tablets + smartphonestablets20231669 + + + Household tumble dryers + tumbledryers20232534 + + + + Air conditioners + airconditioners + + + Domestic Ovens + ovens + + + Range hoods + rangehoods + + + Household tumble driers + tumbledriers + + + Local space heaters + localspaceheaters + + + Professional refrigerating storage cabinets + professionalrefrigeratedstoragecabinets + + + Residential Ventilation Units + residentialventilationunits + + + Solid fuel boilers + solidfuelboilers + + + Packages of solid fuel boilers + solidfuelboilerpackages + + + Space heaters/Combination heaters + spaceheaters + + + Packages of space heaters/combinations heaters + spaceheaterpackages + + + Temperature controls for space heaters + spaceheatertemperaturecontrol + + + Solar devices for space heaters + spaceheatersolardevice + + + Water heaters + waterheaters + + + Packages of water heaters + waterheaterpackages + + + Hot water storage tanks for water heaters + hotwaterstoragetanks + + + Solar devices for water heaters + waterheatersolardevices + + + + Electrical Lamps + lamps + + + Household combined washer-driers + washerdriers + + + Household dishwashers + dishwashers + + + Household refrigerating appliances + refrigeratingappliances + + + Household washing machines + washingmachines + + + Televisions + televisions + + diff --git a/product_eprel/demo/product_template.xml b/product_eprel/demo/product_template.xml new file mode 100644 index 00000000000..d2249aa3bfb --- /dev/null +++ b/product_eprel/demo/product_template.xml @@ -0,0 +1,22 @@ + + + + Smartphones and slate tablets + smartphonestablets20231669 + + + Smartphones + + + + Edge 60 Pro + + edge 60 pro (XT2507-1) + + diff --git a/product_eprel/i18n/es.po b/product_eprel/i18n/es.po new file mode 100644 index 00000000000..524e4ad8c94 --- /dev/null +++ b/product_eprel/i18n/es.po @@ -0,0 +1,208 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * product_eprel +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 14.0\n" +"Report-Msgid-Bugs-To: \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: product_eprel +#: model:ir.model.fields,field_description:product_eprel.field_res_company__eprel_api_key +#: model:ir.model.fields,field_description:product_eprel.field_res_config_settings__eprel_api_key +msgid "API Key" +msgstr "" + +#. module: product_eprel +#: model_terms:ir.ui.view,arch_db:product_eprel.res_config_settings_view_form +msgid "Authentication key for accessing EPREL product data via API" +msgstr "" +"Clave de autenticación para acceder a los datos de producto de EPREL " +"mediante API" + +#. module: product_eprel +#: model:ir.model.fields,field_description:product_eprel.field_product_category_eprel__code +msgid "Code" +msgstr "Código" + +#. module: product_eprel +#: model:ir.model,name:product_eprel.model_res_company +msgid "Companies" +msgstr "" + +#. module: product_eprel +#: model:ir.model,name:product_eprel.model_res_config_settings +msgid "Config Settings" +msgstr "" + +#. module: product_eprel +#: model:ir.model.fields,field_description:product_eprel.field_product_category_eprel__create_uid +msgid "Created by" +msgstr "" + +#. module: product_eprel +#: model:ir.model.fields,field_description:product_eprel.field_product_category_eprel__create_date +msgid "Created on" +msgstr "" + +#. module: product_eprel +#: model:ir.model.fields,field_description:product_eprel.field_product_category__display_name +#: model:ir.model.fields,field_description:product_eprel.field_product_category_eprel__display_name +#: model:ir.model.fields,field_description:product_eprel.field_product_template__display_name +#: model:ir.model.fields,field_description:product_eprel.field_res_company__display_name +#: model:ir.model.fields,field_description:product_eprel.field_res_config_settings__display_name +msgid "Display Name" +msgstr "" + +#. module: product_eprel +#: model_terms:ir.ui.view,arch_db:product_eprel.res_config_settings_view_form +msgid "Documentation" +msgstr "Documentación" + +#. module: product_eprel +#: code:addons/product_eprel/models/product_template.py:0 +#, python-format +msgid "EPREL API Key is not configured." +msgstr "La clave API de EPREL no está configurada." + +#. module: product_eprel +#: model:ir.model.fields,field_description:product_eprel.field_product_category__eprel_category_id +msgid "EPREL Category" +msgstr "Categoría EPREL" + +#. module: product_eprel +#: model:ir.model.fields,field_description:product_eprel.field_product_product__eprel_enabled +#: model:ir.model.fields,field_description:product_eprel.field_product_template__eprel_enabled +msgid "EPREL Enabled" +msgstr "EPREL activado" + +#. module: product_eprel +#: model:ir.model,name:product_eprel.model_product_category_eprel +msgid "EPREL Product Category" +msgstr "Categoría de producto EPREL" + +#. module: product_eprel +#: model:ir.model.fields,field_description:product_eprel.field_product_product__eprel_registration_number +#: model:ir.model.fields,field_description:product_eprel.field_product_template__eprel_registration_number +msgid "EPREL Registration Number" +msgstr "Número de registro EPREL" + +#. module: product_eprel +#: model:ir.actions.server,name:product_eprel.ir_cron_eprel_sync_products_ir_actions_server +#: model:ir.cron,cron_name:product_eprel.ir_cron_eprel_sync_products +#: model:ir.cron,name:product_eprel.ir_cron_eprel_sync_products +msgid "EPREL: Sync Product Data" +msgstr "EPREL: Sincronizar datos del producto" + +#. module: product_eprel +#: model:product.template,name:product_eprel.product_template_edge_60_pro +msgid "Edge 60 Pro" +msgstr "" + +#. module: product_eprel +#: model:ir.model.fields,field_description:product_eprel.field_product_product__eprel_energy_class_image +#: model:ir.model.fields,field_description:product_eprel.field_product_template__eprel_energy_class_image +msgid "Energy Class Image" +msgstr "Imagen de clase energética" + +#. module: product_eprel +#: code:addons/product_eprel/models/product_template.py:0 +#, python-format +msgid "Failed to fetch EPREL data: %s" +msgstr "Error al obtener datos de EPREL: %s" + +#. module: product_eprel +#: model:ir.model.fields,field_description:product_eprel.field_product_product__fiche_url +#: model:ir.model.fields,field_description:product_eprel.field_product_template__fiche_url +msgid "Fiche URL" +msgstr "URL de ficha" + +#. module: product_eprel +#: model_terms:ir.ui.view,arch_db:product_eprel.view_product_template_form_eprel +msgid "Get EPREL data" +msgstr "Obtener datos de EPREL" + +#. module: product_eprel +#: model:ir.model.fields,field_description:product_eprel.field_product_category__id +#: model:ir.model.fields,field_description:product_eprel.field_product_category_eprel__id +#: model:ir.model.fields,field_description:product_eprel.field_product_template__id +#: model:ir.model.fields,field_description:product_eprel.field_res_company__id +#: model:ir.model.fields,field_description:product_eprel.field_res_config_settings__id +msgid "ID" +msgstr "" + +#. module: product_eprel +#: model:ir.model.fields,field_description:product_eprel.field_product_product__label_arrow_url +#: model:ir.model.fields,field_description:product_eprel.field_product_template__label_arrow_url +msgid "Label Arrow URL" +msgstr "URL de la flecha energética" + +#. module: product_eprel +#: model:ir.model.fields,field_description:product_eprel.field_product_product__label_url +#: model:ir.model.fields,field_description:product_eprel.field_product_template__label_url +msgid "Label URL" +msgstr "URL de etiqueta" + +#. module: product_eprel +#: model:ir.model.fields,field_description:product_eprel.field_product_category____last_update +#: model:ir.model.fields,field_description:product_eprel.field_product_category_eprel____last_update +#: model:ir.model.fields,field_description:product_eprel.field_product_template____last_update +#: model:ir.model.fields,field_description:product_eprel.field_res_company____last_update +#: model:ir.model.fields,field_description:product_eprel.field_res_config_settings____last_update +msgid "Last Modified on" +msgstr "" + +#. module: product_eprel +#: model:ir.model.fields,field_description:product_eprel.field_product_category_eprel__write_uid +msgid "Last Updated by" +msgstr "" + +#. module: product_eprel +#: model:ir.model.fields,field_description:product_eprel.field_product_category_eprel__write_date +msgid "Last Updated on" +msgstr "" + +#. module: product_eprel +#: model:ir.model.fields,field_description:product_eprel.field_product_product__eprel_model_identifier +#: model:ir.model.fields,field_description:product_eprel.field_product_template__eprel_model_identifier +msgid "Model Identifier" +msgstr "Identificador del modelo" + +#. module: product_eprel +#: code:addons/product_eprel/models/product_template.py:0 +#, python-format +msgid "Model Identifier and EPREL Category must be set." +msgstr "Debe establecerse el identificador del modelo y la categoría EPREL." + +#. module: product_eprel +#: model:ir.model,name:product_eprel.model_product_category +msgid "Product Category" +msgstr "" + +#. module: product_eprel +#: model:ir.model,name:product_eprel.model_product_template +msgid "Product Template" +msgstr "" + +#. module: product_eprel +#: model:ir.model.fields,field_description:product_eprel.field_product_category_eprel__name +msgid "Product group" +msgstr "Grupo de producto" + +#. module: product_eprel +#: model:ir.model.fields,help:product_eprel.field_product_product__eprel_registration_number +#: model:ir.model.fields,help:product_eprel.field_product_template__eprel_registration_number +msgid "The EPREL registration number fetched from the API." +msgstr "El número de registro EPREL obtenido desde la API." + +#. module: product_eprel +#: model:product.template,uom_name:product_eprel.product_template_edge_60_pro +msgid "Units" +msgstr "" diff --git a/product_eprel/i18n/product_eprel.pot b/product_eprel/i18n/product_eprel.pot new file mode 100644 index 00000000000..486fc616628 --- /dev/null +++ b/product_eprel/i18n/product_eprel.pot @@ -0,0 +1,205 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * product_eprel +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 14.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: product_eprel +#: model:ir.model.fields,field_description:product_eprel.field_res_company__eprel_api_key +#: model:ir.model.fields,field_description:product_eprel.field_res_config_settings__eprel_api_key +msgid "API Key" +msgstr "" + +#. module: product_eprel +#: model_terms:ir.ui.view,arch_db:product_eprel.res_config_settings_view_form +msgid "Authentication key for accessing EPREL product data via API" +msgstr "" + +#. module: product_eprel +#: model:ir.model.fields,field_description:product_eprel.field_product_category_eprel__code +msgid "Code" +msgstr "" + +#. module: product_eprel +#: model:ir.model,name:product_eprel.model_res_company +msgid "Companies" +msgstr "" + +#. module: product_eprel +#: model:ir.model,name:product_eprel.model_res_config_settings +msgid "Config Settings" +msgstr "" + +#. module: product_eprel +#: model:ir.model.fields,field_description:product_eprel.field_product_category_eprel__create_uid +msgid "Created by" +msgstr "" + +#. module: product_eprel +#: model:ir.model.fields,field_description:product_eprel.field_product_category_eprel__create_date +msgid "Created on" +msgstr "" + +#. module: product_eprel +#: model:ir.model.fields,field_description:product_eprel.field_product_category__display_name +#: model:ir.model.fields,field_description:product_eprel.field_product_category_eprel__display_name +#: model:ir.model.fields,field_description:product_eprel.field_product_template__display_name +#: model:ir.model.fields,field_description:product_eprel.field_res_company__display_name +#: model:ir.model.fields,field_description:product_eprel.field_res_config_settings__display_name +msgid "Display Name" +msgstr "" + +#. module: product_eprel +#: model_terms:ir.ui.view,arch_db:product_eprel.res_config_settings_view_form +msgid "Documentation" +msgstr "" + +#. module: product_eprel +#: code:addons/product_eprel/models/product_template.py:0 +#, python-format +msgid "EPREL API Key is not configured." +msgstr "" + +#. module: product_eprel +#: model:ir.model.fields,field_description:product_eprel.field_product_category__eprel_category_id +msgid "EPREL Category" +msgstr "" + +#. module: product_eprel +#: model:ir.model.fields,field_description:product_eprel.field_product_product__eprel_enabled +#: model:ir.model.fields,field_description:product_eprel.field_product_template__eprel_enabled +msgid "EPREL Enabled" +msgstr "" + +#. module: product_eprel +#: model:ir.model,name:product_eprel.model_product_category_eprel +msgid "EPREL Product Category" +msgstr "" + +#. module: product_eprel +#: model:ir.model.fields,field_description:product_eprel.field_product_product__eprel_registration_number +#: model:ir.model.fields,field_description:product_eprel.field_product_template__eprel_registration_number +msgid "EPREL Registration Number" +msgstr "" + +#. module: product_eprel +#: model:ir.actions.server,name:product_eprel.ir_cron_eprel_sync_products_ir_actions_server +#: model:ir.cron,cron_name:product_eprel.ir_cron_eprel_sync_products +#: model:ir.cron,name:product_eprel.ir_cron_eprel_sync_products +msgid "EPREL: Sync Product Data" +msgstr "" + +#. module: product_eprel +#: model:product.template,name:product_eprel.product_template_edge_60_pro +msgid "Edge 60 Pro" +msgstr "" + +#. module: product_eprel +#: model:ir.model.fields,field_description:product_eprel.field_product_product__eprel_energy_class_image +#: model:ir.model.fields,field_description:product_eprel.field_product_template__eprel_energy_class_image +msgid "Energy Class Image" +msgstr "" + +#. module: product_eprel +#: code:addons/product_eprel/models/product_template.py:0 +#, python-format +msgid "Failed to fetch EPREL data: %s" +msgstr "" + +#. module: product_eprel +#: model:ir.model.fields,field_description:product_eprel.field_product_product__fiche_url +#: model:ir.model.fields,field_description:product_eprel.field_product_template__fiche_url +msgid "Fiche URL" +msgstr "" + +#. module: product_eprel +#: model_terms:ir.ui.view,arch_db:product_eprel.view_product_template_form_eprel +msgid "Get EPREL data" +msgstr "" + +#. module: product_eprel +#: model:ir.model.fields,field_description:product_eprel.field_product_category__id +#: model:ir.model.fields,field_description:product_eprel.field_product_category_eprel__id +#: model:ir.model.fields,field_description:product_eprel.field_product_template__id +#: model:ir.model.fields,field_description:product_eprel.field_res_company__id +#: model:ir.model.fields,field_description:product_eprel.field_res_config_settings__id +msgid "ID" +msgstr "" + +#. module: product_eprel +#: model:ir.model.fields,field_description:product_eprel.field_product_product__label_arrow_url +#: model:ir.model.fields,field_description:product_eprel.field_product_template__label_arrow_url +msgid "Label Arrow URL" +msgstr "" + +#. module: product_eprel +#: model:ir.model.fields,field_description:product_eprel.field_product_product__label_url +#: model:ir.model.fields,field_description:product_eprel.field_product_template__label_url +msgid "Label URL" +msgstr "" + +#. module: product_eprel +#: model:ir.model.fields,field_description:product_eprel.field_product_category____last_update +#: model:ir.model.fields,field_description:product_eprel.field_product_category_eprel____last_update +#: model:ir.model.fields,field_description:product_eprel.field_product_template____last_update +#: model:ir.model.fields,field_description:product_eprel.field_res_company____last_update +#: model:ir.model.fields,field_description:product_eprel.field_res_config_settings____last_update +msgid "Last Modified on" +msgstr "" + +#. module: product_eprel +#: model:ir.model.fields,field_description:product_eprel.field_product_category_eprel__write_uid +msgid "Last Updated by" +msgstr "" + +#. module: product_eprel +#: model:ir.model.fields,field_description:product_eprel.field_product_category_eprel__write_date +msgid "Last Updated on" +msgstr "" + +#. module: product_eprel +#: model:ir.model.fields,field_description:product_eprel.field_product_product__eprel_model_identifier +#: model:ir.model.fields,field_description:product_eprel.field_product_template__eprel_model_identifier +msgid "Model Identifier" +msgstr "" + +#. module: product_eprel +#: code:addons/product_eprel/models/product_template.py:0 +#, python-format +msgid "Model Identifier and EPREL Category must be set." +msgstr "" + +#. module: product_eprel +#: model:ir.model,name:product_eprel.model_product_category +msgid "Product Category" +msgstr "" + +#. module: product_eprel +#: model:ir.model,name:product_eprel.model_product_template +msgid "Product Template" +msgstr "" + +#. module: product_eprel +#: model:ir.model.fields,field_description:product_eprel.field_product_category_eprel__name +msgid "Product group" +msgstr "" + +#. module: product_eprel +#: model:ir.model.fields,help:product_eprel.field_product_product__eprel_registration_number +#: model:ir.model.fields,help:product_eprel.field_product_template__eprel_registration_number +msgid "The EPREL registration number fetched from the API." +msgstr "" + +#. module: product_eprel +#: model:product.template,uom_name:product_eprel.product_template_edge_60_pro +msgid "Units" +msgstr "" diff --git a/product_eprel/models/__init__.py b/product_eprel/models/__init__.py new file mode 100644 index 00000000000..a71285fc91b --- /dev/null +++ b/product_eprel/models/__init__.py @@ -0,0 +1,6 @@ +from . import product_category +from . import product_category_eprel +from . import product_template +from . import res_company +from . import res_config_settings +from . import product_product diff --git a/product_eprel/models/product_category.py b/product_eprel/models/product_category.py new file mode 100644 index 00000000000..eb44f1eb03e --- /dev/null +++ b/product_eprel/models/product_category.py @@ -0,0 +1,11 @@ +# Copyright 2025 Juan Carlos Oñate - Tecnativa +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). +from odoo import fields, models + + +class ProductCategory(models.Model): + _inherit = "product.category" + + eprel_category_id = fields.Many2one( + comodel_name="product.category.eprel", string="EPREL Category" + ) diff --git a/product_eprel/models/product_category_eprel.py b/product_eprel/models/product_category_eprel.py new file mode 100644 index 00000000000..91b9eb9a63d --- /dev/null +++ b/product_eprel/models/product_category_eprel.py @@ -0,0 +1,11 @@ +# Copyright 2025 Juan Carlos Oñate - Tecnativa +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). +from odoo import fields, models + + +class ProductCategoryEprel(models.Model): + _name = "product.category.eprel" + _description = "EPREL Product Category" + + name = fields.Char(string="Product group", required=True) + code = fields.Char(string="Code", required=True) diff --git a/product_eprel/models/product_product.py b/product_eprel/models/product_product.py new file mode 100644 index 00000000000..67df627579f --- /dev/null +++ b/product_eprel/models/product_product.py @@ -0,0 +1,11 @@ +# Copyright 2025 Juan Carlos Oñate - Tecnativa +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). +from odoo import models + + +class ProductProduct(models.Model): + _inherit = "product.product" + + def action_get_eprel_registration_number(self): + for product in self: + product.product_tmpl_id.action_get_eprel_registration_number() diff --git a/product_eprel/models/product_template.py b/product_eprel/models/product_template.py new file mode 100644 index 00000000000..beaa8590819 --- /dev/null +++ b/product_eprel/models/product_template.py @@ -0,0 +1,121 @@ +# Copyright 2025 Juan Carlos Oñate - Tecnativa +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). +import time + +import requests + +from odoo import _, api, fields, models +from odoo.exceptions import UserError + +API_URL = "https://eprel.ec.europa.eu/" + + +class ProductTemplate(models.Model): + _inherit = "product.template" + + eprel_enabled = fields.Boolean( + string="EPREL Enabled", compute="_compute_eprel_enabled" + ) + eprel_model_identifier = fields.Char(string="Model Identifier", copy=False) + eprel_energy_class_image = fields.Char(string="Energy Class Image", copy=False) + fiche_url = fields.Char( + compute="_compute_eprel_url", string="Fiche URL", readonly=True, store=True + ) + label_url = fields.Char( + compute="_compute_eprel_url", string="Label URL", readonly=True, store=True + ) + label_arrow_url = fields.Char( + compute="_compute_eprel_url", + string="Label Arrow URL", + readonly=True, + store=True, + ) + eprel_registration_number = fields.Char( + string="EPREL Registration Number", + copy=False, + help="The EPREL registration number fetched from the API.", + ) + + def _compute_eprel_enabled(self): + for rec in self: + rec.eprel_enabled = bool(rec.env.company.eprel_api_key) + + @api.depends("categ_id.eprel_category_id", "eprel_registration_number") + def _compute_eprel_url(self): + for rec in self: + cat = rec.categ_id.eprel_category_id.code + reg_num = rec.eprel_registration_number + arrow_image_name = ( + rec.eprel_energy_class_image.replace(".svg", ".png") + if rec.eprel_energy_class_image + and isinstance(rec.eprel_energy_class_image, str) + else False + ) + lang_code = self.env.company.partner_id.lang.split("_")[0] + rec.fiche_url = ( + f"{API_URL}fiches/{cat}/Fiche_{reg_num}_{lang_code.upper()}.pdf" + if cat and reg_num + else False + ) + rec.label_url = ( + f"{API_URL}labels/{cat}/Label_{reg_num}.png" + if cat and reg_num + else False + ) + rec.label_arrow_url = ( + "https://ec.europa.eu/assets/move-ener/eprel/" + f"EPREL%20Public/Nested-labels%20thumbnails/{arrow_image_name}" + if arrow_image_name + else False + ) + + def action_get_eprel_registration_number(self): + if not self.env.company.eprel_api_key: + raise UserError(_("EPREL API Key is not configured.")) + min_interval = 0.25 + last_request_time = 0.0 + for product in self: + # Delay control to respect the API rate limit + now = time.time() + elapsed = now - last_request_time + if elapsed < min_interval: + time.sleep(min_interval - elapsed) + if ( + not product.eprel_model_identifier + or not product.categ_id.eprel_category_id + ): + raise UserError(_("Model Identifier and EPREL Category must be set.")) + identifier = product.eprel_model_identifier + category_code = product.categ_id.eprel_category_id.code + data = self._request_eprel_data(identifier, category_code) + last_request_time = time.time() + if data.get("hits"): + product.eprel_registration_number = data.get("hits")[0].get( + "eprelRegistrationNumber" + ) + product.eprel_energy_class_image = data.get("hits")[0].get( + "energyClassImage" + ) + + @api.model + def _get_eprel_registration_number(self): + products = self.search( + [ + ("eprel_model_identifier", "!=", False), + ("categ_id.eprel_category_id", "!=", False), + ] + ) + products.action_get_eprel_registration_number() + + def _request_eprel_data(self, identifier, category_code): + url = f"{API_URL}api/products/{category_code}?modelIdentifier={identifier}" + headers = { + "X-API-KEY": self.env.company.eprel_api_key, + "Accept": "application/json", + } + try: + response = requests.get(url, headers=headers, timeout=10) + response.raise_for_status() + return response.json() + except Exception as e: + raise UserError(_("Failed to fetch EPREL data: %s") % str(e)) diff --git a/product_eprel/models/res_company.py b/product_eprel/models/res_company.py new file mode 100644 index 00000000000..e4388c545f3 --- /dev/null +++ b/product_eprel/models/res_company.py @@ -0,0 +1,9 @@ +# Copyright 2025 Juan Carlos Oñate - Tecnativa +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). +from odoo import fields, models + + +class ResCompany(models.Model): + _inherit = "res.company" + + eprel_api_key = fields.Char(string="API Key") diff --git a/product_eprel/models/res_config_settings.py b/product_eprel/models/res_config_settings.py new file mode 100644 index 00000000000..bb8e054d4a3 --- /dev/null +++ b/product_eprel/models/res_config_settings.py @@ -0,0 +1,9 @@ +# Copyright 2025 Juan Carlos Oñate - Tecnativa +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). +from odoo import fields, models + + +class ResConfigSettings(models.TransientModel): + _inherit = "res.config.settings" + + eprel_api_key = fields.Char(related="company_id.eprel_api_key", readonly=False) diff --git a/product_eprel/readme/CONFIGURE.md b/product_eprel/readme/CONFIGURE.md new file mode 100644 index 00000000000..c576acc7bb6 --- /dev/null +++ b/product_eprel/readme/CONFIGURE.md @@ -0,0 +1,2 @@ +To configure this module, go to Settings > General Settings. Under the +Integrations section, enter your EPREL API key. diff --git a/product_eprel/readme/CONTRIBUTORS.md b/product_eprel/readme/CONTRIBUTORS.md new file mode 100644 index 00000000000..18e68214cfb --- /dev/null +++ b/product_eprel/readme/CONTRIBUTORS.md @@ -0,0 +1,2 @@ +- [Tecnativa](https://www.tecnativa.com) + - Juan Carlos Oñate diff --git a/product_eprel/readme/DESCRIPTION.md b/product_eprel/readme/DESCRIPTION.md new file mode 100644 index 00000000000..f29787a5e90 --- /dev/null +++ b/product_eprel/readme/DESCRIPTION.md @@ -0,0 +1,3 @@ +This module connects Odoo with the EPREL API to fetch energy labels and fiches +for products that have a model identifier and an EPREL product category. It +retrieves the EPREL registration number and builds URLs. diff --git a/product_eprel/readme/USAGE.md b/product_eprel/readme/USAGE.md new file mode 100644 index 00000000000..639e3a54929 --- /dev/null +++ b/product_eprel/readme/USAGE.md @@ -0,0 +1,8 @@ +- In the **EPREL** page of the product form you will find: + - Model Identifier (required). + - URLs for product fiche, energy label, and energy arrow. +- Product categories must have a valid **EPREL Category**. +- Usage options: + 1. Click **Get EPREL data** in the product form. + 2. Enable the scheduled action **EPREL: Sync Product Data** in + *Settings > Technical > Scheduled Actions*. diff --git a/product_eprel/security/ir.model.access.csv b/product_eprel/security/ir.model.access.csv new file mode 100644 index 00000000000..8ea22b5985c --- /dev/null +++ b/product_eprel/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_product_category_eprel_user,product.category.eprel user,model_product_category_eprel,base.group_user,1,0,0,0 +access_product_category_eprel_admin,product.category.eprel admin,model_product_category_eprel,base.group_system,1,1,1,1 diff --git a/product_eprel/static/description/index.html b/product_eprel/static/description/index.html new file mode 100644 index 00000000000..a805749ad4c --- /dev/null +++ b/product_eprel/static/description/index.html @@ -0,0 +1,452 @@ + + + + + +Product EPREL + + + +
+

Product EPREL

+ + +

Beta License: AGPL-3 OCA/product-attribute Translate me on Weblate Try me on Runboat

+

This module connects Odoo with the EPREL API to fetch energy labels and +fiches for products that have a model identifier and an EPREL product +category. It retrieves the EPREL registration number and builds URLs.

+

Table of contents

+ +
+

Configuration

+

To configure this module, go to Settings > General Settings. Under the +Integrations section, enter your EPREL API key.

+
+
+

Usage

+
    +
  • In the EPREL page of the product form you will find:
      +
    • Model Identifier (required).
    • +
    • URLs for product fiche, energy label, and energy arrow.
    • +
    +
  • +
  • Product categories must have a valid EPREL Category.
  • +
  • Usage options:
      +
    1. Click Get EPREL data in the product form.
    2. +
    3. Enable the scheduled action EPREL: Sync Product Data in +Settings > Technical > Scheduled Actions.
    4. +
    +
  • +
+
+
+

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

+
    +
  • Tecnativa
  • +
+
+
+

Contributors

+ +
+
+

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.

+

This module is part of the OCA/product-attribute project on GitHub.

+

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

+
+
+
+ + diff --git a/product_eprel/tests/__init__.py b/product_eprel/tests/__init__.py new file mode 100644 index 00000000000..8a4bbb4d68f --- /dev/null +++ b/product_eprel/tests/__init__.py @@ -0,0 +1 @@ +from . import test_product_eprel diff --git a/product_eprel/tests/test_product_eprel.py b/product_eprel/tests/test_product_eprel.py new file mode 100644 index 00000000000..582b7bf6921 --- /dev/null +++ b/product_eprel/tests/test_product_eprel.py @@ -0,0 +1,54 @@ +from unittest.mock import patch + +from odoo.tests.common import TransactionCase + + +class TestProductModelIdentifier(TransactionCase): + def setUp(self): + super().setUp() + self.env.company.eprel_api_key = "FAKE_KEY" + eprel_category = self.env["product.category.eprel"].create( + { + "name": "Smartphones and slate tablets", + "code": "smartphonestablets20231669", + } + ) + self.category = self.env["product.category"].create( + { + "name": "Smartphones", + "eprel_category_id": eprel_category.id, + } + ) + self.product = self.env["product.template"].create( + { + "name": "Edge 60 Pro", + "eprel_model_identifier": "edge 60 pro (XT2507-1)", + "categ_id": self.category.id, + } + ) + + def test_fetch_eprel_registration_number_with_patch(self): + fake_response_data = {"hits": [{"eprelRegistrationNumber": "2266111"}]} + + class MockResponse: + def raise_for_status(self): + pass + + def json(self): + return fake_response_data + + @property + def text(self): + return '{"hits": [{"eprelRegistrationNumber": "2266111"}]}' + + with patch( + "odoo.addons.product_eprel.models.product_template.requests.get", + return_value=MockResponse(), + ): + self.product.action_get_eprel_registration_number() + self.assertEqual(self.product.eprel_registration_number, "2266111") + self.assertTrue(self.product.fiche_url) + self.assertEqual( + "https://eprel.ec.europa.eu/fiches/smartphonestablets20231669/Fiche_2266111_EN.pdf", + self.product.fiche_url, + ) diff --git a/product_eprel/views/product_category_views.xml b/product_eprel/views/product_category_views.xml new file mode 100644 index 00000000000..d17b6a36159 --- /dev/null +++ b/product_eprel/views/product_category_views.xml @@ -0,0 +1,13 @@ + + + + product.category.form.eprel + product.category + + + + + + + + diff --git a/product_eprel/views/product_template_views.xml b/product_eprel/views/product_template_views.xml new file mode 100644 index 00000000000..abc63595257 --- /dev/null +++ b/product_eprel/views/product_template_views.xml @@ -0,0 +1,29 @@ + + + + product.template.form.eprel + product.template + + + + +