From 4cec3b8d46b995df9305d5651d358da62c505fd4 Mon Sep 17 00:00:00 2001 From: "Laurent Mignon (ACSONE)" Date: Fri, 25 Nov 2022 17:12:43 +0100 Subject: [PATCH 1/5] [ADD] stock_release_channel_auto_release: Add automatic release mode This addons define an automatic release mode on the release channels. By default release channels manage the release of the transfers in batch mode. This means that the transfers are released only when the user manually triggers the release process by clicking on the release button. When the automatic release mode is enabled, the transfers are released automatically when a new transfer is added to the channel or as soon a product becomes available. As for the batch mode, the automatic release process is only active on open channels. When is locked, the automatic release process is stopped. Once the channel is unlocked, the automatic release process is restarted and transfers into the channel are released if they are ready to be released (IOW if quantities are available for moves not yet released). --- .../addons/stock_release_channel_auto_release | 1 + .../setup.py | 6 + .../i18n/stock_release_channel.pot | 9 +- .../migrations/16.0.1.0.1/pre-migrate.py | 13 + .../models/stock_release_channel.py | 45 +- stock_release_channel/tests/common.py | 17 + .../tests/test_channel_release_batch.py | 6 +- .../views/stock_release_channel_views.xml | 21 +- stock_release_channel_auto_release/README.rst | 93 ++++ .../__init__.py | 1 + .../__manifest__.py | 20 + .../data/queue_job_data.xml | 15 + .../models/__init__.py | 2 + .../models/stock_picking.py | 49 ++ .../models/stock_release_channel.py | 73 +++ .../readme/CONTRIBUTORS.rst | 2 + .../readme/DESCRIPTION.rst | 13 + .../readme/USAGE.rst | 3 + .../static/description/icon.png | Bin 0 -> 9455 bytes .../static/description/index.html | 436 ++++++++++++++++++ .../tests/__init__.py | 1 + .../tests/test_channel_release_auto.py | 134 ++++++ .../views/stock_release_channel.xml | 31 ++ 23 files changed, 971 insertions(+), 20 deletions(-) create mode 120000 setup/stock_release_channel_auto_release/odoo/addons/stock_release_channel_auto_release create mode 100644 setup/stock_release_channel_auto_release/setup.py create mode 100644 stock_release_channel/migrations/16.0.1.0.1/pre-migrate.py create mode 100644 stock_release_channel_auto_release/README.rst create mode 100644 stock_release_channel_auto_release/__init__.py create mode 100644 stock_release_channel_auto_release/__manifest__.py create mode 100644 stock_release_channel_auto_release/data/queue_job_data.xml create mode 100644 stock_release_channel_auto_release/models/__init__.py create mode 100644 stock_release_channel_auto_release/models/stock_picking.py create mode 100644 stock_release_channel_auto_release/models/stock_release_channel.py create mode 100644 stock_release_channel_auto_release/readme/CONTRIBUTORS.rst create mode 100644 stock_release_channel_auto_release/readme/DESCRIPTION.rst create mode 100644 stock_release_channel_auto_release/readme/USAGE.rst create mode 100644 stock_release_channel_auto_release/static/description/icon.png create mode 100644 stock_release_channel_auto_release/static/description/index.html create mode 100644 stock_release_channel_auto_release/tests/__init__.py create mode 100644 stock_release_channel_auto_release/tests/test_channel_release_auto.py create mode 100644 stock_release_channel_auto_release/views/stock_release_channel.xml diff --git a/setup/stock_release_channel_auto_release/odoo/addons/stock_release_channel_auto_release b/setup/stock_release_channel_auto_release/odoo/addons/stock_release_channel_auto_release new file mode 120000 index 0000000000..9e2130c24e --- /dev/null +++ b/setup/stock_release_channel_auto_release/odoo/addons/stock_release_channel_auto_release @@ -0,0 +1 @@ +../../../../stock_release_channel_auto_release \ No newline at end of file diff --git a/setup/stock_release_channel_auto_release/setup.py b/setup/stock_release_channel_auto_release/setup.py new file mode 100644 index 0000000000..28c57bb640 --- /dev/null +++ b/setup/stock_release_channel_auto_release/setup.py @@ -0,0 +1,6 @@ +import setuptools + +setuptools.setup( + setup_requires=['setuptools-odoo'], + odoo_addon=True, +) diff --git a/stock_release_channel/i18n/stock_release_channel.pot b/stock_release_channel/i18n/stock_release_channel.pot index 95cdf00964..820eb7990b 100644 --- a/stock_release_channel/i18n/stock_release_channel.pot +++ b/stock_release_channel/i18n/stock_release_channel.pot @@ -146,7 +146,7 @@ msgid "Assigned Total" msgstr "" #. module: stock_release_channel -#: model:ir.model.fields,field_description:stock_release_channel.field_stock_release_channel__auto_release +#: model:ir.model.fields,field_description:stock_release_channel.field_stock_release_channel__batch_mode msgid "Auto Release" msgstr "" @@ -243,7 +243,7 @@ msgid "Full Progress" msgstr "" #. module: stock_release_channel -#: model:ir.model.fields.selection,name:stock_release_channel.selection__stock_release_channel__auto_release__group_commercial_partner +#: model:ir.model.fields.selection,name:stock_release_channel.selection__stock_release_channel__batch_mode__group_commercial_partner msgid "Grouped by Commercial Partner" msgstr "" @@ -355,16 +355,17 @@ msgstr "" #. module: stock_release_channel #: model:ir.model.fields.selection,name:stock_release_channel.selection__stock_release_channel__auto_release__max +#: model:ir.model.fields.selection,name:stock_release_channel.selection__stock_release_channel__batch_mode__max msgid "Max" msgstr "" #. module: stock_release_channel -#: model:ir.model.fields,field_description:stock_release_channel.field_stock_release_channel__max_auto_release +#: model:ir.model.fields,field_description:stock_release_channel.field_stock_release_channel__max_batch_mode msgid "Max Transfers to release" msgstr "" #. module: stock_release_channel -#: model:ir.model.fields,help:stock_release_channel.field_stock_release_channel__auto_release +#: model:ir.model.fields,help:stock_release_channel.field_stock_release_channel__batch_mode msgid "" "Max: release N transfers to have a configured max of X deliveries in progress.\n" "Grouped by Commercial Partner: release all transfers for acommercial partner at once." diff --git a/stock_release_channel/migrations/16.0.1.0.1/pre-migrate.py b/stock_release_channel/migrations/16.0.1.0.1/pre-migrate.py new file mode 100644 index 0000000000..e370dceb5d --- /dev/null +++ b/stock_release_channel/migrations/16.0.1.0.1/pre-migrate.py @@ -0,0 +1,13 @@ +# Copyright 2022 ACSONE SA/NV +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +import logging + +from odoo.tools import sql + +_logger = logging.getLogger(__name__) + + +def migrate(cr, version): + sql.rename_column(cr, "stock_release_channel", "auto_release", "batch_mode") + sql.rename_column(cr, "stock_release_channel", "max_auto_release", "max_batch_mode") diff --git a/stock_release_channel/models/stock_release_channel.py b/stock_release_channel/models/stock_release_channel.py index 72182657d6..6ef959d9dc 100644 --- a/stock_release_channel/models/stock_release_channel.py +++ b/stock_release_channel/models/stock_release_channel.py @@ -9,6 +9,7 @@ from pytz import timezone from odoo import _, api, exceptions, fields, models +from odoo.osv.expression import NEGATIVE_TERM_OPERATORS from odoo.tools.safe_eval import ( datetime as safe_datetime, dateutil as safe_dateutil, @@ -67,8 +68,10 @@ class StockReleaseChannel(models.Model): help="Write Python code to filter out pickings.", ) active = fields.Boolean(default=True) - - auto_release = fields.Selection( + release_mode = fields.Selection( + [("batch", "Batch (Manual)")], required=True, default="batch" + ) + batch_mode = fields.Selection( selection=[ ("max", "Max"), ("group_commercial_partner", "Grouped by Commercial Partner"), @@ -79,7 +82,7 @@ class StockReleaseChannel(models.Model): " in progress.\nGrouped by Commercial Partner: release all transfers for a" "commercial partner at once.", ) - max_auto_release = fields.Integer( + max_batch_mode = fields.Integer( string="Max Transfers to release", default=10, help="When clicking on the package icon, it releases X transfers minus " @@ -202,6 +205,7 @@ class StockReleaseChannel(models.Model): ) is_release_allowed = fields.Boolean( compute="_compute_is_release_allowed", + search="_search_is_release_allowed", help="Technical field to check if the " "action 'Release Next Batch' is allowed.", ) @@ -231,6 +235,25 @@ def _compute_is_release_allowed(self): for rec in self: rec.is_release_allowed = rec.state == "open" and not rec.release_forbidden + @api.model + def _get_is_release_allowed_domain(self): + return [("state", "=", "open"), ("release_forbidden", "=", False)] + + @api.model + def _get_is_release_not_allowed_domain(self): + return ["|", ("state", "!=", "open"), ("release_forbidden", "=", True)] + + @api.model + def _search_is_release_allowed(self, operator, value): + if "in" in operator: + raise ValueError(f"Invalid operator {operator}") + negative_op = operator in NEGATIVE_TERM_OPERATORS + is_release_allowed = (value and not negative_op) or (not value and negative_op) + domain = self._get_is_release_allowed_domain() + if not is_release_allowed: + domain = self._get_is_release_not_allowed_domain() + return domain + def _get_picking_to_unassign_domain(self): return [ ("release_channel_id", "in", self.ids), @@ -706,17 +729,23 @@ def _pickings_sort_key(picking): ) def _get_next_pickings(self): - return getattr(self, "_get_next_pickings_{}".format(self.auto_release))() + return getattr(self, "_get_next_pickings_{}".format(self.batch_mode))() + + def _get_pickings_to_release(self): + """Get the pickings to release.""" + domain = self._field_picking_domains()["count_picking_release_ready"] + domain += [("release_channel_id", "in", self.ids)] + return self.env["stock.picking"].search(domain) def _get_next_pickings_max(self): - if not self.max_auto_release: + if not self.max_batch_mode: raise exceptions.UserError(_("No Max transfers to release is configured.")) waiting_domain = self._field_picking_domains()["waiting"] waiting_domain += [("release_channel_id", "=", self.id)] released_in_progress = self.env["stock.picking"].search_count(waiting_domain) - release_limit = max(self.max_auto_release - released_in_progress, 0) + release_limit = max(self.max_batch_mode - released_in_progress, 0) if not release_limit: raise exceptions.UserError( _( @@ -740,9 +769,7 @@ def _get_next_pickings_group_commercial_partner(self): # because "date_priority" is computed and not stored. If needed, we # should evaluate making it a stored field in the module # "stock_available_to_promise_release". - next_pickings = ( - self.env["stock.picking"].search(domain).sorted(self._pickings_sort_key) - ) + next_pickings = self._get_pickings_to_release().sorted(self._pickings_sort_key) if not next_pickings: return self.env["stock.picking"].browse() first_picking = next_pickings[0] diff --git a/stock_release_channel/tests/common.py b/stock_release_channel/tests/common.py index f5a4433fd6..0220d4d073 100644 --- a/stock_release_channel/tests/common.py +++ b/stock_release_channel/tests/common.py @@ -1,6 +1,8 @@ # Copyright 2020 Camptocamp (https://www.camptocamp.com) # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html) +import logging + from odoo import fields from odoo.tests import common @@ -20,6 +22,21 @@ def setUpClass(cls): ) cls._create_base_data() + def setUp(self): + super(ReleaseChannelCase, self).setUp() + loggers = ["odoo.addons.stock_release_channel.models.stock_release_channel"] + for logger in loggers: + logging.getLogger(logger).addFilter(self) + + # pylint: disable=unused-variable + @self.addCleanup + def un_mute_logger(): + for logger_ in loggers: + logging.getLogger(logger_).removeFilter(self) + + def filter(self, record): + return 0 + @classmethod def _create_base_data(cls): cls.wh = cls.env["stock.warehouse"].create( diff --git a/stock_release_channel/tests/test_channel_release_batch.py b/stock_release_channel/tests/test_channel_release_batch.py index 4ffac94011..63d9245f9a 100644 --- a/stock_release_channel/tests/test_channel_release_batch.py +++ b/stock_release_channel/tests/test_channel_release_batch.py @@ -28,12 +28,12 @@ def test_release_auto_forbidden(self): self.channel.release_next_batch() def test_release_auto_max_next_batch_no_config(self): - self.channel.max_auto_release = 0 + self.channel.max_batch_mode = 0 with self.assertRaises(exceptions.UserError): self.channel.release_next_batch() def test_release_auto_max_next_batch(self): - self.channel.max_auto_release = 2 + self.channel.max_batch_mode = 2 self.channel.release_next_batch() # 2 have been released self.assertEqual( @@ -60,7 +60,7 @@ def test_release_auto_max_no_next_batch(self): self._assert_action_nothing_in_the_queue(action) def test_release_auto_group_commercial_partner(self): - self.channel.auto_release = "group_commercial_partner" + self.channel.batch_mode = "group_commercial_partner" self.channel.release_next_batch() self.assertFalse(self.picking.need_release) self.assertFalse(self.picking2.need_release) diff --git a/stock_release_channel/views/stock_release_channel_views.xml b/stock_release_channel/views/stock_release_channel_views.xml index 9493cb251d..aca1558cd9 100644 --- a/stock_release_channel/views/stock_release_channel_views.xml +++ b/stock_release_channel/views/stock_release_channel_views.xml @@ -68,12 +68,16 @@ + @@ -142,7 +146,15 @@ + + @@ -168,6 +180,7 @@ class="oe_background_grey o_kanban_dashboard o_emphasize_colors o_stock_release_channel" create="0" > + @@ -238,7 +251,7 @@ class="btn btn-primary" name="release_next_batch" type="object" - attrs="{'invisible': [('is_release_allowed', '=', False)]}" + attrs="{'invisible': ['|', ('is_release_allowed', '=', False), ('release_mode', '!=', 'batch')]}" > Operations > Release Channels to access to the dashboard. + +Edit a channel and select 'Automatic' into the list of available release mode. + +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 smashing it by providing a detailed and welcomed +`feedback `_. + +Do not contact contributors directly about support or help with technical issues. + +Credits +======= + +Authors +~~~~~~~ + +* ACSONE SA/NV + +Contributors +~~~~~~~~~~~~ + +* Laurent Mignon +* Jacques-Etienne Baudoux + +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/wms `_ project on GitHub. + +You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute. diff --git a/stock_release_channel_auto_release/__init__.py b/stock_release_channel_auto_release/__init__.py new file mode 100644 index 0000000000..0650744f6b --- /dev/null +++ b/stock_release_channel_auto_release/__init__.py @@ -0,0 +1 @@ +from . import models diff --git a/stock_release_channel_auto_release/__manifest__.py b/stock_release_channel_auto_release/__manifest__.py new file mode 100644 index 0000000000..f76ecc9671 --- /dev/null +++ b/stock_release_channel_auto_release/__manifest__.py @@ -0,0 +1,20 @@ +# Copyright 2022 ACSONE SA/NV +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +{ + "name": "Stock Release Channel Auto Release", + "summary": """ + Add an automatic release mode to the release channel""", + "version": "16.0.1.0.0", + "license": "AGPL-3", + "author": "ACSONE SA/NV,Odoo Community Association (OCA)", + "website": "https://github.com/OCA/wms", + "depends": [ + "stock_release_channel", + "stock_move_auto_assign_auto_release", + ], + "data": [ + "data/queue_job_data.xml", + "views/stock_release_channel.xml", + ], +} diff --git a/stock_release_channel_auto_release/data/queue_job_data.xml b/stock_release_channel_auto_release/data/queue_job_data.xml new file mode 100644 index 0000000000..248d47bb07 --- /dev/null +++ b/stock_release_channel_auto_release/data/queue_job_data.xml @@ -0,0 +1,15 @@ + + + + + stock_release_channel + + + + + + auto_release + + + + diff --git a/stock_release_channel_auto_release/models/__init__.py b/stock_release_channel_auto_release/models/__init__.py new file mode 100644 index 0000000000..ebbc07de25 --- /dev/null +++ b/stock_release_channel_auto_release/models/__init__.py @@ -0,0 +1,2 @@ +from . import stock_release_channel +from . import stock_picking diff --git a/stock_release_channel_auto_release/models/stock_picking.py b/stock_release_channel_auto_release/models/stock_picking.py new file mode 100644 index 0000000000..2aa7fa9e22 --- /dev/null +++ b/stock_release_channel_auto_release/models/stock_picking.py @@ -0,0 +1,49 @@ +# Copyright 2022 ACSONE SA/NV +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +from odoo import _, api, models +from odoo.osv.expression import AND + +from odoo.addons.queue_job.job import identity_exact + + +class StockPicking(models.Model): + + _inherit = "stock.picking" + + @api.model + def _is_auto_release_allowed_depends(self): + depends = super()._is_auto_release_allowed_depends() + depends.append("release_channel_id.is_auto_release_allowed") + return depends + + @property + def _is_auto_release_allowed_domain(self): + domain = super()._is_auto_release_allowed_domain + return AND( + [ + domain, + [("release_channel_id.is_auto_release_allowed", "=", True)], + ] + ) + + def _delay_auto_release_available_to_promise(self): + for picking in self: + picking.with_delay( + identity_key=identity_exact, + description=_( + "Auto release available to promise %(name)s", name=picking.name + ), + ).auto_release_available_to_promise() + + def auto_release_available_to_promise(self): + to_release = self.filtered("is_auto_release_allowed") + to_release.release_available_to_promise() + return to_release + + def assign_release_channel(self): + res = super().assign_release_channel() + self.filtered( + "is_auto_release_allowed" + )._delay_auto_release_available_to_promise() + return res diff --git a/stock_release_channel_auto_release/models/stock_release_channel.py b/stock_release_channel_auto_release/models/stock_release_channel.py new file mode 100644 index 0000000000..19458212b8 --- /dev/null +++ b/stock_release_channel_auto_release/models/stock_release_channel.py @@ -0,0 +1,73 @@ +# Copyright 2022 ACSONE SA/NV +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +from odoo import api, fields, models +from odoo.osv.expression import AND, NEGATIVE_TERM_OPERATORS, OR + + +class StockReleaseChannel(models.Model): + + _inherit = "stock.release.channel" + + release_mode = fields.Selection( + selection_add=[("auto", "Automatic")], + ondelete={"auto": "set default"}, + ) + + is_auto_release_allowed = fields.Boolean( + compute="_compute_is_auto_release_allowed", + search="_search_is_auto_release_allowed", + ) + + @api.depends("release_mode", "is_release_allowed") + def _compute_is_auto_release_allowed(self): + for channel in self: + channel.is_auto_release_allowed = ( + channel.release_mode == "auto" and channel.is_release_allowed + ) + + @api.model + def _get_is_auto_release_allowed_domain(self): + return AND( + [self._get_is_release_allowed_domain(), [("release_mode", "=", "auto")]] + ) + + @api.model + def _get_is_auto_release_not_allowed_domain(self): + return OR( + [ + self._get_is_release_not_allowed_domain(), + [("release_mode", "!=", "auto")], + ] + ) + + @api.model + def _search_is_auto_release_allowed(self, operator, value): + if "in" in operator: + raise ValueError(f"Invalid operator {operator}") + negative_op = operator in NEGATIVE_TERM_OPERATORS + is_auto_release_allowed = (value and not negative_op) or ( + not value and negative_op + ) + domain = self._get_is_auto_release_allowed_domain() + if not is_auto_release_allowed: + domain = self._get_is_auto_release_not_allowed_domain() + return domain + + def write(self, vals): + res = super().write(vals) + release_mode = vals.get("release_mode") + if release_mode == "auto": + self.invalidate_recordset(["is_auto_release_allowed"]) + self.auto_release_all() + return res + + def action_unlock(self): + res = super().action_unlock() + if not self.env.context.get("no_auto_release"): + self.auto_release_all() + return res + + def auto_release_all(self): + pickings = self.filtered("is_auto_release_allowed")._get_pickings_to_release() + pickings._delay_auto_release_available_to_promise() diff --git a/stock_release_channel_auto_release/readme/CONTRIBUTORS.rst b/stock_release_channel_auto_release/readme/CONTRIBUTORS.rst new file mode 100644 index 0000000000..ffd2426095 --- /dev/null +++ b/stock_release_channel_auto_release/readme/CONTRIBUTORS.rst @@ -0,0 +1,2 @@ +* Laurent Mignon +* Jacques-Etienne Baudoux diff --git a/stock_release_channel_auto_release/readme/DESCRIPTION.rst b/stock_release_channel_auto_release/readme/DESCRIPTION.rst new file mode 100644 index 0000000000..0ef1e9001a --- /dev/null +++ b/stock_release_channel_auto_release/readme/DESCRIPTION.rst @@ -0,0 +1,13 @@ +This addons define an automatic release mode on the release channels. By default +release channels manage the release of the transfers in batch mode. This means +that the transfers are released only when the user manually triggers the release +process by clicking on the release button. + +When the automatic release mode is enabled, the transfers are released automatically +when a new transfer is added to the channel or as soon a product becomes available. + +As for the batch mode, the automatic release process is only active on open channels. +When is locked, the automatic release process is stopped. Once the channel is unlocked, +the automatic release process is restarted and transfers into the channel are released +if they are ready to be released (IOW if quantities are available for moves not +yet released). diff --git a/stock_release_channel_auto_release/readme/USAGE.rst b/stock_release_channel_auto_release/readme/USAGE.rst new file mode 100644 index 0000000000..9228bb484d --- /dev/null +++ b/stock_release_channel_auto_release/readme/USAGE.rst @@ -0,0 +1,3 @@ +Use Inventory > Operations > Release Channels to access to the dashboard. + +Edit a channel and select 'Automatic' into the list of available release mode. diff --git a/stock_release_channel_auto_release/static/description/icon.png b/stock_release_channel_auto_release/static/description/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..3a0328b516c4980e8e44cdb63fd945757ddd132d GIT binary patch literal 9455 zcmW++2RxMjAAjx~&dlBk9S+%}OXg)AGE&Cb*&}d0jUxM@u(PQx^-s)697TX`ehR4?GS^qbkof1cslKgkU)h65qZ9Oc=ml_0temigYLJfnz{IDzUf>bGs4N!v3=Z3jMq&A#7%rM5eQ#dc?k~! zVpnB`o+K7|Al`Q_U;eD$B zfJtP*jH`siUq~{KE)`jP2|#TUEFGRryE2`i0**z#*^6~AI|YzIWy$Cu#CSLW3q=GA z6`?GZymC;dCPk~rBS%eCb`5OLr;RUZ;D`}um=H)BfVIq%7VhiMr)_#G0N#zrNH|__ zc+blN2UAB0=617@>_u;MPHN;P;N#YoE=)R#i$k_`UAA>WWCcEVMh~L_ zj--gtp&|K1#58Yz*AHCTMziU1Jzt_jG0I@qAOHsk$2}yTmVkBp_eHuY$A9)>P6o~I z%aQ?!(GqeQ-Y+b0I(m9pwgi(IIZZzsbMv+9w{PFtd_<_(LA~0H(xz{=FhLB@(1&qHA5EJw1>>=%q2f&^X>IQ{!GJ4e9U z&KlB)z(84HmNgm2hg2C0>WM{E(DdPr+EeU_N@57;PC2&DmGFW_9kP&%?X4}+xWi)( z;)z%wI5>D4a*5XwD)P--sPkoY(a~WBw;E~AW`Yue4kFa^LM3X`8x|}ZUeMnqr}>kH zG%WWW>3ml$Yez?i%)2pbKPI7?5o?hydokgQyZsNEr{a|mLdt;X2TX(#B1j35xPnPW z*bMSSOauW>o;*=kO8ojw91VX!qoOQb)zHJ!odWB}d+*K?#sY_jqPdg{Sm2HdYzdEx zOGVPhVRTGPtv0o}RfVP;Nd(|CB)I;*t&QO8h zFfekr30S!-LHmV_Su-W+rEwYXJ^;6&3|L$mMC8*bQptyOo9;>Qb9Q9`ySe3%V$A*9 zeKEe+b0{#KWGp$F+tga)0RtI)nhMa-K@JS}2krK~n8vJ=Ngm?R!9G<~RyuU0d?nz# z-5EK$o(!F?hmX*2Yt6+coY`6jGbb7tF#6nHA zuKk=GGJ;ZwON1iAfG$E#Y7MnZVmrY|j0eVI(DN_MNFJmyZ|;w4tf@=CCDZ#5N_0K= z$;R~bbk?}TpfDjfB&aiQ$VA}s?P}xPERJG{kxk5~R`iRS(SK5d+Xs9swCozZISbnS zk!)I0>t=A<-^z(cmSFz3=jZ23u13X><0b)P)^1T_))Kr`e!-pb#q&J*Q`p+B6la%C zuVl&0duN<;uOsB3%T9Fp8t{ED108<+W(nOZd?gDnfNBC3>M8WE61$So|P zVvqH0SNtDTcsUdzaMDpT=Ty0pDHHNL@Z0w$Y`XO z2M-_r1S+GaH%pz#Uy0*w$Vdl=X=rQXEzO}d6J^R6zjM1u&c9vYLvLp?W7w(?np9x1 zE_0JSAJCPB%i7p*Wvg)pn5T`8k3-uR?*NT|J`eS#_#54p>!p(mLDvmc-3o0mX*mp_ zN*AeS<>#^-{S%W<*mz^!X$w_2dHWpcJ6^j64qFBft-o}o_Vx80o0>}Du;>kLts;$8 zC`7q$QI(dKYG`Wa8#wl@V4jVWBRGQ@1dr-hstpQL)Tl+aqVpGpbSfN>5i&QMXfiZ> zaA?T1VGe?rpQ@;+pkrVdd{klI&jVS@I5_iz!=UMpTsa~mBga?1r}aRBm1WS;TT*s0f0lY=JBl66Upy)-k4J}lh=P^8(SXk~0xW=T9v*B|gzIhN z>qsO7dFd~mgxAy4V?&)=5ieYq?zi?ZEoj)&2o)RLy=@hbCRcfT5jigwtQGE{L*8<@Yd{zg;CsL5mvzfDY}P-wos_6PfprFVaeqNE%h zKZhLtcQld;ZD+>=nqN~>GvROfueSzJD&BE*}XfU|H&(FssBqY=hPCt`d zH?@s2>I(|;fcW&YM6#V#!kUIP8$Nkdh0A(bEVj``-AAyYgwY~jB zT|I7Bf@%;7aL7Wf4dZ%VqF$eiaC38OV6oy3Z#TER2G+fOCd9Iaoy6aLYbPTN{XRPz z;U!V|vBf%H!}52L2gH_+j;`bTcQRXB+y9onc^wLm5wi3-Be}U>k_u>2Eg$=k!(l@I zcCg+flakT2Nej3i0yn+g+}%NYb?ta;R?(g5SnwsQ49U8Wng8d|{B+lyRcEDvR3+`O{zfmrmvFrL6acVP%yG98X zo&+VBg@px@i)%o?dG(`T;n*$S5*rnyiR#=wW}}GsAcfyQpE|>a{=$Hjg=-*_K;UtD z#z-)AXwSRY?OPefw^iI+ z)AXz#PfEjlwTes|_{sB?4(O@fg0AJ^g8gP}ex9Ucf*@_^J(s_5jJV}c)s$`Myn|Kd z$6>}#q^n{4vN@+Os$m7KV+`}c%4)4pv@06af4-x5#wj!KKb%caK{A&Y#Rfs z-po?Dcb1({W=6FKIUirH&(yg=*6aLCekcKwyfK^JN5{wcA3nhO(o}SK#!CINhI`-I z1)6&n7O&ZmyFMuNwvEic#IiOAwNkR=u5it{B9n2sAJV5pNhar=j5`*N!Na;c7g!l$ z3aYBqUkqqTJ=Re-;)s!EOeij=7SQZ3Hq}ZRds%IM*PtM$wV z@;rlc*NRK7i3y5BETSKuumEN`Xu_8GP1Ri=OKQ$@I^ko8>H6)4rjiG5{VBM>B|%`&&s^)jS|-_95&yc=GqjNo{zFkw%%HHhS~e=s zD#sfS+-?*t|J!+ozP6KvtOl!R)@@-z24}`9{QaVLD^9VCSR2b`b!KC#o;Ki<+wXB6 zx3&O0LOWcg4&rv4QG0)4yb}7BFSEg~=IR5#ZRj8kg}dS7_V&^%#Do==#`u zpy6{ox?jWuR(;pg+f@mT>#HGWHAJRRDDDv~@(IDw&R>9643kK#HN`!1vBJHnC+RM&yIh8{gG2q zA%e*U3|N0XSRa~oX-3EAneep)@{h2vvd3Xvy$7og(sayr@95+e6~Xvi1tUqnIxoIH zVWo*OwYElb#uyW{Imam6f2rGbjR!Y3`#gPqkv57dB6K^wRGxc9B(t|aYDGS=m$&S!NmCtrMMaUg(c zc2qC=2Z`EEFMW-me5B)24AqF*bV5Dr-M5ig(l-WPS%CgaPzs6p_gnCIvTJ=Y<6!gT zVt@AfYCzjjsMEGi=rDQHo0yc;HqoRNnNFeWZgcm?f;cp(6CNylj36DoL(?TS7eU#+ z7&mfr#y))+CJOXQKUMZ7QIdS9@#-}7y2K1{8)cCt0~-X0O!O?Qx#E4Og+;A2SjalQ zs7r?qn0H044=sDN$SRG$arw~n=+T_DNdSrarmu)V6@|?1-ZB#hRn`uilTGPJ@fqEy zGt(f0B+^JDP&f=r{#Y_wi#AVDf-y!RIXU^0jXsFpf>=Ji*TeqSY!H~AMbJdCGLhC) zn7Rx+sXw6uYj;WRYrLd^5IZq@6JI1C^YkgnedZEYy<&4(z%Q$5yv#Boo{AH8n$a zhb4Y3PWdr269&?V%uI$xMcUrMzl=;w<_nm*qr=c3Rl@i5wWB;e-`t7D&c-mcQl7x! zZWB`UGcw=Y2=}~wzrfLx=uet<;m3~=8I~ZRuzvMQUQdr+yTV|ATf1Uuomr__nDf=X zZ3WYJtHp_ri(}SQAPjv+Y+0=fH4krOP@S&=zZ-t1jW1o@}z;xk8 z(Nz1co&El^HK^NrhVHa-_;&88vTU>_J33=%{if;BEY*J#1n59=07jrGQ#IP>@u#3A z;!q+E1Rj3ZJ+!4bq9F8PXJ@yMgZL;>&gYA0%_Kbi8?S=XGM~dnQZQ!yBSgcZhY96H zrWnU;k)qy`rX&&xlDyA%(a1Hhi5CWkmg(`Gb%m(HKi-7Z!LKGRP_B8@`7&hdDy5n= z`OIxqxiVfX@OX1p(mQu>0Ai*v_cTMiw4qRt3~NBvr9oBy0)r>w3p~V0SCm=An6@3n)>@z!|o-$HvDK z|3D2ZMJkLE5loMKl6R^ez@Zz%S$&mbeoqH5`Bb){Ei21q&VP)hWS2tjShfFtGE+$z zzCR$P#uktu+#!w)cX!lWN1XU%K-r=s{|j?)Akf@q#3b#{6cZCuJ~gCxuMXRmI$nGtnH+-h z+GEi!*X=AP<|fG`1>MBdTb?28JYc=fGvAi2I<$B(rs$;eoJCyR6_bc~p!XR@O-+sD z=eH`-ye})I5ic1eL~TDmtfJ|8`0VJ*Yr=hNCd)G1p2MMz4C3^Mj?7;!w|Ly%JqmuW zlIEW^Ft%z?*|fpXda>Jr^1noFZEwFgVV%|*XhH@acv8rdGxeEX{M$(vG{Zw+x(ei@ zmfXb22}8-?Fi`vo-YVrTH*C?a8%M=Hv9MqVH7H^J$KsD?>!SFZ;ZsvnHr_gn=7acz z#W?0eCdVhVMWN12VV^$>WlQ?f;P^{(&pYTops|btm6aj>_Uz+hqpGwB)vWp0Cf5y< zft8-je~nn?W11plq}N)4A{l8I7$!ks_x$PXW-2XaRFswX_BnF{R#6YIwMhAgd5F9X zGmwdadS6(a^fjHtXg8=l?Rc0Sm%hk6E9!5cLVloEy4eh(=FwgP`)~I^5~pBEWo+F6 zSf2ncyMurJN91#cJTy_u8Y}@%!bq1RkGC~-bV@SXRd4F{R-*V`bS+6;W5vZ(&+I<9$;-V|eNfLa5n-6% z2(}&uGRF;p92eS*sE*oR$@pexaqr*meB)VhmIg@h{uzkk$9~qh#cHhw#>O%)b@+(| z^IQgqzuj~Sk(J;swEM-3TrJAPCq9k^^^`q{IItKBRXYe}e0Tdr=Huf7da3$l4PdpwWDop%^}n;dD#K4s#DYA8SHZ z&1!riV4W4R7R#C))JH1~axJ)RYnM$$lIR%6fIVA@zV{XVyx}C+a-Dt8Y9M)^KU0+H zR4IUb2CJ{Hg>CuaXtD50jB(_Tcx=Z$^WYu2u5kubqmwp%drJ6 z?Fo40g!Qd<-l=TQxqHEOuPX0;^z7iX?Ke^a%XT<13TA^5`4Xcw6D@Ur&VT&CUe0d} z1GjOVF1^L@>O)l@?bD~$wzgf(nxX1OGD8fEV?TdJcZc2KoUe|oP1#=$$7ee|xbY)A zDZq+cuTpc(fFdj^=!;{k03C69lMQ(|>uhRfRu%+!k&YOi-3|1QKB z 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 Xviia!U7SGha1wx#SCgwmn*{w2TRX*I literal 0 HcmV?d00001 diff --git a/stock_release_channel_auto_release/static/description/index.html b/stock_release_channel_auto_release/static/description/index.html new file mode 100644 index 0000000000..ad46444dd9 --- /dev/null +++ b/stock_release_channel_auto_release/static/description/index.html @@ -0,0 +1,436 @@ + + + + + + +Stock Release Channel Auto Release + + + +
+

