diff --git a/product_food/README.rst b/product_food/README.rst new file mode 100644 index 00000000..79d863fb --- /dev/null +++ b/product_food/README.rst @@ -0,0 +1,118 @@ +============================ +Products - Food Informations +============================ + +.. + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! This file is generated by oca-gen-addon-readme !! + !! changes will be overwritten. !! + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! source digest: sha256:ecfaf240c3715a7c432e398dbd4668caed8e92dbc38dd773d64e780df60fba27 + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + +.. |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-grap%2Fgrap--odoo--business-lightgray.png?logo=github + :target: https://github.com/grap/grap-odoo-business/tree/12.0/product_food + :alt: grap/grap-odoo-business + +|badge1| |badge2| |badge3| + +This module extends the functionality of sale module to support food features. + +It provides a new model ``product.allergen`` + +It also adds many fields on product models. (templates and variants) + +* ``is_alimentary``, boolean for analysis purpose. +* ``best_before_date_day`` that mentions for how many days a product can + be eaten, after having packed. (for cheese, meats, etc.) +* ``has_alcohol``, boolean to mention if the product contains alcohol. +* ``allergen_ids`` to mention the list of allergens. +* ``ingredients``. (free text). + +Alls the fields are defined on ``product.product`` model and can be set also +on ``product.template`` models, in a mono variant context. + +**Table of contents** + +.. contents:: + :local: + +Configuration +============= + +* Go to 'Sale > Configuration > Allergens' and create new items. + +.. figure:: https://raw.githubusercontent.com/grap/grap-odoo-business/12.0/product_food/static/description/product_allergen_form.png + +* Go to 'Inventory > Configuration > Product Categories' and check new fields: + * ``Contain Alimentary Products`` + * ``Contain Vegan Products`` + * ``Contain Alcohol Products`` + +.. figure:: https://raw.githubusercontent.com/grap/grap-odoo-business/12.0/product_food/static/description/product_category_form.png + +* Go to 'Sale > Configuration > Product Labels' and update your labels, + setting two new fields ``Has Alcohol`` and ``Is Vegan``. + +.. figure:: https://raw.githubusercontent.com/grap/grap-odoo-business/12.0/product_food/static/description/product_label_form.png + +Usage +===== + +* Go to your product variants or templates form and set the information in the following two tabs. + +.. figure:: https://raw.githubusercontent.com/grap/grap-odoo-business/12.0/product_food/static/description/product_product_alimentary_tab.png + +Known issues / Roadmap +====================== + +* It could be great to have the possibility to manage the certification + document that provide certification organization with the following model + ``res.company.certification`` and the fields ``company_id``, + ``organization_id``, ``date_start``, ``date_end`` + +* In the same way, it could be great to have the possibility to store + the certification document of each supplier with the following model + ``res.partner.certification`` and the fields ``partner_id``, + ``organization_id``, ``date_start``, ``date_end`` + +* Make a dependency to the new module ``product_net_weight`` and update + algorithm + create a new module ``product_volumen_price``. + https://github.com/OCA/product-attribute/pull/894 + +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 +~~~~~~~ + +* GRAP + +Contributors +~~~~~~~~~~~~ + +* Julien WESTE +* Sylvain LE GAL (https://www.twitter.com/legalsylvain) + +Maintainers +~~~~~~~~~~~ + +This module is part of the `grap/grap-odoo-business `_ project on GitHub. + +You are welcome to contribute. diff --git a/product_food/__init__.py b/product_food/__init__.py new file mode 100644 index 00000000..0650744f --- /dev/null +++ b/product_food/__init__.py @@ -0,0 +1 @@ +from . import models diff --git a/product_food/__manifest__.py b/product_food/__manifest__.py new file mode 100644 index 00000000..c6f2b746 --- /dev/null +++ b/product_food/__manifest__.py @@ -0,0 +1,35 @@ +# Copyright (C) 2012 - Today: GRAP (http://www.grap.coop) +# @author: Sylvain LE GAL (https://twitter.com/legalsylvain) +# @author Julien WESTE +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). +{ + "name": "Products - Food Informations", + "version": "16.0.1.0.0", + "category": "Sales", + "author": "GRAP", + "website": "https://github.com/grap/grap-odoo-business", + "license": "AGPL-3", + "depends": [ + # OCA + "product_usability", + "product_compute_template_field_from_variant_helper", + # GRAP + "product_label", + ], + "data": [ + "security/res_groups.xml", + "security/ir.model.access.csv", + "views/view_product_label.xml", + "views/view_product_allergen.xml", + "views/view_product_category.xml", + "views/view_product_product.xml", + "views/view_product_template.xml", + ], + "demo": [ + "demo/product_label.xml", + "demo/product_allergen.xml", + "demo/product_category.xml", + "demo/product_product.xml", + ], + "installable": True, +} diff --git a/product_food/demo/product_allergen.xml b/product_food/demo/product_allergen.xml new file mode 100644 index 00000000..bd7782af --- /dev/null +++ b/product_food/demo/product_allergen.xml @@ -0,0 +1,79 @@ + + + + + + ARA + Arachide + + + + CEL + Céleri + + + + CRU + Crustacés + + + + FAC + Fruits à coques (oléagineux) + + + + GLU + Gluten + + + + LAIT + Lait + + + + LUP + Lupin + + + + MOL + Mollusques + + + + MOU + Moutarde + + + + POI + Poissons + + + + SES + Sésame + + + + SOJA + Soja + + + + SUL + Sulfites + + + + ŒUF + Oeuf + + + diff --git a/product_food/demo/product_category.xml b/product_food/demo/product_category.xml new file mode 100644 index 00000000..20e6e19d --- /dev/null +++ b/product_food/demo/product_category.xml @@ -0,0 +1,22 @@ + + + + + + Alimentary + + + + + + Beers + + + + + + diff --git a/product_food/demo/product_label.xml b/product_food/demo/product_label.xml new file mode 100644 index 00000000..bfed1bd1 --- /dev/null +++ b/product_food/demo/product_label.xml @@ -0,0 +1,25 @@ + + + + + + Demeter + DEM + https://www.demeter.fr/ + + + + + Vegan + VEG + https://www.vegan.org/ + + + + + diff --git a/product_food/demo/product_product.xml b/product_food/demo/product_product.xml new file mode 100644 index 00000000..8fb5ca9d --- /dev/null +++ b/product_food/demo/product_product.xml @@ -0,0 +1,42 @@ + + + + + + Arachide Bio Toasté - Jean Hervé + + + + 2.70 + + + Arachides de Chine issues de l'agriculture biologique + 365 + + + + + Bière Lammsbrau sans gluten 33cl + + + + + 4012852001698 + 2.30 + 0.33 + + + + diff --git a/product_food/i18n/fr.po b/product_food/i18n/fr.po new file mode 100644 index 00000000..ef78716c --- /dev/null +++ b/product_food/i18n/fr.po @@ -0,0 +1,304 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * product_food +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 16.0\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2025-01-10 13:04+0000\n" +"PO-Revision-Date: 2025-01-10 13:04+0000\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_food +#: model:ir.model.fields,field_description:product_food.field_product_allergen__active +msgid "Active" +msgstr "Actif" + +#. module: product_food +#: model:ir.model.fields,field_description:product_food.field_product_product__alcohol_by_volume +#: model:ir.model.fields,field_description:product_food.field_product_template__alcohol_by_volume +msgid "Alcohol By Volume" +msgstr "Degré d'alcool" + +#. module: product_food +#: model_terms:ir.ui.view,arch_db:product_food.view_product_product_form +#: model_terms:ir.ui.view,arch_db:product_food.view_product_template_form +msgid "Alimentary Informations" +msgstr "Informations alimentaires" + +#. module: product_food +#: model:ir.model.fields,field_description:product_food.field_product_product__allergen_ids +#: model:ir.model.fields,field_description:product_food.field_product_template__allergen_ids +msgid "Allergen" +msgstr "Allergènes" + +#. module: product_food +#: model:ir.model,name:product_food.model_product_allergen +msgid "Allergens" +msgstr "Allergènes" + +#. module: product_food +#: model:ir.model.fields,field_description:product_food.field_product_product__trace_allergen_ids +#: model:ir.model.fields,field_description:product_food.field_product_template__trace_allergen_ids +msgid "Allergens (Traces)" +msgstr "Allergènes (Traces)" + +#. module: product_food +#: model:res.groups,name:product_food.group_allergen_manager +msgid "Allergens Creation" +msgstr "Création d'allergènes" + +#. module: product_food +#: model:product.template,name:product_food.product_arachide_toaste_product_template +msgid "Arachide Bio Toasté - Jean Hervé" +msgstr "" + +#. module: product_food +#: model_terms:ir.ui.view,arch_db:product_food.view_product_allergen_form +msgid "Archived" +msgstr "Archivé" + +#. module: product_food +#: model:ir.model.fields,field_description:product_food.field_product_product__best_before_date_day +#: model:ir.model.fields,field_description:product_food.field_product_template__best_before_date_day +msgid "Best Before Date Day" +msgstr "DLUO (j)" + +#. module: product_food +#: model:product.template,name:product_food.product_biere_sans_gluten_product_template +msgid "Bière Lammsbrau sans gluten 33cl" +msgstr "" + +#. module: product_food +#: model:ir.model.fields,field_description:product_food.field_product_allergen__code +msgid "Code" +msgstr "" + +#. module: product_food +#: model:ir.model.fields,field_description:product_food.field_product_category__has_alcohol +msgid "Contain Alcohol Products" +msgstr "Contient des articles avec alcool" + +#. module: product_food +#: model:ir.model.fields,field_description:product_food.field_product_category__is_alimentary +msgid "Contain Alimentary Products" +msgstr "Contient des articles alimentaires" + +#. module: product_food +#: model:ir.model.fields,field_description:product_food.field_product_category__is_vegan +msgid "Contain Vegan Products" +msgstr "Contient seulement des articles végan" + +#. module: product_food +#: model:ir.model.fields.selection,name:product_food.selection__product_product__storage_method__cool +msgid "Cool (< 4°)" +msgstr "Froid (< 4°)" + +#. module: product_food +#: model:ir.model.fields,field_description:product_food.field_product_allergen__create_uid +msgid "Created by" +msgstr "Créé par" + +#. module: product_food +#: model:ir.model.fields,field_description:product_food.field_product_allergen__create_date +msgid "Created on" +msgstr "Créé le" + +#. module: product_food +#: model_terms:ir.ui.view,arch_db:product_food.view_product_product_form +#: model_terms:ir.ui.view,arch_db:product_food.view_product_product_form_variant +#: model_terms:ir.ui.view,arch_db:product_food.view_product_template_form +msgid "Detailed Content" +msgstr "Contenu détaillé" + +#. module: product_food +#: model:ir.model.fields,field_description:product_food.field_product_allergen__display_name +msgid "Display Name" +msgstr "Nom affiché" + +#. module: product_food +#: model_terms:ir.ui.view,arch_db:product_food.view_product_product_form +#: model_terms:ir.ui.view,arch_db:product_food.view_product_product_form_variant +#: model_terms:ir.ui.view,arch_db:product_food.view_product_template_form +msgid "Food" +msgstr "Alimentaire" + +#. module: product_food +#: model:ir.model.fields.selection,name:product_food.selection__product_product__storage_method__fresh +msgid "Fresh (< 10°)" +msgstr "Frais (< 10°)" + +#. module: product_food +#: model:ir.model.fields.selection,name:product_food.selection__product_product__storage_method__frozen +msgid "Frozen (< -18°)" +msgstr "Congelé (<-18°)" + +#. module: product_food +#: model:ir.model.fields,field_description:product_food.field_product_product__has_alcohol +#: model:ir.model.fields,field_description:product_food.field_product_template__has_alcohol +msgid "Has Alcohol" +msgstr "Contient de l'alcool" + +#. module: product_food +#: model:ir.model.fields,field_description:product_food.field_product_allergen__id +msgid "ID" +msgstr "" + +#. module: product_food +#: model:ir.model.fields,help:product_food.field_product_category__is_alimentary +msgid "" +"If this box is checked, the products that belong to that category will be " +"set as 'Alimentary Product' by default" +msgstr "" +"Si cette case est cochée, les articles qui sont associés à cette categorie " +"seront marqué comme 'Articles alimentaire' par défaut" + +#. module: product_food +#: model:ir.model.fields,help:product_food.field_product_category__has_alcohol +msgid "" +"If this box is checked, the products that belong to that category will be " +"set as 'Contain alcohol' by default" +msgstr "" +"Si cette case est cochée, les articles associés à cette catégorie seront " +"cochés comme 'contient de l'alcool' par default" + +#. module: product_food +#: model:ir.model.fields,help:product_food.field_product_category__is_vegan +msgid "" +"If this box is checked, the products that belong to that category will be " +"set as 'Vegan product' by default" +msgstr "" +"Si la case est cochée, les articles qui ont cette catégorie seront marqués " +"comme 'végan' par défaut" + +#. module: product_food +#: model:ir.model.fields,help:product_food.field_product_label__is_vegan +msgid "" +"If this box is checked, the products that have this label will be set as " +"'Vegan product' by default" +msgstr "" +"Si la case est cochée, les articles qui ont ce label seront marqués comme " +"'végan' par défaut" + +#. module: product_food +#. odoo-python +#: code:addons/product_food/models/product_product.py:0 +#, python-format +msgid "Incorrect Setting. Alcohol by volume should be between 0 and 100." +msgstr "" +"Paramétrage incorrect. Le degré d'alcool doit être compris entre 0 et 100." + +#. module: product_food +#: model_terms:ir.ui.view,arch_db:product_food.view_product_allergen_form +msgid "Information" +msgstr "" + +#. module: product_food +#: model:ir.model.fields,field_description:product_food.field_product_product__ingredients +#: model:ir.model.fields,field_description:product_food.field_product_template__ingredients +#: model_terms:ir.ui.view,arch_db:product_food.view_product_product_form +#: model_terms:ir.ui.view,arch_db:product_food.view_product_product_form_variant +#: model_terms:ir.ui.view,arch_db:product_food.view_product_template_form +msgid "Ingredients" +msgstr "Ingrédients" + +#. module: product_food +#: model:ir.model.fields,field_description:product_food.field_product_product__is_alimentary +#: model:ir.model.fields,field_description:product_food.field_product_template__is_alimentary +msgid "Is Alimentary" +msgstr "Est alimentaire" + +#. module: product_food +#: model:ir.model.fields,field_description:product_food.field_product_label__is_vegan +#: model:ir.model.fields,field_description:product_food.field_product_product__is_vegan +#: model:ir.model.fields,field_description:product_food.field_product_template__is_vegan +msgid "Is Vegan" +msgstr "Est Végan" + +#. module: product_food +#: model:ir.model.fields,field_description:product_food.field_product_allergen____last_update +msgid "Last Modified on" +msgstr "Dernière modification le" + +#. module: product_food +#: model:ir.model.fields,field_description:product_food.field_product_allergen__write_uid +msgid "Last Updated by" +msgstr "Dernière mise à jour par" + +#. module: product_food +#: model:ir.model.fields,field_description:product_food.field_product_allergen__write_date +msgid "Last Updated on" +msgstr "Dernière mise à jour le" + +#. module: product_food +#: model:ir.model.fields,field_description:product_food.field_product_allergen__name +#: model_terms:ir.ui.view,arch_db:product_food.view_product_allergen_form +msgid "Name" +msgstr "Nom" + +#. module: product_food +#: model:ir.model.fields,field_description:product_food.field_product_allergen__note +#: model_terms:ir.ui.view,arch_db:product_food.view_product_allergen_form +msgid "Note" +msgstr "" + +#. module: product_food +#: model:ir.model,name:product_food.model_product_template +#: model:ir.model.fields,field_description:product_food.field_product_allergen__product_ids +msgid "Product" +msgstr "Produit" + +#. module: product_food +#: model:ir.actions.act_window,name:product_food.action_product_allergen +#: model:ir.ui.menu,name:product_food.menu_product_allergen +msgid "Product Allergens" +msgstr "Allergènes de produit" + +#. module: product_food +#: model:ir.model,name:product_food.model_product_category +msgid "Product Category" +msgstr "Catégorie de produit" + +#. module: product_food +#: model:ir.model,name:product_food.model_product_label +msgid "Product Labels" +msgstr "Labels de produits" + +#. module: product_food +#: model:ir.model,name:product_food.model_product_product +msgid "Product Variant" +msgstr "Variante de produit" + +#. module: product_food +#: model:ir.actions.act_window,name:product_food.action_product_allergen_2_product_product +#: model_terms:ir.ui.view,arch_db:product_food.view_product_allergen_form +msgid "Products" +msgstr "Articles" + +#. module: product_food +#: model:ir.model.fields,field_description:product_food.field_product_allergen__product_qty +msgid "Products Quantity" +msgstr "Quantité d'articles" + +#. module: product_food +#: model:ir.model.fields,field_description:product_food.field_product_product__storage_method +#: model:ir.model.fields,field_description:product_food.field_product_template__storage_method +msgid "Storage Method" +msgstr "Méthode de conservation" + +#. module: product_food +#: model:ir.model.fields,field_description:product_food.field_product_product__use_by_date_day +#: model:ir.model.fields,field_description:product_food.field_product_template__use_by_date_day +msgid "Use By Date Day" +msgstr "DLC (j)" + +#. module: product_food +#: model:ir.model.fields,field_description:product_food.field_product_allergen__website +msgid "Website" +msgstr "Site Web" diff --git a/product_food/models/__init__.py b/product_food/models/__init__.py new file mode 100644 index 00000000..86115d4b --- /dev/null +++ b/product_food/models/__init__.py @@ -0,0 +1,5 @@ +from . import product_allergen +from . import product_label +from . import product_category +from . import product_product +from . import product_template diff --git a/product_food/models/product_allergen.py b/product_food/models/product_allergen.py new file mode 100644 index 00000000..d5bb67c9 --- /dev/null +++ b/product_food/models/product_allergen.py @@ -0,0 +1,36 @@ +# Copyright (C) 2020 - Today: GRAP (http://www.grap.coop) +# @author: Sylvain LE GAL (https://twitter.com/legalsylvain) +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). + +from odoo import api, fields, models + + +class ProductAllergen(models.Model): + _name = "product.allergen" + _description = "Allergens" + + code = fields.Char() + + name = fields.Char(required=True) + + active = fields.Boolean(default=True) + + website = fields.Char() + + note = fields.Text() + + product_ids = fields.Many2many( + comodel_name="product.product", + relation="product_allergen_product_rel", + column1="allergen_id", + column2="product_id", + ) + + product_qty = fields.Integer( + string="Products Quantity", compute="_compute_product_qty" + ) + + @api.depends("product_ids") + def _compute_product_qty(self): + for allergen in self: + allergen.product_qty = len(allergen.product_ids) diff --git a/product_food/models/product_category.py b/product_food/models/product_category.py new file mode 100644 index 00000000..f97438f8 --- /dev/null +++ b/product_food/models/product_category.py @@ -0,0 +1,30 @@ +# Copyright (C) 2018 - Today: GRAP (http://www.grap.coop) +# @author: Sylvain LE GAL (https://twitter.com/legalsylvain) +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). + +from odoo import fields, models + + +class ProductCategory(models.Model): + _inherit = "product.category" + + is_alimentary = fields.Boolean( + string="Contain Alimentary Products", + help="If this box is checked, the" + " products that belong to that category will be set as " + "'Alimentary Product' by default", + ) + + is_vegan = fields.Boolean( + string="Contain Vegan Products", + help="If this box is checked, the" + " products that belong to that category will be set as " + "'Vegan product' by default", + ) + + has_alcohol = fields.Boolean( + string="Contain Alcohol Products", + help="If this box is checked, the" + " products that belong to that category will be set as" + " 'Contain alcohol' by default", + ) diff --git a/product_food/models/product_label.py b/product_food/models/product_label.py new file mode 100644 index 00000000..52944a1e --- /dev/null +++ b/product_food/models/product_label.py @@ -0,0 +1,16 @@ +# Copyright (C) 2012 - Today: GRAP (http://www.grap.coop) +# @author: Sylvain LE GAL (https://twitter.com/legalsylvain) +# @author Julien WESTE +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). + +from odoo import fields, models + + +class ProductLabel(models.Model): + _inherit = "product.label" + + is_vegan = fields.Boolean( + help="If this box is checked, the" + " products that have this label will be set as " + "'Vegan product' by default", + ) diff --git a/product_food/models/product_product.py b/product_food/models/product_product.py new file mode 100644 index 00000000..cc14e6b7 --- /dev/null +++ b/product_food/models/product_product.py @@ -0,0 +1,88 @@ +# Copyright (C) 2012 - Today: GRAP (http://www.grap.coop) +# @author: Sylvain LE GAL (https://twitter.com/legalsylvain) +# @author Julien WESTE +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). + +from odoo import _, api, fields, models +from odoo.exceptions import Warning as UserError + + +class ProductProduct(models.Model): + _inherit = "product.product" + + _STORAGE_METHOD_SELECTION = [ + ("fresh", "Fresh (< 10°)"), + ("cool", "Cool (< 4°)"), + ("frozen", "Frozen (< -18°)"), + ] + + is_alimentary = fields.Boolean() + + is_vegan = fields.Boolean() + + has_alcohol = fields.Boolean() + + alcohol_by_volume = fields.Float() + + use_by_date_day = fields.Integer() + + best_before_date_day = fields.Integer() + + storage_method = fields.Selection(selection=_STORAGE_METHOD_SELECTION) + + ingredients = fields.Text() + + allergen_ids = fields.Many2many( + comodel_name="product.allergen", + relation="product_allergen_product_rel", + column1="product_id", + column2="allergen_id", + ) + + trace_allergen_ids = fields.Many2many( + string="Allergens (Traces)", + comodel_name="product.allergen", + relation="product_allergen_trace_product_rel", + column1="product_id", + column2="allergen_id", + ) + + # Constrains Section + @api.constrains("alcohol_by_volume") + def _check_alcohol_by_volume(self): + if self.filtered( + lambda x: x.alcohol_by_volume < 0 or x.alcohol_by_volume > 100 + ): + raise UserError( + _( + "Incorrect Setting. Alcohol by volume should be" + " between 0 and 100." + ) + ) + + # Onchange Section + @api.onchange("categ_id") + def onchange_categ_id_product_food(self): + if self.categ_id: + self.is_alimentary = self.categ_id.is_alimentary + self.has_alcohol = self.categ_id.has_alcohol + self.is_vegan = self.categ_id.is_vegan + + @api.onchange("label_ids") + def onchange_label_ids_product_food(self): + if self.label_ids.filtered(lambda x: x.is_vegan): + self.is_vegan = True + + @api.model_create_multi + def create(self, vals_list): + for vals in vals_list: + if "categ_id" in vals: + # Guess values if not present, based on the category + categ = self.env["product.category"].browse(vals.get("categ_id")) + if "is_alimentary" not in vals: + vals["is_alimentary"] = categ.is_alimentary + if "has_alcohol" not in vals: + vals["has_alcohol"] = categ.has_alcohol + if "is_vegan" not in vals: + vals["is_vegan"] = categ.is_vegan + return super().create(vals_list) diff --git a/product_food/models/product_template.py b/product_food/models/product_template.py new file mode 100644 index 00000000..fdedafc6 --- /dev/null +++ b/product_food/models/product_template.py @@ -0,0 +1,112 @@ +# Copyright (C) 2012 - Today: GRAP (http://www.grap.coop) +# @author: Sylvain LE GAL (https://twitter.com/legalsylvain) +# @author Julien WESTE +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). + +from odoo import api, fields, models + +from .product_product import ProductProduct + + +class ProductTemplate(models.Model): + _inherit = "product.template" + + is_alimentary = fields.Boolean( + compute=lambda x: x._compute_template_field_from_variant_field("is_alimentary"), + inverse=lambda x: x._set_product_variant_field("is_alimentary"), + readonly=False, + ) + + alcohol_by_volume = fields.Float( + compute=lambda x: x._compute_template_field_from_variant_field( + "alcohol_by_volume" + ), + inverse=lambda x: x._set_product_variant_field("alcohol_by_volume"), + readonly=False, + ) + + has_alcohol = fields.Boolean( + compute=lambda x: x._compute_template_field_from_variant_field("has_alcohol"), + inverse=lambda x: x._set_product_variant_field("has_alcohol"), + readonly=False, + ) + + is_vegan = fields.Boolean( + compute=lambda x: x._compute_template_field_from_variant_field("is_vegan"), + inverse=lambda x: x._set_product_variant_field("is_vegan"), + readonly=False, + ) + + use_by_date_day = fields.Integer( + compute=lambda x: x._compute_template_field_from_variant_field( + "use_by_date_day" + ), + inverse=lambda x: x._set_product_variant_field("use_by_date_day"), + readonly=False, + ) + + best_before_date_day = fields.Integer( + compute=lambda x: x._compute_template_field_from_variant_field( + "best_before_date_day" + ), + inverse=lambda x: x._set_product_variant_field("best_before_date_day"), + readonly=False, + ) + + storage_method = fields.Selection( + compute=lambda x: x._compute_template_field_from_variant_field( + "storage_method" + ), + inverse=lambda x: x._set_product_variant_field("storage_method"), + readonly=False, + selection=lambda self: self.env["product.product"] + ._fields["storage_method"] + .selection, + ) + + ingredients = fields.Text( + compute=lambda x: x._compute_template_field_from_variant_field("ingredients"), + inverse=lambda x: x._set_product_variant_field("ingredients"), + readonly=False, + ) + + allergen_ids = fields.Many2many( + comodel_name="product.allergen", + compute=lambda x: x._compute_template_field_from_variant_field("allergen_ids"), + inverse=lambda x: x._set_product_variant_field("allergen_ids"), + readonly=False, + ) + + trace_allergen_ids = fields.Many2many( + string="Allergens (Traces)", + comodel_name="product.allergen", + compute=lambda x: x._compute_template_field_from_variant_field( + "trace_allergen_ids" + ), + inverse=lambda x: x._set_product_variant_field("trace_allergen_ids"), + readonly=False, + ) + + def _get_related_fields_variant_template(self): + res = super()._get_related_fields_variant_template() + res += [ + "is_alimentary", + "alcohol_by_volume", + "has_alcohol", + "use_by_date_day", + "best_before_date_day", + "storage_method", + "ingredients", + "allergen_ids", + "trace_allergen_ids", + ] + return res + + # Onchange Section + @api.onchange("categ_id") + def onchange_categ_id_product_food(self): + ProductProduct.onchange_categ_id_product_food(self) + + @api.onchange("label_ids") + def onchange_label_ids_product_food(self): + ProductProduct.onchange_label_ids_product_food(self) diff --git a/product_food/readme/CONFIGURE.rst b/product_food/readme/CONFIGURE.rst new file mode 100644 index 00000000..54376449 --- /dev/null +++ b/product_food/readme/CONFIGURE.rst @@ -0,0 +1,17 @@ +* Go to 'Sale > Configuration > Allergens' and create new items. + +.. figure:: ../static/description/product_allergen_form.png + +Note: Module lands with "official" allergens. + +* Go to 'Inventory > Configuration > Product Categories' and check new fields: + * ``Contain Alimentary Products`` + * ``Contain Vegan Products`` + * ``Contain Alcohol Products`` + +.. figure:: ../static/description/product_category_form.png + +* Go to 'Sale > Configuration > Product Labels' and update your labels, + setting new field ``Is Vegan``. + +.. figure:: ../static/description/product_label_form.png diff --git a/product_food/readme/CONTRIBUTORS.rst b/product_food/readme/CONTRIBUTORS.rst new file mode 100644 index 00000000..90d26307 --- /dev/null +++ b/product_food/readme/CONTRIBUTORS.rst @@ -0,0 +1,2 @@ +* Julien WESTE +* Sylvain LE GAL (https://www.twitter.com/legalsylvain) diff --git a/product_food/readme/DESCRIPTION.rst b/product_food/readme/DESCRIPTION.rst new file mode 100644 index 00000000..6a9b798b --- /dev/null +++ b/product_food/readme/DESCRIPTION.rst @@ -0,0 +1,15 @@ +This module extends the functionality of sale module to support food features. + +It provides a new model ``product.allergen`` + +It also adds many fields on product models. (templates and variants) + +* ``is_alimentary``, boolean for analysis purpose. +* ``best_before_date_day`` that mentions for how many days a product can + be eaten, after having packed. (for cheese, meats, etc.) +* ``has_alcohol``, boolean to mention if the product contains alcohol. +* ``allergen_ids`` to mention the list of allergens. +* ``ingredients``. (free text). + +Alls the fields are defined on ``product.product`` model and can be set also +on ``product.template`` models, in a mono variant context. diff --git a/product_food/readme/ROADMAP.rst b/product_food/readme/ROADMAP.rst new file mode 100644 index 00000000..99bda6bd --- /dev/null +++ b/product_food/readme/ROADMAP.rst @@ -0,0 +1,13 @@ +* It could be great to have the possibility to manage the certification + document that provide certification organization with the following model + ``res.company.certification`` and the fields ``company_id``, + ``organization_id``, ``date_start``, ``date_end`` + +* In the same way, it could be great to have the possibility to store + the certification document of each supplier with the following model + ``res.partner.certification`` and the fields ``partner_id``, + ``organization_id``, ``date_start``, ``date_end`` + +* Make a dependency to the new module ``product_net_weight`` and update + algorithm + create a new module ``product_volumen_price``. + https://github.com/OCA/product-attribute/pull/894 diff --git a/product_food/readme/USAGE.rst b/product_food/readme/USAGE.rst new file mode 100644 index 00000000..57bdb93a --- /dev/null +++ b/product_food/readme/USAGE.rst @@ -0,0 +1,3 @@ +* Go to your product variants or templates form and set the information in the following two tabs. + +.. figure:: ../static/description/product_product_alimentary_tab.png diff --git a/product_food/security/ir.model.access.csv b/product_food/security/ir.model.access.csv new file mode 100644 index 00000000..eef81325 --- /dev/null +++ b/product_food/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_allergen_manager,Product Allergen Manager,model_product_allergen,group_allergen_manager,1,1,1,1 +access_product_allergen_all,Product Allergen All,model_product_allergen,,1,,, diff --git a/product_food/security/res_groups.xml b/product_food/security/res_groups.xml new file mode 100644 index 00000000..3168c78a --- /dev/null +++ b/product_food/security/res_groups.xml @@ -0,0 +1,15 @@ + + + + + + Allergens Creation + + + + diff --git a/product_food/static/description/product_allergen_form.png b/product_food/static/description/product_allergen_form.png new file mode 100644 index 00000000..7cca2c86 Binary files /dev/null and b/product_food/static/description/product_allergen_form.png differ diff --git a/product_food/static/description/product_category_form.png b/product_food/static/description/product_category_form.png new file mode 100644 index 00000000..fc29e1f4 Binary files /dev/null and b/product_food/static/description/product_category_form.png differ diff --git a/product_food/static/description/product_label_form.png b/product_food/static/description/product_label_form.png new file mode 100644 index 00000000..0d5fbf61 Binary files /dev/null and b/product_food/static/description/product_label_form.png differ diff --git a/product_food/static/description/product_product_alimentary_tab.png b/product_food/static/description/product_product_alimentary_tab.png new file mode 100644 index 00000000..5edddc78 Binary files /dev/null and b/product_food/static/description/product_product_alimentary_tab.png differ diff --git a/product_food/static/img/label_demeter-image.png b/product_food/static/img/label_demeter-image.png new file mode 100644 index 00000000..3cf669cf Binary files /dev/null and b/product_food/static/img/label_demeter-image.png differ diff --git a/product_food/static/img/label_vegan-image.png b/product_food/static/img/label_vegan-image.png new file mode 100644 index 00000000..a43e8fd7 Binary files /dev/null and b/product_food/static/img/label_vegan-image.png differ diff --git a/product_food/tests/__init__.py b/product_food/tests/__init__.py new file mode 100644 index 00000000..d9b96c4f --- /dev/null +++ b/product_food/tests/__init__.py @@ -0,0 +1 @@ +from . import test_module diff --git a/product_food/tests/test_module.py b/product_food/tests/test_module.py new file mode 100644 index 00000000..58fb4db8 --- /dev/null +++ b/product_food/tests/test_module.py @@ -0,0 +1,43 @@ +# Copyright 2021 - Today Sylvain LE GAL (https://twitter.com/legalsylvain) +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). + +from odoo.tests.common import TransactionCase + + +class TestModule(TransactionCase): + def setUp(self): + super().setUp() + self.ProductProduct = self.env["product.product"] + self.ProductTemplate = self.env["product.template"] + self.alimentary_category = self.env.ref("product_food.alimentary_category") + self.beer_category = self.env.ref("product_food.beer_category") + self.main_category = self.env.ref("product.product_category_all") + self.uom_unit = self.env.ref("uom.product_uom_unit") + + def test_product_alimentary(self): + product = self.ProductProduct.create( + { + "name": "Product", + "uom_id": self.uom_unit.id, + "uom_po_id": self.uom_unit.id, + "categ_id": self.alimentary_category.id, + } + ) + self.assertEqual(product.is_alimentary, True) + + # Affect product to a non-alimentary category and run onchange + product.categ_id = self.main_category.id + product.onchange_categ_id_product_food() + self.assertEqual(product.is_alimentary, False) + + def test_product_alcohol(self): + product = self.ProductProduct.create( + { + "name": "Product", + "uom_id": self.uom_unit.id, + "uom_po_id": self.uom_unit.id, + "categ_id": self.beer_category.id, + } + ) + self.assertEqual(product.is_alimentary, True) + self.assertEqual(product.has_alcohol, True) diff --git a/product_food/views/view_product_allergen.xml b/product_food/views/view_product_allergen.xml new file mode 100644 index 00000000..0fcb5fb8 --- /dev/null +++ b/product_food/views/view_product_allergen.xml @@ -0,0 +1,81 @@ + + + + + + Products + product.product + tree,form + [('allergen_ids', '=', active_id)] + + + + product.allergen + + + + + + + + + + + product.allergen + +
+ +
+ + + + + +
+
+

