Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[17.0] [MIG] stock_putaway_hook: Migration to 17.0 #1842

Open
wants to merge 19 commits into
base: 17.0
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
117 changes: 117 additions & 0 deletions stock_putaway_hook/README.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
===================
Stock Putaway Hooks
===================

..
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! This file is generated by oca-gen-addon-readme !!
!! changes will be overwritten. !!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! source digest: sha256:969702101624517d66c9b9128d54940a186ec9602e0321754507913c14f1bf41
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

.. |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--workflow-lightgray.png?logo=github
:target: https://github.com/OCA/stock-logistics-workflow/tree/17.0/stock_putaway_hook
:alt: OCA/stock-logistics-workflow
.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png
:target: https://translation.odoo-community.org/projects/stock-logistics-workflow-17-0/stock-logistics-workflow-17-0-stock_putaway_hook
: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-workflow&target_branch=17.0
:alt: Try me on Runboat

|badge1| |badge2| |badge3| |badge4| |badge5|

Technical module. It adds hooks to the core putaway method
``StockLocation._get_putaway_strategy()`` allowing to plug other
strategies and makes the selector fields in the tree views dynamic
(required/readonly). See the usage section for details.

An example of implementation is the module ``stock_putaway_by_route``.

**Table of contents**

.. contents::
:local:

Usage
=====

The modules that implement a new strategy have to follow the following
steps. The module ``stock_putaway_by_route`` should be used as
reference.

Add the field to match on ``stock.putaway.rule`` in the model and in the
view. In the view, the field must have
``options="{'exclusive_selection': True}"``, which will allow this
module to dynamically build dynamic attrs, restricting the selection of
more than one field. (defining the readonly and required attrs in the
view is therefore useless).

Add the strategy key, named after the new field name, in
``StockLocation._putaway_strategies``. Example:

::

class StockLocation(models.Model):
_inherit = "stock.location"

@property
def _putaway_strategies(self):
strategies = super()._putaway_strategies
return strategies + ["route_id"]

Pass the value to match with the putaway rule field in the context, in
every method calling ``StockLocation._get_putaway_strategy``. The name
of the key in the context is:``_putaway_<KEY>``, where KEY is the name
of the new field on the putaway rule. The value can be a unit, a
recordset of any length or a list/tuple. In latter cases, the putaway
rule is selected if its field match any value in the list/recordset.

Bug Tracker
===========

Bugs are tracked on `GitHub Issues <https://github.com/OCA/stock-logistics-workflow/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-workflow/issues/new?body=module:%20stock_putaway_hook%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
-------

* Camptocamp

Contributors
------------

- Guewen Baconnier <[email protected]>
- Fernando La Chica <[email protected]>
- Denis Roussel <[email protected]>

Maintainers
-----------

This module is maintained by the OCA.

.. image:: https://odoo-community.org/logo.png
:alt: Odoo Community Association
:target: https://odoo-community.org

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

This module is part of the `OCA/stock-logistics-workflow <https://github.com/OCA/stock-logistics-workflow/tree/17.0/stock_putaway_hook>`_ project on GitHub.

You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.
1 change: 1 addition & 0 deletions stock_putaway_hook/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from . import models
13 changes: 13 additions & 0 deletions stock_putaway_hook/__manifest__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# Copyright 2020 Camptocamp
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html).

{
"name": "Stock Putaway Hooks",
"summary": "Add hooks allowing modules to add more putaway strategies",
"version": "17.0.1.0.0",
"category": "Hidden",
"website": "https://github.com/OCA/stock-logistics-workflow",
"author": "Camptocamp, Odoo Community Association (OCA)",
"license": "AGPL-3",
"depends": ["stock"],
}
27 changes: 27 additions & 0 deletions stock_putaway_hook/i18n/it.po
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * stock_putaway_hook
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 17.0\n"
"Report-Msgid-Bugs-To: \n"
"PO-Revision-Date: 2024-07-01 09:47+0000\n"
"Last-Translator: mymage <[email protected]>\n"
"Language-Team: none\n"
"Language: it\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: stock_putaway_hook
#: model:ir.model,name:stock_putaway_hook.model_stock_location
msgid "Inventory Locations"
msgstr "Ubicazioni di inventario"

#. module: stock_putaway_hook
#: model:ir.model,name:stock_putaway_hook.model_stock_putaway_rule
msgid "Putaway Rule"
msgstr "Regola stoccaggio"
24 changes: 24 additions & 0 deletions stock_putaway_hook/i18n/stock_putaway_hook.pot
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * stock_putaway_hook
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 17.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: stock_putaway_hook
#: model:ir.model,name:stock_putaway_hook.model_stock_location
msgid "Inventory Locations"
msgstr ""

#. module: stock_putaway_hook
#: model:ir.model,name:stock_putaway_hook.model_stock_putaway_rule
msgid "Putaway Rule"
msgstr ""
2 changes: 2 additions & 0 deletions stock_putaway_hook/models/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
from . import stock_location
from . import stock_putaway_rule
107 changes: 107 additions & 0 deletions stock_putaway_hook/models/stock_location.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
# Copyright 2020 Camptocamp
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html).

from odoo import models
from odoo.fields import first


class StockLocation(models.Model):
_inherit = "stock.location"

@property
def _putaway_strategies(self):
"""List of plugged put-away strategies

Each item is the key of the strategy. When applying the putaway, if no
strategy is found for the product and the category (default ones), the
method ``_alternative_putaway_strategy`` will loop over these keys.

The key of a strategy must be the name of the field added on
``stock.putaway.rule``.

For instance if the strategies are ["route_id", "foo"], the putaway will:

* search a putaway for the product (core module)
* if not found, search for the product category (core module)

If None is found, the alternatives strategies are looked for:

* if not found, search for route_id
* if not found, search for foo
"""
return []

