diff --git a/product_route_profile/README.rst b/product_route_profile/README.rst new file mode 100644 index 000000000000..9b6c50c807c7 --- /dev/null +++ b/product_route_profile/README.rst @@ -0,0 +1,102 @@ +===================== +Product Route Profile +===================== + +.. + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! This file is generated by oca-gen-addon-readme !! + !! changes will be overwritten. !! + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! source digest: sha256:5e5c1b0e163469f9f69b63c044b7d995e7c4ac4c0763450fd8075f60d84ecdc5 + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + +.. |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%2Fstock--logistics--warehouse-lightgray.png?logo=github + :target: https://github.com/OCA/stock-logistics-warehouse/tree/14.0/product_route_profile + :alt: OCA/stock-logistics-warehouse +.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png + :target: https://translation.odoo-community.org/projects/stock-logistics-warehouse-14-0/stock-logistics-warehouse-14-0-product_route_profile + :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/stock-logistics-warehouse&target_branch=14.0 + :alt: Try me on Runboat + +|badge1| |badge2| |badge3| |badge4| |badge5| + +This module replaces the initial concept of route_ids with a new concept of "route profile", coming with a company-specific and priority route profile. + +**Table of contents** + +.. contents:: + :local: + +Usage +===== + +**Route profile** +In Inventory > Configuration > Settings > Routes Profiles +- Create some Route profile depending on your needs + + +**On product** +On each template product, in inventory page, we can select: +- **Route Profile**: a default profile, common to all companies +- **Priority Route Profile**: a profile specific to each company and priority if existing. + +Known issues / Roadmap +====================== + +Tests of this module are running separately than the other tests. + +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 +~~~~~~~ + +* Akretion + +Contributors +~~~~~~~~~~~~ + +* Kévin Roche + +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-Kev-Roche| image:: https://github.com/Kev-Roche.png?size=40px + :target: https://github.com/Kev-Roche + :alt: Kev-Roche + +Current `maintainer `__: + +|maintainer-Kev-Roche| + +This module is part of the `OCA/stock-logistics-warehouse `_ project on GitHub. + +You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute. diff --git a/product_route_profile/__init__.py b/product_route_profile/__init__.py new file mode 100644 index 000000000000..cc6b6354ad8f --- /dev/null +++ b/product_route_profile/__init__.py @@ -0,0 +1,2 @@ +from . import models +from .hooks import post_init_hook diff --git a/product_route_profile/__manifest__.py b/product_route_profile/__manifest__.py new file mode 100644 index 000000000000..7519aed60e56 --- /dev/null +++ b/product_route_profile/__manifest__.py @@ -0,0 +1,25 @@ +# Copyright 2022 Akretion (https://www.akretion.com). +# @author Kévin Roche +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +{ + "name": "Product Route Profile", + "summary": "Add Route profile concept on product", + "version": "15.0.1.0.0", + "category": "Warehouse", + "website": "https://github.com/OCA/stock-logistics-warehouse", + "author": "Akretion, Odoo Community Association (OCA)", + "maintainers": ["Kev-Roche"], + "license": "AGPL-3", + "application": False, + "installable": True, + "depends": [ + "stock", + ], + "data": [ + "views/route_profile.xml", + "views/product_template.xml", + "security/ir.model.access.csv", + ], + "post_init_hook": "post_init_hook", +} diff --git a/product_route_profile/hooks.py b/product_route_profile/hooks.py new file mode 100644 index 000000000000..c0ba61394c31 --- /dev/null +++ b/product_route_profile/hooks.py @@ -0,0 +1,43 @@ +# Copyright (C) 2022 Akretion (). +# @author Kévin Roche +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from collections import defaultdict + +from odoo import SUPERUSER_ID, api + + +def post_init_hook(cr, registry): + def get_profile(route_ids): + route_ids = tuple(set(route_ids)) + profile = route2profile.get(route_ids) + if not profile: + profile_name = "" + route_names = [ + rec.name for rec in env["stock.location.route"].browse(route_ids) + ] + profile_name = " / ".join(route_names) + profile = env["route.profile"].create( + { + "name": profile_name, + "route_ids": [(6, 0, route_ids)], + } + ) + route2profile[route_ids] = profile + return profile + + env = api.Environment(cr, SUPERUSER_ID, {}) + query = """ + SELECT product_id, array_agg(route_id) + FROM stock_route_product group by product_id; + """ + cr.execute(query) + results = cr.fetchall() + route2profile = {} + profile2product = defaultdict(lambda: env["product.template"]) + for row in results: + profile = get_profile(row[1]) + profile2product[profile.id] |= env["product.template"].browse(row[0]) + + for profile in profile2product: + profile2product[profile].write({"route_profile_id": profile}) diff --git a/product_route_profile/i18n/fr.po b/product_route_profile/i18n/fr.po new file mode 100644 index 000000000000..d0299f681144 --- /dev/null +++ b/product_route_profile/i18n/fr.po @@ -0,0 +1,130 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * product_route_profile +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 14.0\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2022-04-27 18:10+0000\n" +"PO-Revision-Date: 2022-04-27 20:13+0200\n" +"Last-Translator: \n" +"Language-Team: \n" +"Language: fr\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: \n" +"X-Generator: Poedit 3.0.1\n" + +#. module: product_route_profile +#: model:ir.model.fields,field_description:product_route_profile.field_route_profile__company_id +msgid "Company" +msgstr "" + +#. module: product_route_profile +#: model:ir.model.fields,field_description:product_route_profile.field_route_profile__create_uid +msgid "Created by" +msgstr "" + +#. module: product_route_profile +#: model:ir.model.fields,field_description:product_route_profile.field_route_profile__create_date +msgid "Created on" +msgstr "" + +#. module: product_route_profile +#: model:ir.model.fields,help:product_route_profile.field_product_product__route_ids +#: model:ir.model.fields,help:product_route_profile.field_product_template__route_ids +msgid "" +"Depending on the modules installed, this will allow you to define the route " +"of the product: whether it will be bought, manufactured, replenished on " +"order, etc." +msgstr "" +"En fonction des modules installés, cela va vous permettre de définir les " +"routes sur l'article: acheter, fabriquer, réapprovisionner sur commande, etc." + +#. module: product_route_profile +#: model:ir.model.fields,field_description:product_route_profile.field_product_template__display_name +#: model:ir.model.fields,field_description:product_route_profile.field_route_profile__display_name +msgid "Display Name" +msgstr "Nom" + +#. module: product_route_profile +#: model:ir.model.fields,field_description:product_route_profile.field_product_template__id +#: model:ir.model.fields,field_description:product_route_profile.field_route_profile__id +msgid "ID" +msgstr "" + +#. module: product_route_profile +#: model:ir.model.fields,help:product_route_profile.field_product_product__force_route_profile_id +#: model:ir.model.fields,help:product_route_profile.field_product_template__force_route_profile_id +msgid "" +"If defined, the priority route profile will be used and will replace the " +"route profile, only for this company." +msgstr "" + +#. module: product_route_profile +#: model:ir.model.fields,field_description:product_route_profile.field_product_template____last_update +#: model:ir.model.fields,field_description:product_route_profile.field_route_profile____last_update +msgid "Last Modified on" +msgstr "Dernière modification le" + +#. module: product_route_profile +#: model:ir.model.fields,field_description:product_route_profile.field_route_profile__write_uid +msgid "Last Updated by" +msgstr "" + +#. module: product_route_profile +#: model:ir.model.fields,field_description:product_route_profile.field_route_profile__write_date +msgid "Last Updated on" +msgstr "" + +#. module: product_route_profile +#: model:ir.model.fields,field_description:product_route_profile.field_route_profile__name +msgid "Name" +msgstr "" + +#. module: product_route_profile +#: model:ir.model.fields,field_description:product_route_profile.field_product_product__force_route_profile_id +#: model:ir.model.fields,field_description:product_route_profile.field_product_template__force_route_profile_id +msgid "Priority Route Profile" +msgstr "Profil de Routes Prioritaires" + +#. module: product_route_profile +#: model:ir.model,name:product_route_profile.model_product_template +msgid "Product Template" +msgstr "Modèle de produit" + +#. module: product_route_profile +#: model:ir.model,name:product_route_profile.model_route_profile +#: model:ir.model.fields,field_description:product_route_profile.field_product_product__route_profile_id +#: model:ir.model.fields,field_description:product_route_profile.field_product_template__route_profile_id +msgid "Route Profile" +msgstr "Profil de routes" + +#. module: product_route_profile +#: model:ir.model.fields,field_description:product_route_profile.field_product_product__route_ids +#: model:ir.model.fields,field_description:product_route_profile.field_product_template__route_ids +#: model:ir.model.fields,field_description:product_route_profile.field_route_profile__route_ids +msgid "Routes" +msgstr "Routes" + +#. module: product_route_profile +#: model:ir.actions.act_window,name:product_route_profile.action_route_profile_form +#: model:ir.ui.menu,name:product_route_profile.menu_route_profile_config +#: model_terms:ir.ui.view,arch_db:product_route_profile.route_profile_form +msgid "Routes Profiles" +msgstr "Profils de Routes" + +#. module: product_route_profile +#: model_terms:ir.actions.act_window,help:product_route_profile.action_route_profile_form +msgid "" +"You can define here the routes profiles that run through\n" +" your warehouses and that define the flows of your products.\n" +" A route profile can be set on each product as \"Route Profile" +"\" or \"Priority Route Profile\" (company dependent)." +msgstr "" +"Vous pouvez définir ici les routes qui régissent les mouvements de vos " +"produits dans vos entrepôts. \n" +"Un profil de route peut être défini pour chaque produit en tant que \"Profil " +"de Routes\" ou \"Profil de Routes Prioritaires\" (société dépendant)." diff --git a/product_route_profile/i18n/nl.po b/product_route_profile/i18n/nl.po new file mode 100644 index 000000000000..040855be1c40 --- /dev/null +++ b/product_route_profile/i18n/nl.po @@ -0,0 +1,134 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * product_route_profile +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 14.0\n" +"Report-Msgid-Bugs-To: \n" +"PO-Revision-Date: 2023-06-23 19:09+0000\n" +"Last-Translator: Bosd \n" +"Language-Team: none\n" +"Language: nl\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: product_route_profile +#: model:ir.model.fields,field_description:product_route_profile.field_route_profile__company_id +msgid "Company" +msgstr "Bedrijf" + +#. module: product_route_profile +#: model:ir.model.fields,field_description:product_route_profile.field_route_profile__create_uid +msgid "Created by" +msgstr "Aangemaakt door" + +#. module: product_route_profile +#: model:ir.model.fields,field_description:product_route_profile.field_route_profile__create_date +msgid "Created on" +msgstr "Aangemaakt op" + +#. module: product_route_profile +#: model:ir.model.fields,help:product_route_profile.field_product_product__route_ids +#: model:ir.model.fields,help:product_route_profile.field_product_template__route_ids +msgid "" +"Depending on the modules installed, this will allow you to define the route " +"of the product: whether it will be bought, manufactured, replenished on " +"order, etc." +msgstr "" +"Afhankelijk van de geïnstalleerde modules kunt u hiermee de route van het " +"product bepalen: of het wordt gekocht, geproduceerd, op bestelling wordt " +"aangevuld, enz." + +#. module: product_route_profile +#: model:ir.model.fields,field_description:product_route_profile.field_product_template__display_name +#: model:ir.model.fields,field_description:product_route_profile.field_route_profile__display_name +msgid "Display Name" +msgstr "Weergavenaam" + +#. module: product_route_profile +#: model:ir.model.fields,field_description:product_route_profile.field_product_template__id +#: model:ir.model.fields,field_description:product_route_profile.field_route_profile__id +msgid "ID" +msgstr "ID" + +#. module: product_route_profile +#: model:ir.model.fields,help:product_route_profile.field_product_product__force_route_profile_id +#: model:ir.model.fields,help:product_route_profile.field_product_template__force_route_profile_id +msgid "" +"If defined, the priority route profile will be used and will replace the " +"route profile, only for this company." +msgstr "" +"Indien gedefinieerd, wordt het prioritaire routeprofiel gebruikt en vervangt " +"het het routeprofiel, enkel voor dit bedrijf." + +#. module: product_route_profile +#: model:ir.model.fields,field_description:product_route_profile.field_product_template____last_update +#: model:ir.model.fields,field_description:product_route_profile.field_route_profile____last_update +msgid "Last Modified on" +msgstr "Laatst bijgewerkt op" + +#. module: product_route_profile +#: model:ir.model.fields,field_description:product_route_profile.field_route_profile__write_uid +msgid "Last Updated by" +msgstr "Laatst bijgewerkt door" + +#. module: product_route_profile +#: model:ir.model.fields,field_description:product_route_profile.field_route_profile__write_date +msgid "Last Updated on" +msgstr "Laatst bijgewerkt op" + +#. module: product_route_profile +#: model:ir.model.fields,field_description:product_route_profile.field_route_profile__name +msgid "Name" +msgstr "Naam" + +#. module: product_route_profile +#: model:ir.model.fields,field_description:product_route_profile.field_product_product__force_route_profile_id +#: model:ir.model.fields,field_description:product_route_profile.field_product_template__force_route_profile_id +msgid "Priority Route Profile" +msgstr "Prioriteit Roue Profiel" + +#. module: product_route_profile +#: model:ir.model,name:product_route_profile.model_product_template +msgid "Product Template" +msgstr "Productsjabloon" + +#. module: product_route_profile +#: model:ir.model,name:product_route_profile.model_route_profile +#: model:ir.model.fields,field_description:product_route_profile.field_product_product__route_profile_id +#: model:ir.model.fields,field_description:product_route_profile.field_product_template__route_profile_id +msgid "Route Profile" +msgstr "Route Profiel" + +#. module: product_route_profile +#: model:ir.model.fields,field_description:product_route_profile.field_product_product__route_ids +#: model:ir.model.fields,field_description:product_route_profile.field_product_template__route_ids +#: model:ir.model.fields,field_description:product_route_profile.field_route_profile__route_ids +msgid "Routes" +msgstr "Routes" + +#. module: product_route_profile +#: model:ir.actions.act_window,name:product_route_profile.action_route_profile_form +#: model:ir.ui.menu,name:product_route_profile.menu_route_profile_config +#: model_terms:ir.ui.view,arch_db:product_route_profile.route_profile_form +msgid "Routes Profiles" +msgstr "Route Profielen" + +#. module: product_route_profile +#: model_terms:ir.actions.act_window,help:product_route_profile.action_route_profile_form +msgid "" +"You can define here the routes profiles that run through\n" +" your warehouses and that define the flows of your products.\n" +" A route profile can be set on each product as \"Route Profile" +"\" or \"Priority Route Profile\" (company dependent)." +msgstr "" +"U kunt hier de routeprofielen definiëren die door\n" +" uw magazijnen lopen en die de stromen van uw producten " +"bepalen.\n" +" Een routeprofiel kan voor elk product worden ingesteld als " +"\"Routeprofiel\" of \"Prioriteit routeprofiel\" (afhankelijk van het " +"bedrijf)." diff --git a/product_route_profile/i18n/product_route_profile.pot b/product_route_profile/i18n/product_route_profile.pot new file mode 100644 index 000000000000..60b1007f423c --- /dev/null +++ b/product_route_profile/i18n/product_route_profile.pot @@ -0,0 +1,119 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * product_route_profile +# +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_route_profile +#: model:ir.model.fields,field_description:product_route_profile.field_route_profile__company_id +msgid "Company" +msgstr "" + +#. module: product_route_profile +#: model:ir.model.fields,field_description:product_route_profile.field_route_profile__create_uid +msgid "Created by" +msgstr "" + +#. module: product_route_profile +#: model:ir.model.fields,field_description:product_route_profile.field_route_profile__create_date +msgid "Created on" +msgstr "" + +#. module: product_route_profile +#: model:ir.model.fields,help:product_route_profile.field_product_product__route_ids +#: model:ir.model.fields,help:product_route_profile.field_product_template__route_ids +msgid "" +"Depending on the modules installed, this will allow you to define the route " +"of the product: whether it will be bought, manufactured, replenished on " +"order, etc." +msgstr "" + +#. module: product_route_profile +#: model:ir.model.fields,field_description:product_route_profile.field_product_template__display_name +#: model:ir.model.fields,field_description:product_route_profile.field_route_profile__display_name +msgid "Display Name" +msgstr "" + +#. module: product_route_profile +#: model:ir.model.fields,field_description:product_route_profile.field_product_template__id +#: model:ir.model.fields,field_description:product_route_profile.field_route_profile__id +msgid "ID" +msgstr "" + +#. module: product_route_profile +#: model:ir.model.fields,help:product_route_profile.field_product_product__force_route_profile_id +#: model:ir.model.fields,help:product_route_profile.field_product_template__force_route_profile_id +msgid "" +"If defined, the priority route profile will be used and will replace the " +"route profile, only for this company." +msgstr "" + +#. module: product_route_profile +#: model:ir.model.fields,field_description:product_route_profile.field_product_template____last_update +#: model:ir.model.fields,field_description:product_route_profile.field_route_profile____last_update +msgid "Last Modified on" +msgstr "" + +#. module: product_route_profile +#: model:ir.model.fields,field_description:product_route_profile.field_route_profile__write_uid +msgid "Last Updated by" +msgstr "" + +#. module: product_route_profile +#: model:ir.model.fields,field_description:product_route_profile.field_route_profile__write_date +msgid "Last Updated on" +msgstr "" + +#. module: product_route_profile +#: model:ir.model.fields,field_description:product_route_profile.field_route_profile__name +msgid "Name" +msgstr "" + +#. module: product_route_profile +#: model:ir.model.fields,field_description:product_route_profile.field_product_product__force_route_profile_id +#: model:ir.model.fields,field_description:product_route_profile.field_product_template__force_route_profile_id +msgid "Priority Route Profile" +msgstr "" + +#. module: product_route_profile +#: model:ir.model,name:product_route_profile.model_product_template +msgid "Product Template" +msgstr "" + +#. module: product_route_profile +#: model:ir.model,name:product_route_profile.model_route_profile +#: model:ir.model.fields,field_description:product_route_profile.field_product_product__route_profile_id +#: model:ir.model.fields,field_description:product_route_profile.field_product_template__route_profile_id +msgid "Route Profile" +msgstr "" + +#. module: product_route_profile +#: model:ir.model.fields,field_description:product_route_profile.field_product_product__route_ids +#: model:ir.model.fields,field_description:product_route_profile.field_product_template__route_ids +#: model:ir.model.fields,field_description:product_route_profile.field_route_profile__route_ids +msgid "Routes" +msgstr "" + +#. module: product_route_profile +#: model:ir.actions.act_window,name:product_route_profile.action_route_profile_form +#: model:ir.ui.menu,name:product_route_profile.menu_route_profile_config +#: model_terms:ir.ui.view,arch_db:product_route_profile.route_profile_form +msgid "Routes Profiles" +msgstr "" + +#. module: product_route_profile +#: model_terms:ir.actions.act_window,help:product_route_profile.action_route_profile_form +msgid "" +"You can define here the routes profiles that run through\n" +" your warehouses and that define the flows of your products.\n" +" A route profile can be set on each product as \"Route Profile\" or \"Priority Route Profile\" (company dependent)." +msgstr "" diff --git a/product_route_profile/models/__init__.py b/product_route_profile/models/__init__.py new file mode 100644 index 000000000000..a07d89f7332e --- /dev/null +++ b/product_route_profile/models/__init__.py @@ -0,0 +1,2 @@ +from . import route_profile +from . import product_template diff --git a/product_route_profile/models/product_template.py b/product_route_profile/models/product_template.py new file mode 100644 index 000000000000..a6b158975f68 --- /dev/null +++ b/product_route_profile/models/product_template.py @@ -0,0 +1,74 @@ +# Copyright 2022 Akretion (https://www.akretion.com). +# @author Kévin Roche +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +from odoo import api, fields, models + + +class ProductTemplate(models.Model): + _inherit = "product.template" + + route_profile_id = fields.Many2one("route.profile", string="Route Profile") + force_route_profile_id = fields.Many2one( + "route.profile", + string="Priority Route Profile", + company_dependent=True, + help="If defined, the " + "priority route profile will be used and will replace the " + "route profile, only for this company.", + ) + + route_ids = fields.Many2many( + compute="_compute_route_ids", + inverse="_inverse_route_ids", + search="_search_route_ids", + store=False, + ) + + @api.depends("route_profile_id", "force_route_profile_id") + @api.depends_context("company") + def _compute_route_ids(self): + for rec in self.sudo(): + if rec.force_route_profile_id: + rec.route_ids = [(6, 0, rec.force_route_profile_id.route_ids.ids)] + elif rec.route_profile_id: + rec.route_ids = [(6, 0, rec.route_profile_id.route_ids.ids)] + else: + rec.route_ids = False + + def _search_route_ids(self, operator, value): + return [ + "|", + ("force_route_profile_id.route_ids", operator, value), + "&", + ("force_route_profile_id", "=", False), + ("route_profile_id.route_ids", operator, value), + ] + + def _inverse_route_ids(self): + if self._context.get("skip_inverse_route_ids"): + return + profiles = self.env["route.profile"].search([]) + for rec in self: + for profile in profiles: + if rec.route_ids == profile.route_ids: + rec.route_profile_id = profile + break + else: + vals = rec._prepare_profile() + rec.route_profile_id = self.env["route.profile"].create(vals) + + def _prepare_profile(self): + return { + "name": " / ".join(self.route_ids.mapped("name")), + "route_ids": [(6, 0, self.route_ids.ids)], + } + + @api.model + def create(self, vals): + route_profile_id = vals.get("route_profile_id", False) + if route_profile_id: + route_profile = self.env["route.profile"].browse(route_profile_id) + vals["route_ids"] = [(6, 0, route_profile.route_ids.ids)] + self = self.with_context(skip_inverse_route_ids=True) + return super(ProductTemplate, self).create(vals) diff --git a/product_route_profile/models/route_profile.py b/product_route_profile/models/route_profile.py new file mode 100644 index 000000000000..12df849b49b4 --- /dev/null +++ b/product_route_profile/models/route_profile.py @@ -0,0 +1,23 @@ +# Copyright 2022 Akretion (https://www.akretion.com). +# @author Kévin Roche +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +from odoo import fields, models + + +class RouteProfile(models.Model): + _name = "route.profile" + _description = "Route Profile" + + name = fields.Char() + company_id = fields.Many2one( + comodel_name="res.company", + default=lambda self: self.env.company.id, + required=False, + string="Company", + ) + route_ids = fields.Many2many( + "stock.location.route", + string="Routes", + domain=[("product_selectable", "=", True)], + ) diff --git a/product_route_profile/readme/CONTRIBUTORS.rst b/product_route_profile/readme/CONTRIBUTORS.rst new file mode 100644 index 000000000000..dcae277c8c6e --- /dev/null +++ b/product_route_profile/readme/CONTRIBUTORS.rst @@ -0,0 +1 @@ +* Kévin Roche diff --git a/product_route_profile/readme/DESCRIPTION.rst b/product_route_profile/readme/DESCRIPTION.rst new file mode 100644 index 000000000000..ebec5c327f60 --- /dev/null +++ b/product_route_profile/readme/DESCRIPTION.rst @@ -0,0 +1 @@ +This module replaces the initial concept of route_ids with a new concept of "route profile", coming with a company-specific and priority route profile. diff --git a/product_route_profile/readme/ROADMAP.rst b/product_route_profile/readme/ROADMAP.rst new file mode 100644 index 000000000000..2e585f331539 --- /dev/null +++ b/product_route_profile/readme/ROADMAP.rst @@ -0,0 +1 @@ +Tests of this module are running separately than the other tests. diff --git a/product_route_profile/readme/USAGE.rst b/product_route_profile/readme/USAGE.rst new file mode 100644 index 000000000000..c1c836fd19c7 --- /dev/null +++ b/product_route_profile/readme/USAGE.rst @@ -0,0 +1,9 @@ +**Route profile** +In Inventory > Configuration > Settings > Routes Profiles +- Create some Route profile depending on your needs + + +**On product** +On each template product, in inventory page, we can select: +- **Route Profile**: a default profile, common to all companies +- **Priority Route Profile**: a profile specific to each company and priority if existing. diff --git a/product_route_profile/security/ir.model.access.csv b/product_route_profile/security/ir.model.access.csv new file mode 100644 index 000000000000..ecd8a85c3bd1 --- /dev/null +++ b/product_route_profile/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_route_profile_manager,access_route_profile_manager,model_route_profile,stock.group_stock_manager,1,1,1,1 +access_route_profile_user,access_route_profile_user,model_route_profile,stock.group_stock_user,1,0,0,0 diff --git a/product_route_profile/static/description/icon.png b/product_route_profile/static/description/icon.png new file mode 100644 index 000000000000..252978613ce7 Binary files /dev/null and b/product_route_profile/static/description/icon.png differ diff --git a/product_route_profile/static/description/icon.svg b/product_route_profile/static/description/icon.svg new file mode 100644 index 000000000000..0992dead20b6 --- /dev/null +++ b/product_route_profile/static/description/icon.svg @@ -0,0 +1,1803 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/product_route_profile/static/description/index.html b/product_route_profile/static/description/index.html new file mode 100644 index 000000000000..6e86bf43d1b3 --- /dev/null +++ b/product_route_profile/static/description/index.html @@ -0,0 +1,439 @@ + + + + + + +Product Route Profile + + + +
+