+ +

+
+ + + + + + + + + + + + + +
+
+
+
+ + + Product Allergens + ir.actions.act_window + product.allergen + tree,form + + + + +
diff --git a/product_food/views/view_product_category.xml b/product_food/views/view_product_category.xml new file mode 100644 index 00000000..abb908c6 --- /dev/null +++ b/product_food/views/view_product_category.xml @@ -0,0 +1,33 @@ + + + + + + product.category + + + + + + + + + + + + product.category + + + + + + + + + + + diff --git a/product_food/views/view_product_label.xml b/product_food/views/view_product_label.xml new file mode 100644 index 00000000..455ec2d9 --- /dev/null +++ b/product_food/views/view_product_label.xml @@ -0,0 +1,30 @@ + + + + + + product.label + + + + + + + + + + product.label + + + + + + + + + diff --git a/product_food/views/view_product_product.xml b/product_food/views/view_product_product.xml new file mode 100644 index 00000000..ef59f8d8 --- /dev/null +++ b/product_food/views/view_product_product.xml @@ -0,0 +1,95 @@ + + + + + + product.product + + + + + + + + + + + + + + + + + + + + product.product + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + product.product + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/product_food/views/view_product_template.xml b/product_food/views/view_product_template.xml new file mode 100644 index 00000000..8b87d7ab --- /dev/null +++ b/product_food/views/view_product_template.xml @@ -0,0 +1,64 @@ + + + + + + product.template + + + + + + + + + + + + + + + + + + + + product.template + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/product_label/__manifest__.py b/product_label/__manifest__.py index 27c7754a..9a72a67e 100644 --- a/product_label/__manifest__.py +++ b/product_label/__manifest__.py @@ -14,6 +14,7 @@ "depends": [ # OCA "product_usability", + "product_compute_template_field_from_variant_helper", ], "data": [ "security/res_groups.xml", diff --git a/product_label/i18n/fr.po b/product_label/i18n/fr.po index 8093976d..0fbebe58 100644 --- a/product_label/i18n/fr.po +++ b/product_label/i18n/fr.po @@ -1,6 +1,6 @@ # Translation of Odoo Server. # This file contains the translation of the following modules: -# * product_label +# * product_label # msgid "" msgstr "" @@ -84,11 +84,29 @@ msgid "ID" msgstr "" #. module: product_label -#: model:ir.model.fields,field_description:product_label.field_product_label__image -#: model:ir.model.fields,field_description:product_label.field_product_label__image_medium -#: model:ir.model.fields,field_description:product_label.field_product_label__image_small +#: model:ir.model.fields,field_description:product_label.field_product_label__image_1920 msgid "Image" -msgstr "Image de taille moyenne" +msgstr "" + +#. module: product_label +#: model:ir.model.fields,field_description:product_label.field_product_label__image_1024 +msgid "Image 1024" +msgstr "" + +#. module: product_label +#: model:ir.model.fields,field_description:product_label.field_product_label__image_128 +msgid "Image 128" +msgstr "" + +#. module: product_label +#: model:ir.model.fields,field_description:product_label.field_product_label__image_256 +msgid "Image 256" +msgstr "" + +#. module: product_label +#: model:ir.model.fields,field_description:product_label.field_product_label__image_512 +msgid "Image 512" +msgstr "" #. module: product_label #: model:product.template,name:product_label.infusion_girl_power_product_template @@ -101,10 +119,8 @@ msgid "Label Name" msgstr "Nom du label" #. module: product_label -#: model:ir.actions.act_window,name:product_label.action_product_label #: model:ir.model.fields,field_description:product_label.field_product_product__label_ids #: model:ir.model.fields,field_description:product_label.field_product_template__label_ids -#: model:ir.ui.menu,name:product_label.menu_product_label msgid "Labels" msgstr "" @@ -139,7 +155,9 @@ msgid "Product" msgstr "Produit" #. module: product_label +#: model:ir.actions.act_window,name:product_label.action_product_label #: model:ir.model,name:product_label.model_product_label +#: model:ir.ui.menu,name:product_label.menu_product_label msgid "Product Labels" msgstr "Labels de produits" @@ -173,4 +191,4 @@ msgstr "Site Web" #. module: product_label #: model_terms:ir.ui.view,arch_db:product_label.view_product_label_form msgid "e.g. Organic label" -msgstr "ex : Agriculture biologique" \ No newline at end of file +msgstr "ex : Agriculture biologique" diff --git a/product_label/models/product_template.py b/product_label/models/product_template.py index 2153d5e3..6ade7899 100644 --- a/product_label/models/product_template.py +++ b/product_label/models/product_template.py @@ -4,33 +4,20 @@ # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). -from odoo import api, fields, models +from odoo import fields, models class ProductTemplate(models.Model): _inherit = "product.template" label_ids = fields.Many2many( - comodel_name="product.label", - compute="_compute_label_ids", - inverse="_inverse_label_ids", string="Labels", + comodel_name="product.label", + compute=lambda x: x._compute_template_field_from_variant_field("label_ids"), + inverse=lambda x: x._set_product_variant_field("label_ids"), readonly=False, ) - @api.depends("product_variant_ids", "product_variant_ids.label_ids") - def _compute_label_ids(self): - for p in self: - if len(p.product_variant_ids) == 1: - p.label_ids = p.product_variant_ids.label_ids - else: - p.label_ids = False - - def _inverse_label_ids(self): - for p in self: - if len(p.product_variant_ids) == 1: - p.product_variant_ids.label_ids = p.label_ids - def _get_related_fields_variant_template(self): res = super()._get_related_fields_variant_template() res.append("label_ids") diff --git a/product_label/views/view_product_label.xml b/product_label/views/view_product_label.xml index 75ee9b80..a6d3b23c 100644 --- a/product_label/views/view_product_label.xml +++ b/product_label/views/view_product_label.xml @@ -110,7 +110,7 @@ License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). - Labels + Product Labels ir.actions.act_window product.label kanban,tree,form diff --git a/product_label/views/view_product_product.xml b/product_label/views/view_product_product.xml index 9ac6b227..f9407932 100644 --- a/product_label/views/view_product_product.xml +++ b/product_label/views/view_product_product.xml @@ -31,11 +31,9 @@ License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). product.product - - - - - + + + diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 00000000..9cd16292 --- /dev/null +++ b/requirements.txt @@ -0,0 +1 @@ +# generated from manifests external_dependencies diff --git a/setup/product_food/odoo/addons/product_food b/setup/product_food/odoo/addons/product_food new file mode 120000 index 00000000..3081e900 --- /dev/null +++ b/setup/product_food/odoo/addons/product_food @@ -0,0 +1 @@ +../../../../product_food \ No newline at end of file diff --git a/setup/product_food/setup.py b/setup/product_food/setup.py new file mode 100644 index 00000000..28c57bb6 --- /dev/null +++ b/setup/product_food/setup.py @@ -0,0 +1,6 @@ +import setuptools + +setuptools.setup( + setup_requires=['setuptools-odoo'], + odoo_addon=True, +) diff --git a/test-requirements.txt b/test-requirements.txt index 7293fdbe..125832f7 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -1,4 +1,5 @@ # Unmerged OCA modules +git+https://github.com/OCA/product-attributes@refs/pull/1840/head#subdirectory=setup/product_compute_template_field_from_variant_helper git+https://github.com/OCA/manufacture@refs/pull/1227/head#subdirectory=setup/mrp_bom_product_price_margin git+https://github.com/OCA/manufacture@refs/pull/1255/head#subdirectory=setup/mrp_product_characterisation