Stock Release Channel Auto Release

+ + +

Beta License: AGPL-3 OCA/wms Translate me on Weblate Try me on Runbot

+

This addons define an automatic release mode on the release channels. By default +release channels manage the release of the transfers in batch mode. This means +that the transfers are released only when the user manually triggers the release +process by clicking on the release button.

+

When the automatic release mode is enabled, the transfers are released automatically +when a new transfer is added to the channel or as soon a product becomes available.

+

As for the batch mode, the automatic release process is only active on open channels. +When is locked, the automatic release process is stopped. Once the channel is unlocked, +the automatic release process is restarted and transfers into the channel are released +if they are ready to be released (IOW if quantities are available for moves not +yet released).

+

Table of contents

+ +
+

Usage

+

Use Inventory > Operations > Release Channels to access to the dashboard.

+

Edit a channel and select ‘Automatic’ into the list of available release mode.

+
+
+

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 smashing it by providing a detailed and welcomed +feedback.

+

Do not contact contributors directly about support or help with technical issues.

+
+
+

Credits

+
+

Authors

+
    +
  • ACSONE SA/NV
  • +
+
+
+

Contributors

+ +
+
+

Maintainers

+

This module is maintained by the OCA.

+Odoo Community Association +

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

+

This module is part of the OCA/wms project on GitHub.