Product Route Profile

+ + +

Beta License: AGPL-3 OCA/stock-logistics-warehouse Translate me on Weblate Try me on Runboat

+

This module replaces the initial concept of route_ids with a new concept of “route profile”, coming with a company-specific and priority route profile.

+

Table of contents

+ +
+

Usage

+

Route profile +In Inventory > Configuration > Settings > Routes Profiles +- Create some Route profile depending on your needs

+

On product +On each template product, in inventory page, we can select: +- Route Profile: a default profile, common to all companies +- Priority Route Profile: a profile specific to each company and priority if existing.

+
+
+

Known issues / Roadmap

+

Tests of this module are running separately than the other tests.

+
+
+

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

+
    +
  • Akretion
  • +
+
+
+

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.

+

Current maintainer:

+

Kev-Roche

+

This module is part of the OCA/stock-logistics-warehouse project on GitHub.

+

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

+
+
+
+ + diff --git a/product_route_profile/tests/__init__.py b/product_route_profile/tests/__init__.py new file mode 100644 index 000000000000..320c379248ed --- /dev/null +++ b/product_route_profile/tests/__init__.py @@ -0,0 +1 @@ +from . import test_product_route_profile diff --git a/product_route_profile/tests/test_product_route_profile.py b/product_route_profile/tests/test_product_route_profile.py new file mode 100644 index 000000000000..2ff6636e419f --- /dev/null +++ b/product_route_profile/tests/test_product_route_profile.py @@ -0,0 +1,83 @@ +# Copyright 2022 Akretion (https://www.akretion.com). +# @author Kévin Roche +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +from odoo.tests.common import TransactionCase + + +class TestProductRouteProfile(TransactionCase): + @classmethod + def setUpClass(cls): + super(TestProductRouteProfile, cls).setUpClass() + + cls.company_bis = cls.env["res.company"].create( + { + "name": "company 2", + "parent_id": cls.env.ref("base.main_company").id, + } + ) + + cls.route_1 = cls.env.ref("stock.route_warehouse0_mto") + cls.route_1.active = True + cls.route_2 = cls.route_1.copy({"name": "route 2"}) + + cls.route_profile_1 = cls.env["route.profile"].create( + { + "name": "profile 1", + "route_ids": [(6, 0, [cls.route_1.id])], + } + ) + cls.route_profile_2 = cls.env["route.profile"].create( + { + "name": "profile 2", + "route_ids": [(6, 0, [cls.route_2.id])], + } + ) + + cls.product = cls.env["product.template"].create( + { + "name": "Template 1", + "company_id": False, + } + ) + + def test_1_route_profile(self): + self.product.route_profile_id = self.route_profile_1.id + self.assertEqual(self.product.route_ids, self.route_profile_1.route_ids) + # In other company, no change + self.assertEqual( + self.product.with_company(self.company_bis).route_ids, + self.route_profile_1.route_ids, + ) + + def test_2_force_route_profile(self): + self.product.route_profile_id = self.route_profile_1.id + self.product.with_company( + self.env.company + ).force_route_profile_id = self.route_profile_2.id + self.assertEqual( + self.product.with_company(self.env.company).route_ids, + self.route_profile_2.route_ids, + ) + # In other company, no change + self.assertEqual( + self.product.with_company(self.company_bis).route_ids, + self.route_profile_1.route_ids, + ) + # Return to route_profile_id if no force_route_profile_id + self.product.with_company(self.env.company).force_route_profile_id = False + self.assertEqual( + self.product.with_company(self.env.company).route_ids, + self.route_profile_1.route_ids, + ) + + def test_3_product_creation_with_route_profile(self): + product = self.env["product.template"].create( + { + "name": "Template 2", + "company_id": False, + "route_profile_id": self.route_profile_1.id, + } + ) + + self.assertEqual(product.route_profile_id.id, self.route_profile_1.id) diff --git a/product_route_profile/views/product_template.xml b/product_route_profile/views/product_template.xml new file mode 100644 index 000000000000..33c49e431c0c --- /dev/null +++ b/product_route_profile/views/product_template.xml @@ -0,0 +1,26 @@ + + + + + product.template.route.profile.form + product.template + + + + {'invisible': True} + + + + + + + + diff --git a/product_route_profile/views/route_profile.xml b/product_route_profile/views/route_profile.xml new file mode 100644 index 000000000000..d0f4e4164467 --- /dev/null +++ b/product_route_profile/views/route_profile.xml @@ -0,0 +1,50 @@ + + + + + route.profile.tree + route.profile + + + + + + + + + route.profile.form + route.profile + +
+ + + + +
+
+
+ + Routes Profiles + route.profile + ir.actions.act_window + tree,form + + +

+ You can define here the routes profiles that run through + your warehouses and that define the flows of your products. + A route profile can be set on each product as "Route Profile" or "Priority Route Profile" (company dependent). +

+
+
+ +
diff --git a/setup/product_route_profile/odoo/addons/product_route_profile b/setup/product_route_profile/odoo/addons/product_route_profile new file mode 120000 index 000000000000..6a327a5f291c --- /dev/null +++ b/setup/product_route_profile/odoo/addons/product_route_profile @@ -0,0 +1 @@ +../../../../product_route_profile \ No newline at end of file diff --git a/setup/product_route_profile/setup.py b/setup/product_route_profile/setup.py new file mode 100644 index 000000000000..28c57bb64031 --- /dev/null +++ b/setup/product_route_profile/setup.py @@ -0,0 +1,6 @@ +import setuptools + +setuptools.setup( + setup_requires=['setuptools-odoo'], + odoo_addon=True, +)