Merge PR #2097 into 17.0

Signed-off-by jbaudoux
This commit is contained in:
OCA-git-bot
2024-10-08 12:28:21 +00:00
23 changed files with 3088 additions and 0 deletions

View File

@@ -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/17.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-17-0/stock-logistics-warehouse-17-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=17.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 <https://github.com/OCA/stock-logistics-warehouse/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 <https://github.com/OCA/stock-logistics-warehouse/issues/new?body=module:%20product_route_profile%0Aversion:%2017.0%0A%0A**Steps%20to%20reproduce**%0A-%20...%0A%0A**Current%20behavior**%0A%0A**Expected%20behavior**>`_.
Do not contact contributors directly about support or help with technical issues.
Credits
=======
Authors
-------
* Akretion
Contributors
------------
- Kévin Roche <kevin.roche@akretion.com>
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 <https://odoo-community.org/page/maintainer-role>`__:
|maintainer-Kev-Roche|
This module is part of the `OCA/stock-logistics-warehouse <https://github.com/OCA/stock-logistics-warehouse/tree/17.0/product_route_profile>`_ project on GitHub.
You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.

View File

@@ -0,0 +1,2 @@
from . import models
from .hooks import post_init_hook

View File

@@ -0,0 +1,25 @@
# Copyright 2022 Akretion (https://www.akretion.com).
# @author Kévin Roche <kevin.roche@akretion.com>
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
{
"name": "Product Route Profile",
"summary": "Add Route profile concept on product",
"version": "17.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",
}

View File

@@ -0,0 +1,38 @@
# Copyright (C) 2022 Akretion (<http://www.akretion.com>).
# @author Kévin Roche <kevin.roche@akretion.com>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from collections import defaultdict
def post_init_hook(env):
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.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
query = """
SELECT product_id, array_agg(route_id)
FROM stock_route_product group by product_id;
"""
env.cr.execute(query)
results = env.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})

View File

@@ -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)."

View File

@@ -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 <c5e2fd43-d292-4c90-9d1f-74ff3436329a@anonaddy.me>\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)."

View File

@@ -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 ""

View File

@@ -0,0 +1,2 @@
from . import route_profile
from . import product_template

View File

@@ -0,0 +1,87 @@
# Copyright 2022 Akretion (https://www.akretion.com).
# @author Kévin Roche <kevin.roche@akretion.com>
# 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_create_multi
def create(self, vals_list):
vals_with_profile = []
vals_without_profile = []
for vals in vals_list:
route_profile_id = vals.get("route_profile_id")
if route_profile_id:
vals = vals.copy()
route_profile = self.env["route.profile"].browse(route_profile_id)
vals["route_ids"] = [(6, 0, route_profile.route_ids.ids)]
vals_with_profile.append(vals)
else:
vals_without_profile.append(vals)
res = self.env["product.template"]
if vals_without_profile:
res += super().create(vals_without_profile)
if vals_with_profile:
res += super(
ProductTemplate, self.with_context(skip_inverse_route_ids=True)
).create(vals_with_profile)
return res

View File

@@ -0,0 +1,22 @@
# Copyright 2022 Akretion (https://www.akretion.com).
# @author Kévin Roche <kevin.roche@akretion.com>
# 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,
)
route_ids = fields.Many2many(
comodel_name="stock.route",
string="Routes",
domain=[("product_selectable", "=", True)],
)

View File

@@ -0,0 +1,3 @@
[build-system]
requires = ["whool"]
build-backend = "whool.buildapi"

View File

@@ -0,0 +1 @@
- Kévin Roche \<<kevin.roche@akretion.com>\>

View File

@@ -0,0 +1,3 @@
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.

View File

@@ -0,0 +1 @@
Tests of this module are running separately than the other tests.

View File

@@ -0,0 +1,7 @@
**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.

View File

@@ -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
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
2 access_route_profile_manager access_route_profile_manager model_route_profile stock.group_stock_manager 1 1 1 1
3 access_route_profile_user access_route_profile_user model_route_profile stock.group_stock_user 1 0 0 0

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.3 KiB

File diff suppressed because it is too large Load Diff

After