def _get_putaway_strategy(
self, product, quantity=0, package=None, packaging=None, additional_qty=None
):
"""Extend the code method to add hooks

* Call the alternative strategies lookups
* Call a hook ``_putaway_strategy_finalizer`` after all the strategies
* This should always return a result
"""
putaway_location = super()._get_putaway_strategy(
product, quantity, package, packaging, additional_qty
)
if putaway_location == self:
putaway_location = self._alternative_putaway_strategy()
return self._putaway_strategy_finalizer(
putaway_location, product, quantity, package, packaging, additional_qty
)

def _alternative_putaway_strategy(self):
"""Find a putaway according to the ``_putaway_strategies`` keys

The methods that calls ``StockLocation._get_putaway_strategy have to
pass in the context a key with the name ``_putaway_<KEY>``, where KEY
is the name of the strategy. The value must be the value to match with
the putaway rule. The value can be a unit, a recordset of any length or
a list/tuple. In latter cases, the putaway rule is selected if its
field match any value in the list/recordset.
"""
current_location = self
putaway_location = self.browse()

strategy_values = {
field: self.env.context.get(f"_putaway_{field}")
for field in self._putaway_strategies
}

# retain only the strategies for which we have a value provided in context
available_strategies = [
strategy
for strategy in self._putaway_strategies
if strategy_values.get(strategy)
]

if not available_strategies:
return current_location

while current_location and not putaway_location:
# copy and reverse the strategies, so we pop them in their order
strategies = available_strategies[::-1]
while not putaway_location and strategies:
strategy = strategies.pop()
value = strategy_values[strategy]
# Looking for a putaway from the strategy
putaway_rules = current_location.putaway_rule_ids.filtered(
lambda x, strategy=strategy, value=value: x[strategy] in value
if isinstance(value, models.BaseModel | list | tuple)
else x[strategy] == value
)
putaway_location = first(putaway_rules).location_out_id
current_location = current_location.location_id
return putaway_location or self

def _putaway_strategy_finalizer(
self,
putaway_location,
product,
quantity=0,
package=None,
packaging=None,
additional_qty=None,
):
"""Hook for putaway called after the strategy lookup"""
# by default, do nothing
return putaway_location
85 changes: 85 additions & 0 deletions stock_putaway_hook/models/stock_putaway_rule.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
# Copyright 2020 Camptocamp
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html).

from lxml import etree

from odoo import api, models
from odoo.tools.safe_eval import safe_eval


class StockPutawayRule(models.Model):
_inherit = "stock.putaway.rule"

@api.model
def _get_view(self, view_id=None, view_type="form", **options):
arch, view = super()._get_view(view_id=view_id, view_type=view_type, **options)
if view.type == "tree":
self._get_view_set_adapt_attrs(arch)
view.arch = etree.tostring(arch, encoding="unicode")
return arch, view

def _get_view_add_exclusive_selection_attrs(self, arch):
"""Make the readonly and required attrs dynamic for putaway rules

By default, product_id and category_id fields have static domains
such as they are mutually exclusive: both fields are required,
as soon as we select a product, the category becomes readonly and
not required, if we select a category, the product becomes readonly
and not required.

If we add a third field, such as "route_id", the domains for the
readonly and required attrs should now include "route_id" as well,
and if we add a fourth field, again. We can't extend them this way
from XML, so this method dynamically generate these domains and
set it on the fields attrs.

The only requirement is to have exclusive_selection set in the options
of the field:

::

<field name="route_id"
options="{'no_create': True, 'no_open': True,
'exclusive_selection': True}"
readonly="context.get('putaway_route', False)"
force_save="1"
/>

Look in module stock_putaway_by_route (where this is tested as well).
"""
exclusive_fields = set()
nodes = arch.xpath("//field[@options]")
for field in nodes:
options = safe_eval(field.attrib.get("options", "{}"))
if options.get("exclusive_selection"):
exclusive_fields.add(field)

for field in exclusive_fields:
readonly_domain = " or ".join(
[other.attrib["name"] for other in exclusive_fields if other != field]
)
required_domain = " and ".join(
[
f"not {other.attrib['name']}"
for other in exclusive_fields
if other != field
]
)
field.set("readonly", str(readonly_domain))
field.set("required", str(required_domain))

def _get_view_add_exclusive_selection(self, arch, field_name):
nodes = arch.xpath(f"//field[@name='{field_name}']")
for field in nodes:
options = safe_eval(field.attrib.get("options", "{}"))
options["exclusive_selection"] = True
field.set("options", str(options))

def _get_view_set_adapt_attrs(self, arch):
# Add the "exclusive_selection" option on product_id and category_id
# fields from core, so they are treated by
# _view_get_add_exclusive_selection_attrs
self._get_view_add_exclusive_selection(arch, "product_id")
self._get_view_add_exclusive_selection(arch, "category_id")

self._get_view_add_exclusive_selection_attrs(arch)
3 changes: 3 additions & 0 deletions stock_putaway_hook/pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[build-system]
requires = ["whool"]
build-backend = "whool.buildapi"
3 changes: 3 additions & 0 deletions stock_putaway_hook/readme/CONTRIBUTORS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
- Guewen Baconnier \<[email protected]\>
- Fernando La Chica \<[email protected]\>
- Denis Roussel \<[email protected]\>
6 changes: 6 additions & 0 deletions stock_putaway_hook/readme/DESCRIPTION.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
Technical module. It adds hooks to the core putaway method
`StockLocation._get_putaway_strategy()` allowing to plug other
strategies and makes the selector fields in the tree views dynamic
(required/readonly). See the usage section for details.

An example of implementation is the module `stock_putaway_by_route`.
Loading
Loading