+

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

+
+
+
+ + diff --git a/stock_release_channel_auto_release/tests/__init__.py b/stock_release_channel_auto_release/tests/__init__.py new file mode 100644 index 0000000000..a42cf52214 --- /dev/null +++ b/stock_release_channel_auto_release/tests/__init__.py @@ -0,0 +1 @@ +from . import test_channel_release_auto diff --git a/stock_release_channel_auto_release/tests/test_channel_release_auto.py b/stock_release_channel_auto_release/tests/test_channel_release_auto.py new file mode 100644 index 0000000000..73e7e0009c --- /dev/null +++ b/stock_release_channel_auto_release/tests/test_channel_release_auto.py @@ -0,0 +1,134 @@ +# Copyright 2022 ACSONE SA/NV +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +from contextlib import contextmanager + +from odoo.addons.queue_job.job import identity_exact +from odoo.addons.queue_job.tests.common import trap_jobs +from odoo.addons.stock_release_channel.tests.common import ChannelReleaseCase + + +class TestChannelReleaseAuto(ChannelReleaseCase): + @classmethod + def setUpClass(cls): + super().setUpClass() + cls.channel.release_mode = "auto" + + cls._update_qty_in_location(cls.loc_bin1, cls.product1, 1000.0) + cls._update_qty_in_location(cls.loc_bin1, cls.product2, 1000.0) + + # invalidate cache for computed fields bases on qty in stock + cls.env.invalidate_all() + + @contextmanager + def assert_release_job_enqueued(self, channel): + pickings_to_release = channel._get_pickings_to_release() + self.assertTrue(pickings_to_release) + with trap_jobs() as trap: + yield + trap.assert_jobs_count(len(pickings_to_release)) + for pick in pickings_to_release: + trap.assert_enqueued_job( + pick.auto_release_available_to_promise, + args=(), + kwargs={}, + properties=dict( + identity_key=identity_exact, + ), + ) + + def test_channel_auto_release_forbidden(self): + self.assertTrue(self.channel.is_auto_release_allowed) + self.channel.release_forbidden = True + self.assertFalse(self.channel.is_auto_release_allowed) + + def test_channel_search_is_auto_release_allowed(self): + self.assertTrue(self.channel.is_auto_release_allowed) + self.assertIn( + self.channel, + self.env["stock.release.channel"].search( + [("is_auto_release_allowed", "=", True)] + ), + ) + self.assertNotIn( + self.channel, + self.env["stock.release.channel"].search( + [("is_auto_release_allowed", "=", False)] + ), + ) + self.channel.release_forbidden = True + self.assertFalse(self.channel.is_auto_release_allowed) + self.assertNotIn( + self.channel, + self.env["stock.release.channel"].search( + [("is_auto_release_allowed", "=", True)] + ), + ) + self.assertIn( + self.channel, + self.env["stock.release.channel"].search( + [("is_auto_release_allowed", "=", False)] + ), + ) + + def test_picking_auto_release_forbidden(self): + self.assertTrue(self.picking.is_auto_release_allowed) + self.channel.release_forbidden = True + self.assertFalse(self.picking.is_auto_release_allowed) + + def test_picking_search_is_auto_release_allowed(self): + self.assertTrue(self.picking.is_auto_release_allowed) + self.assertIn( + self.picking, + self.env["stock.picking"].search([("is_auto_release_allowed", "=", True)]), + ) + self.assertNotIn( + self.picking, + self.env["stock.picking"].search([("is_auto_release_allowed", "=", False)]), + ) + self.channel.release_forbidden = True + self.assertFalse(self.picking.is_auto_release_allowed) + self.assertNotIn( + self.picking, + self.env["stock.picking"].search([("is_auto_release_allowed", "=", True)]), + ) + self.assertIn( + self.picking, + self.env["stock.picking"].search([("is_auto_release_allowed", "=", False)]), + ) + + def test_channel_auto_release_all_launch_release_job(self): + with self.assert_release_job_enqueued(self.channel): + self.channel.auto_release_all() + + def test_picking_assign_release_channel_launch_release_job(self): + self.picking.release_channel_id = None + self.channel.release_mode = "batch" + with trap_jobs() as trap: + self.picking.assign_release_channel() + self.assertEqual(self.picking.release_channel_id, self.channel) + trap.assert_jobs_count(0) + self.picking.release_channel_id = None + self.channel.release_mode = "auto" + with trap_jobs() as trap: + self.picking.assign_release_channel() + self.assertEqual(self.picking.release_channel_id, self.channel) + trap.assert_jobs_count(1) + trap.assert_enqueued_job( + self.picking.auto_release_available_to_promise, + args=(), + kwargs={}, + properties=dict( + identity_key=identity_exact, + ), + ) + + def test_release_channel_change_mode_launch_release_job(self): + self.channel.release_mode = "batch" + with self.assert_release_job_enqueued(self.channel): + self.channel.release_mode = "auto" + + def test_release_channel_action_unlock_launch_release_job(self): + self.channel.action_lock() + with self.assert_release_job_enqueued(self.channel): + self.channel.action_unlock() diff --git a/stock_release_channel_auto_release/views/stock_release_channel.xml b/stock_release_channel_auto_release/views/stock_release_channel.xml new file mode 100644 index 0000000000..3a5834fd29 --- /dev/null +++ b/stock_release_channel_auto_release/views/stock_release_channel.xml @@ -0,0 +1,31 @@ + + + + + stock.release.channel.kanban (in stock_release_channel_auto_release) + stock.release.channel + + + + + + From 28292d595a3565573201c1b80e106decd1c3fbfc Mon Sep 17 00:00:00 2001 From: Thierry Ducrest Date: Thu, 2 Feb 2023 16:32:16 +0100 Subject: [PATCH 2/5] Fix stock_release_channel_auto_release backward port to 14 --- stock_available_to_promise_release/__manifest__.py | 1 + .../models/stock_picking.py | 9 +++++++++ .../migrations/{16.0.1.0.1 => 14.0.1.3.0}/pre-migrate.py | 0 .../views/stock_release_channel_views.xml | 1 + stock_release_channel_auto_release/__manifest__.py | 2 +- .../data/queue_job_data.xml | 8 ++++---- .../models/stock_release_channel.py | 2 +- .../tests/test_channel_release_auto.py | 2 +- 8 files changed, 18 insertions(+), 7 deletions(-) rename stock_release_channel/migrations/{16.0.1.0.1 => 14.0.1.3.0}/pre-migrate.py (100%) diff --git a/stock_available_to_promise_release/__manifest__.py b/stock_available_to_promise_release/__manifest__.py index f24c276e42..f941752f15 100644 --- a/stock_available_to_promise_release/__manifest__.py +++ b/stock_available_to_promise_release/__manifest__.py @@ -20,6 +20,7 @@ "wizards/stock_release_views.xml", "wizards/stock_unrelease_views.xml", ], + "demo": [], "installable": True, "license": "LGPL-3", "application": False, diff --git a/stock_available_to_promise_release/models/stock_picking.py b/stock_available_to_promise_release/models/stock_picking.py index 59f6ff6610..0b6545e859 100644 --- a/stock_available_to_promise_release/models/stock_picking.py +++ b/stock_available_to_promise_release/models/stock_picking.py @@ -38,6 +38,15 @@ class StockPicking(models.Model): help="It specifies how to release a transfer partially or all at once", ) + set_printed_at_release = fields.Boolean(compute="_compute_set_printed_at_release") + + @api.depends("move_lines") + def _compute_set_printed_at_release(self): + for picking in self: + picking.set_printed_at_release = not ( + any(picking.move_lines.mapped("rule_id.no_backorder_at_release")) + ) + @api.depends("move_lines.need_release") def _compute_need_release(self): data = self.env["stock.move"].read_group( diff --git a/stock_release_channel/migrations/16.0.1.0.1/pre-migrate.py b/stock_release_channel/migrations/14.0.1.3.0/pre-migrate.py similarity index 100% rename from stock_release_channel/migrations/16.0.1.0.1/pre-migrate.py rename to stock_release_channel/migrations/14.0.1.3.0/pre-migrate.py diff --git a/stock_release_channel/views/stock_release_channel_views.xml b/stock_release_channel/views/stock_release_channel_views.xml index aca1558cd9..5bfba064bf 100644 --- a/stock_release_channel/views/stock_release_channel_views.xml +++ b/stock_release_channel/views/stock_release_channel_views.xml @@ -181,6 +181,7 @@ create="0" > + diff --git a/stock_release_channel_auto_release/__manifest__.py b/stock_release_channel_auto_release/__manifest__.py index f76ecc9671..60868d6e9f 100644 --- a/stock_release_channel_auto_release/__manifest__.py +++ b/stock_release_channel_auto_release/__manifest__.py @@ -5,7 +5,7 @@ "name": "Stock Release Channel Auto Release", "summary": """ Add an automatic release mode to the release channel""", - "version": "16.0.1.0.0", + "version": "14.0.1.0.0", "license": "AGPL-3", "author": "ACSONE SA/NV,Odoo Community Association (OCA)", "website": "https://github.com/OCA/wms", diff --git a/stock_release_channel_auto_release/data/queue_job_data.xml b/stock_release_channel_auto_release/data/queue_job_data.xml index 248d47bb07..05550a888d 100644 --- a/stock_release_channel_auto_release/data/queue_job_data.xml +++ b/stock_release_channel_auto_release/data/queue_job_data.xml @@ -1,15 +1,15 @@ - - stock_release_channel + + stock_release_channel_auto_release - auto_release - + auto_release_available_to_promise + diff --git a/stock_release_channel_auto_release/models/stock_release_channel.py b/stock_release_channel_auto_release/models/stock_release_channel.py index 19458212b8..f7c13b576f 100644 --- a/stock_release_channel_auto_release/models/stock_release_channel.py +++ b/stock_release_channel_auto_release/models/stock_release_channel.py @@ -58,7 +58,7 @@ def write(self, vals): res = super().write(vals) release_mode = vals.get("release_mode") if release_mode == "auto": - self.invalidate_recordset(["is_auto_release_allowed"]) + self.invalidate_cache(["is_auto_release_allowed"]) self.auto_release_all() return res diff --git a/stock_release_channel_auto_release/tests/test_channel_release_auto.py b/stock_release_channel_auto_release/tests/test_channel_release_auto.py index 73e7e0009c..16c5327328 100644 --- a/stock_release_channel_auto_release/tests/test_channel_release_auto.py +++ b/stock_release_channel_auto_release/tests/test_channel_release_auto.py @@ -18,7 +18,7 @@ def setUpClass(cls): cls._update_qty_in_location(cls.loc_bin1, cls.product2, 1000.0) # invalidate cache for computed fields bases on qty in stock - cls.env.invalidate_all() + cls.env["product.product"].invalidate_cache() @contextmanager def assert_release_job_enqueued(self, channel): From 22ad3d1bb9574ee045dc4d94de1533ab7b39b9fa Mon Sep 17 00:00:00 2001 From: Akim Juillerat Date: Tue, 24 Oct 2023 16:36:21 +0200 Subject: [PATCH 3/5] Fix execution of tests post install --- stock_available_to_promise_release/tests/common.py | 3 +-- stock_available_to_promise_release/tests/test_reservation.py | 3 +++ stock_available_to_promise_release/tests/test_unrelease.py | 2 ++ .../tests/test_unrelease_2steps.py | 3 +++ .../tests/test_unrelease_3steps.py | 3 +++ 5 files changed, 12 insertions(+), 2 deletions(-) diff --git a/stock_available_to_promise_release/tests/common.py b/stock_available_to_promise_release/tests/common.py index 8cf631dba9..0d6eb1e97c 100644 --- a/stock_available_to_promise_release/tests/common.py +++ b/stock_available_to_promise_release/tests/common.py @@ -2,10 +2,9 @@ # License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html). from odoo import fields -from odoo.tests import common, tagged +from odoo.tests import common -@tagged("post_install", "-at_install") class PromiseReleaseCommonCase(common.SavepointCase): @classmethod def setUpClass(cls): diff --git a/stock_available_to_promise_release/tests/test_reservation.py b/stock_available_to_promise_release/tests/test_reservation.py index f45f2b000f..28670941b4 100644 --- a/stock_available_to_promise_release/tests/test_reservation.py +++ b/stock_available_to_promise_release/tests/test_reservation.py @@ -7,9 +7,12 @@ from dateutil.relativedelta import relativedelta from freezegun import freeze_time +from odoo.tests import tagged + from .common import PromiseReleaseCommonCase +@tagged("post_install", "-at_install") class TestAvailableToPromiseRelease(PromiseReleaseCommonCase): def test_horizon_date(self): move = self.env["stock.move"].create( diff --git a/stock_available_to_promise_release/tests/test_unrelease.py b/stock_available_to_promise_release/tests/test_unrelease.py index 32728e7ed3..40a103591f 100644 --- a/stock_available_to_promise_release/tests/test_unrelease.py +++ b/stock_available_to_promise_release/tests/test_unrelease.py @@ -5,10 +5,12 @@ from datetime import datetime from odoo.exceptions import UserError +from odoo.tests import tagged from .common import PromiseReleaseCommonCase +@tagged("post_install", "-at_install") class TestAvailableToPromiseRelease(PromiseReleaseCommonCase): @classmethod def setUpClass(cls): diff --git a/stock_available_to_promise_release/tests/test_unrelease_2steps.py b/stock_available_to_promise_release/tests/test_unrelease_2steps.py index 8019e5b9a2..06fb75028c 100644 --- a/stock_available_to_promise_release/tests/test_unrelease_2steps.py +++ b/stock_available_to_promise_release/tests/test_unrelease_2steps.py @@ -3,9 +3,12 @@ from datetime import datetime +from odoo.tests import tagged + from .common import PromiseReleaseCommonCase +@tagged("post_install", "-at_install") class TestAvailableToPromiseRelease(PromiseReleaseCommonCase): @classmethod def setUpClass(cls): diff --git a/stock_available_to_promise_release/tests/test_unrelease_3steps.py b/stock_available_to_promise_release/tests/test_unrelease_3steps.py index d9b7c187dd..5466e45c7d 100644 --- a/stock_available_to_promise_release/tests/test_unrelease_3steps.py +++ b/stock_available_to_promise_release/tests/test_unrelease_3steps.py @@ -3,9 +3,12 @@ from datetime import datetime +from odoo.tests import tagged + from .common import PromiseReleaseCommonCase +@tagged("post_install", "-at_install") class TestAvailableToPromiseRelease3steps(PromiseReleaseCommonCase): @classmethod def setUpClass(cls): From b27081d0044abcb7dec680634d73723804d280e2 Mon Sep 17 00:00:00 2001 From: Akim Juillerat Date: Wed, 8 Nov 2023 13:31:01 +0100 Subject: [PATCH 4/5] fixup! Fix execution of tests post install --- stock_available_to_promise_release/tests/common.py | 4 ++++ stock_available_to_promise_release/tests/test_reservation.py | 4 ++++ stock_available_to_promise_release/tests/test_unrelease.py | 4 ++++ .../tests/test_unrelease_2steps.py | 4 ++++ .../tests/test_unrelease_3steps.py | 4 ++++ 5 files changed, 20 insertions(+) diff --git a/stock_available_to_promise_release/tests/common.py b/stock_available_to_promise_release/tests/common.py index 0d6eb1e97c..85e020b88a 100644 --- a/stock_available_to_promise_release/tests/common.py +++ b/stock_available_to_promise_release/tests/common.py @@ -6,6 +6,10 @@ class PromiseReleaseCommonCase(common.SavepointCase): + + at_install = False + post_install = True + @classmethod def setUpClass(cls): super().setUpClass() diff --git a/stock_available_to_promise_release/tests/test_reservation.py b/stock_available_to_promise_release/tests/test_reservation.py index 28670941b4..22a4de23e1 100644 --- a/stock_available_to_promise_release/tests/test_reservation.py +++ b/stock_available_to_promise_release/tests/test_reservation.py @@ -14,6 +14,10 @@ @tagged("post_install", "-at_install") class TestAvailableToPromiseRelease(PromiseReleaseCommonCase): + + at_install = False + post_install = True + def test_horizon_date(self): move = self.env["stock.move"].create( { diff --git a/stock_available_to_promise_release/tests/test_unrelease.py b/stock_available_to_promise_release/tests/test_unrelease.py index 40a103591f..cc94677910 100644 --- a/stock_available_to_promise_release/tests/test_unrelease.py +++ b/stock_available_to_promise_release/tests/test_unrelease.py @@ -12,6 +12,10 @@ @tagged("post_install", "-at_install") class TestAvailableToPromiseRelease(PromiseReleaseCommonCase): + + at_install = False + post_install = True + @classmethod def setUpClass(cls): super().setUpClass() diff --git a/stock_available_to_promise_release/tests/test_unrelease_2steps.py b/stock_available_to_promise_release/tests/test_unrelease_2steps.py index 06fb75028c..b2163222e3 100644 --- a/stock_available_to_promise_release/tests/test_unrelease_2steps.py +++ b/stock_available_to_promise_release/tests/test_unrelease_2steps.py @@ -10,6 +10,10 @@ @tagged("post_install", "-at_install") class TestAvailableToPromiseRelease(PromiseReleaseCommonCase): + + at_install = False + post_install = True + @classmethod def setUpClass(cls): super().setUpClass() diff --git a/stock_available_to_promise_release/tests/test_unrelease_3steps.py b/stock_available_to_promise_release/tests/test_unrelease_3steps.py index 5466e45c7d..ebd1a009cf 100644 --- a/stock_available_to_promise_release/tests/test_unrelease_3steps.py +++ b/stock_available_to_promise_release/tests/test_unrelease_3steps.py @@ -10,6 +10,10 @@ @tagged("post_install", "-at_install") class TestAvailableToPromiseRelease3steps(PromiseReleaseCommonCase): + + at_install = False + post_install = True + @classmethod def setUpClass(cls): super().setUpClass() From 184f64a964034c6184501cea6af37b64e7b64571 Mon Sep 17 00:00:00 2001 From: Thierry Ducrest Date: Wed, 22 May 2024 10:22:29 +0200 Subject: [PATCH 5/5] fixup! [ADD] stock_release_channel_auto_release: Add automatic release mode --- stock_release_channel/models/stock_release_channel.py | 2 +- stock_release_channel/tests/test_channel_release_batch.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/stock_release_channel/models/stock_release_channel.py b/stock_release_channel/models/stock_release_channel.py index 6ef959d9dc..d543d73d7e 100644 --- a/stock_release_channel/models/stock_release_channel.py +++ b/stock_release_channel/models/stock_release_channel.py @@ -733,7 +733,7 @@ def _get_next_pickings(self): def _get_pickings_to_release(self): """Get the pickings to release.""" - domain = self._field_picking_domains()["count_picking_release_ready"] + domain = self._field_picking_domains()["release_ready"] domain += [("release_channel_id", "in", self.ids)] return self.env["stock.picking"].search(domain) diff --git a/stock_release_channel/tests/test_channel_release_batch.py b/stock_release_channel/tests/test_channel_release_batch.py index 63d9245f9a..c4ba19593e 100644 --- a/stock_release_channel/tests/test_channel_release_batch.py +++ b/stock_release_channel/tests/test_channel_release_batch.py @@ -68,7 +68,7 @@ def test_release_auto_group_commercial_partner(self): self.assertTrue(all(p.need_release) for p in other_pickings) def test_release_auto_group_commercial_partner_no_next_batch(self): - self.channel.auto_release = "group_commercial_partner" + self.channel.batch_mode = "group_commercial_partner" pickings = self.channel.picking_ids.filtered(lambda p: p.release_ready) for _i in range(0, len(pickings.partner_id.commercial_partner_id)): action = self.channel.release_next_batch()