Width:  |  Height:  |  Size: 369 KiB

View File

@@ -0,0 +1,439 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<meta name="generator" content="Docutils: https://docutils.sourceforge.io/" />
<title>Product Route Profile</title>
<style type="text/css">
/*
:Author: David Goodger (goodger@python.org)
:Id: $Id: html4css1.css 8954 2022-01-20 10:10:25Z milde $
:Copyright: This stylesheet has been placed in the public domain.
Default cascading style sheet for the HTML output of Docutils.
See https://docutils.sourceforge.io/docs/howto/html-stylesheets.html for how to
customize this style sheet.
*/
/* used to remove borders from tables and images */
.borderless, table.borderless td, table.borderless th {
border: 0 }
table.borderless td, table.borderless th {
/* Override padding for "table.docutils td" with "! important".
The right padding separates the table cells. */
padding: 0 0.5em 0 0 ! important }
.first {
/* Override more specific margin styles with "! important". */
margin-top: 0 ! important }
.last, .with-subtitle {
margin-bottom: 0 ! important }
.hidden {
display: none }
.subscript {
vertical-align: sub;
font-size: smaller }
.superscript {
vertical-align: super;
font-size: smaller }
a.toc-backref {
text-decoration: none ;
color: black }
blockquote.epigraph {
margin: 2em 5em ; }
dl.docutils dd {
margin-bottom: 0.5em }
object[type="image/svg+xml"], object[type="application/x-shockwave-flash"] {
overflow: hidden;
}
/* Uncomment (and remove this text!) to get bold-faced definition list terms
dl.docutils dt {
font-weight: bold }
*/
div.abstract {
margin: 2em 5em }
div.abstract p.topic-title {
font-weight: bold ;
text-align: center }
div.admonition, div.attention, div.caution, div.danger, div.error,
div.hint, div.important, div.note, div.tip, div.warning {
margin: 2em ;
border: medium outset ;
padding: 1em }
div.admonition p.admonition-title, div.hint p.admonition-title,
div.important p.admonition-title, div.note p.admonition-title,
div.tip p.admonition-title {
font-weight: bold ;
font-family: sans-serif }
div.attention p.admonition-title, div.caution p.admonition-title,
div.danger p.admonition-title, div.error p.admonition-title,
div.warning p.admonition-title, .code .error {
color: red ;
font-weight: bold ;
font-family: sans-serif }
/* Uncomment (and remove this text!) to get reduced vertical space in
compound paragraphs.
div.compound .compound-first, div.compound .compound-middle {
margin-bottom: 0.5em }
div.compound .compound-last, div.compound .compound-middle {
margin-top: 0.5em }
*/
div.dedication {
margin: 2em 5em ;
text-align: center ;
font-style: italic }
div.dedication p.topic-title {
font-weight: bold ;
font-style: normal }
div.figure {
margin-left: 2em ;
margin-right: 2em }
div.footer, div.header {
clear: both;
font-size: smaller }
div.line-block {
display: block ;
margin-top: 1em ;
margin-bottom: 1em }
div.line-block div.line-block {
margin-top: 0 ;
margin-bottom: 0 ;
margin-left: 1.5em }
div.sidebar {
margin: 0 0 0.5em 1em ;
border: medium outset ;
padding: 1em ;
background-color: #ffffee ;
width: 40% ;
float: right ;
clear: right }
div.sidebar p.rubric {
font-family: sans-serif ;
font-size: medium }
div.system-messages {
margin: 5em }
div.system-messages h1 {
color: red }
div.system-message {
border: medium outset ;
padding: 1em }
div.system-message p.system-message-title {
color: red ;
font-weight: bold }
div.topic {
margin: 2em }
h1.section-subtitle, h2.section-subtitle, h3.section-subtitle,
h4.section-subtitle, h5.section-subtitle, h6.section-subtitle {
margin-top: 0.4em }
h1.title {
text-align: center }
h2.subtitle {
text-align: center }
hr.docutils {
width: 75% }
img.align-left, .figure.align-left, object.align-left, table.align-left {
clear: left ;
float: left ;
margin-right: 1em }
img.align-right, .figure.align-right, object.align-right, table.align-right {
clear: right ;
float: right ;
margin-left: 1em }
img.align-center, .figure.align-center, object.align-center {
display: block;
margin-left: auto;
margin-right: auto;
}
table.align-center {
margin-left: auto;
margin-right: auto;
}
.align-left {
text-align: left }
.align-center {
clear: both ;
text-align: center }
.align-right {
text-align: right }
/* reset inner alignment in figures */
div.align-right {
text-align: inherit }
/* div.align-center * { */
/* text-align: left } */
.align-top {
vertical-align: top }
.align-middle {
vertical-align: middle }
.align-bottom {
vertical-align: bottom }
ol.simple, ul.simple {
margin-bottom: 1em }
ol.arabic {
list-style: decimal }
ol.loweralpha {
list-style: lower-alpha }
ol.upperalpha {
list-style: upper-alpha }
ol.lowerroman {
list-style: lower-roman }
ol.upperroman {
list-style: upper-roman }
p.attribution {
text-align: right ;
margin-left: 50% }
p.caption {
font-style: italic }
p.credits {
font-style: italic ;
font-size: smaller }
p.label {
white-space: nowrap }
p.rubric {
font-weight: bold ;
font-size: larger ;
color: maroon ;
text-align: center }
p.sidebar-title {
font-family: sans-serif ;
font-weight: bold ;
font-size: larger }
p.sidebar-subtitle {
font-family: sans-serif ;
font-weight: bold }
p.topic-title {
font-weight: bold }
pre.address {
margin-bottom: 0 ;
margin-top: 0 ;
font: inherit }
pre.literal-block, pre.doctest-block, pre.math, pre.code {
margin-left: 2em ;
margin-right: 2em }
pre.code .ln { color: grey; } /* line numbers */
pre.code, code { background-color: #eeeeee }
pre.code .comment, code .comment { color: #5C6576 }
pre.code .keyword, code .keyword { color: #3B0D06; font-weight: bold }
pre.code .literal.string, code .literal.string { color: #0C5404 }
pre.code .name.builtin, code .name.builtin { color: #352B84 }
pre.code .deleted, code .deleted { background-color: #DEB0A1}
pre.code .inserted, code .inserted { background-color: #A3D289}
span.classifier {
font-family: sans-serif ;
font-style: oblique }
span.classifier-delimiter {
font-family: sans-serif ;
font-weight: bold }
span.interpreted {
font-family: sans-serif }
span.option {
white-space: nowrap }
span.pre {
white-space: pre }
span.problematic {
color: red }
span.section-subtitle {
/* font-size relative to parent (h1..h6 element) */
font-size: 80% }
table.citation {
border-left: solid 1px gray;
margin-left: 1px }
table.docinfo {
margin: 2em 4em }
table.docutils {
margin-top: 0.5em ;
margin-bottom: 0.5em }
table.footnote {
border-left: solid 1px black;
margin-left: 1px }
table.docutils td, table.docutils th,
table.docinfo td, table.docinfo th {
padding-left: 0.5em ;
padding-right: 0.5em ;
vertical-align: top }
table.docutils th.field-name, table.docinfo th.docinfo-name {
font-weight: bold ;
text-align: left ;
white-space: nowrap ;
padding-left: 0 }
/* "booktabs" style (no vertical lines) */
table.docutils.booktabs {
border: 0px;
border-top: 2px solid;
border-bottom: 2px solid;
border-collapse: collapse;
}
table.docutils.booktabs * {
border: 0px;
}
table.docutils.booktabs th {
border-bottom: thin solid;
text-align: left;
}
h1 tt.docutils, h2 tt.docutils, h3 tt.docutils,
h4 tt.docutils, h5 tt.docutils, h6 tt.docutils {
font-size: 100% }
ul.auto-toc {
list-style-type: none }
</style>
</head>
<body>
<div class="document" id="product-route-profile">
<h1 class="title">Product Route Profile</h1>
<!-- !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! This file is generated by oca-gen-addon-readme !!
!! changes will be overwritten. !!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! source digest: sha256:5e5c1b0e163469f9f69b63c044b7d995e7c4ac4c0763450fd8075f60d84ecdc5
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -->
<p><a class="reference external image-reference" href="https://odoo-community.org/page/development-status"><img alt="Beta" src="https://img.shields.io/badge/maturity-Beta-yellow.png" /></a> <a class="reference external image-reference" href="http://www.gnu.org/licenses/agpl-3.0-standalone.html"><img alt="License: AGPL-3" src="https://img.shields.io/badge/licence-AGPL--3-blue.png" /></a> <a class="reference external image-reference" href="https://github.com/OCA/stock-logistics-warehouse/tree/17.0/product_route_profile"><img alt="OCA/stock-logistics-warehouse" src="https://img.shields.io/badge/github-OCA%2Fstock--logistics--warehouse-lightgray.png?logo=github" /></a> <a class="reference external image-reference" href="https://translation.odoo-community.org/projects/stock-logistics-warehouse-17-0/stock-logistics-warehouse-17-0-product_route_profile"><img alt="Translate me on Weblate" src="https://img.shields.io/badge/weblate-Translate%20me-F47D42.png" /></a> <a class="reference external image-reference" href="https://runboat.odoo-community.org/builds?repo=OCA/stock-logistics-warehouse&amp;target_branch=17.0"><img alt="Try me on Runboat" src="https://img.shields.io/badge/runboat-Try%20me-875A7B.png" /></a></p>
<p>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.</p>
<p><strong>Table of contents</strong></p>
<div class="contents local topic" id="contents">
<ul class="simple">
<li><a class="reference internal" href="#usage" id="toc-entry-1">Usage</a></li>
<li><a class="reference internal" href="#known-issues-roadmap" id="toc-entry-2">Known issues / Roadmap</a></li>
<li><a class="reference internal" href="#bug-tracker" id="toc-entry-3">Bug Tracker</a></li>
<li><a class="reference internal" href="#credits" id="toc-entry-4">Credits</a><ul>
<li><a class="reference internal" href="#authors" id="toc-entry-5">Authors</a></li>
<li><a class="reference internal" href="#contributors" id="toc-entry-6">Contributors</a></li>
<li><a class="reference internal" href="#maintainers" id="toc-entry-7">Maintainers</a></li>
</ul>
</li>
</ul>
</div>
<div class="section" id="usage">
<h1><a class="toc-backref" href="#toc-entry-1">Usage</a></h1>
<p><strong>Route profile</strong> In Inventory &gt; Configuration &gt; Settings &gt; Routes
Profiles - Create some Route profile depending on your needs</p>
<p><strong>On product</strong> On each template product, in inventory page, we can
select: - <strong>Route Profile</strong>: a default profile, common to all companies
- <strong>Priority Route Profile</strong>: a profile specific to each company and
priority if existing.</p>
</div>
<div class="section" id="known-issues-roadmap">
<h1><a class="toc-backref" href="#toc-entry-2">Known issues / Roadmap</a></h1>
<p>Tests of this module are running separately than the other tests.</p>
</div>
<div class="section" id="bug-tracker">
<h1><a class="toc-backref" href="#toc-entry-3">Bug Tracker</a></h1>
<p>Bugs are tracked on <a class="reference external" href="https://github.com/OCA/stock-logistics-warehouse/issues">GitHub Issues</a>.
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
<a class="reference external" href="https://github.com/OCA/stock-logistics-warehouse/issues/new?body=module:%20product_route_profile%0Aversion:%2017.0%0A%0A**Steps%20to%20reproduce**%0A-%20...%0A%0A**Current%20behavior**%0A%0A**Expected%20behavior**">feedback</a>.</p>
<p>Do not contact contributors directly about support or help with technical issues.</p>
</div>
<div class="section" id="credits">
<h1><a class="toc-backref" href="#toc-entry-4">Credits</a></h1>
<div class="section" id="authors">
<h2><a class="toc-backref" href="#toc-entry-5">Authors</a></h2>
<ul class="simple">
<li>Akretion</li>
</ul>
</div>
<div class="section" id="contributors">
<h2><a class="toc-backref" href="#toc-entry-6">Contributors</a></h2>
<ul class="simple">
<li>Kévin Roche &lt;<a class="reference external" href="mailto:kevin.roche&#64;akretion.com">kevin.roche&#64;akretion.com</a>&gt;</li>
</ul>
</div>
<div class="section" id="maintainers">
<h2><a class="toc-backref" href="#toc-entry-7">Maintainers</a></h2>
<p>This module is maintained by the OCA.</p>
<a class="reference external image-reference" href="https://odoo-community.org"><img alt="Odoo Community Association" src="https://odoo-community.org/logo.png" /></a>
<p>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.</p>
<p>Current <a class="reference external" href="https://odoo-community.org/page/maintainer-role">maintainer</a>:</p>
<p><a class="reference external image-reference" href="https://github.com/Kev-Roche"><img alt="Kev-Roche" src="https://github.com/Kev-Roche.png?size=40px" /></a></p>
<p>This module is part of the <a class="reference external" href="https://github.com/OCA/stock-logistics-warehouse/tree/17.0/product_route_profile">OCA/stock-logistics-warehouse</a> project on GitHub.</p>
<p>You are welcome to contribute. To learn how please visit <a class="reference external" href="https://odoo-community.org/page/Contribute">https://odoo-community.org/page/Contribute</a>.</p>
</div>
</div>
</div>
</body>
</html>

View File

@@ -0,0 +1 @@
from . import test_product_route_profile

View File

@@ -0,0 +1,84 @@
# Copyright 2022 Akretion (https://www.akretion.com).
# @author Kévin Roche <kevin.roche@akretion.com>
# 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().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.route_profile_id, self.route_profile_1)
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)

View File

@@ -0,0 +1,32 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!-- Copyright (C) 2022 Akretion (<http://www.akretion.com>).
@author Kévin Roche <kevin.roche@akretion.com>
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). -->
<odoo>
<record id="view_template_route_profile_form" model="ir.ui.view">
<field name="name">product.template.route.profile.form</field>
<field name="model">product.template</field>
<field name="inherit_id" ref="product.product_template_form_view" />
<field name="arch" type="xml">
<xpath expr="//field[@name='route_ids']/parent::div" position="attributes">
<attribute name="invisible">True</attribute>
</xpath>
<xpath expr="//field[@name='route_ids']" position="attributes">
<attribute name="readonly">1</attribute>
</xpath>
<xpath expr="//label[@for='route_ids']" position="attributes">
<attribute name="invisible">1</attribute>
</xpath>
<xpath expr="//group[@name='operations']" position="inside">
<field
name="route_profile_id"
invisible="type not in ['product', 'consu']"
/>
<field
name="force_route_profile_id"
invisible="type not in ['product', 'consu']"
/>
</xpath>
</field>
</record>
</odoo>

View File

@@ -0,0 +1,50 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!-- Copyright (C) 2022 Akretion (<http://www.akretion.com>).
@author Kévin Roche <kevin.roche@akretion.com>
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). -->
<odoo>
<record id="route_profile_tree" model="ir.ui.view">
<field name="name">route.profile.tree</field>
<field name="model">route.profile</field>
<field name="arch" type="xml">
<tree>
<field name="name" />
<field name="route_ids" widget="many2many_tags" />
</tree>
</field>
</record>
<record id="route_profile_form" model="ir.ui.view">
<field name="name">route.profile.form</field>
<field name="model">route.profile</field>
<field name="arch" type="xml">
<form>
<group string="Routes Profiles">
<field name="name" />
<field name="route_ids" widget="many2many_checkboxes" />
</group>
</form>
</field>
</record>
<record id="action_route_profile_form" model="ir.actions.act_window">
<field name="name">Routes Profiles</field>
<field name="res_model">route.profile</field>
<field name="type">ir.actions.act_window</field>
<field name="view_mode">tree,form</field>
<field name="view_id" ref="route_profile_tree" />
<field name="help" type="html">
<p>
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).
</p>
</field>
</record>
<menuitem
id="menu_route_profile_config"
action="action_route_profile_form"
name="Routes Profiles"
parent="stock.menu_warehouse_config"
sequence="4"
groups="stock.group_adv_location"
/>
</odoo>