diff --git a/stock_move_auto_assign_auto_release/data/queue_job_function_data.xml b/stock_move_auto_assign_auto_release/data/queue_job_function_data.xml
index 1b01bdfaee9d..64663429ca50 100644
--- a/stock_move_auto_assign_auto_release/data/queue_job_function_data.xml
+++ b/stock_move_auto_assign_auto_release/data/queue_job_function_data.xml
@@ -1,12 +1,17 @@
- moves_auto_release
+ pickings_auto_release
+
+
+
+
+
+ auto_release_available_to_promise
-
diff --git a/stock_move_auto_assign_auto_release/models/product_product.py b/stock_move_auto_assign_auto_release/models/product_product.py
index d099250cdbbe..18386daf1882 100644
--- a/stock_move_auto_assign_auto_release/models/product_product.py
+++ b/stock_move_auto_assign_auto_release/models/product_product.py
@@ -1,4 +1,5 @@
# Copyright 2022 ACSONE SA/NV
+# Copyright 2024 Michael Tietz (MT Software)
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
from odoo import models
@@ -14,16 +15,15 @@ def _moves_auto_release_domain(self):
("is_auto_release_allowed", "=", True),
]
- def moves_auto_release(self):
- """Job trying to auto release moves based on product
+ def pickings_auto_release(self):
+ """Job trying to auto release pickings based on product
- It searches all* the moves auto releasable and trigger the release
- available to promise process.
+ It searches all* the moves auto releasable
+ and triggers a delayed release available to promise for their pickings.
"""
self.ensure_one()
moves = self.env["stock.move"].search(self._moves_auto_release_domain())
pickings = moves.picking_id
if not pickings:
return
- self._lock_pickings_or_retry(pickings)
- moves.release_available_to_promise()
+ pickings._delay_auto_release_available_to_promise()
diff --git a/stock_move_auto_assign_auto_release/models/stock_move.py b/stock_move_auto_assign_auto_release/models/stock_move.py
index 33836980a6fe..38415202bb23 100644
--- a/stock_move_auto_assign_auto_release/models/stock_move.py
+++ b/stock_move_auto_assign_auto_release/models/stock_move.py
@@ -1,4 +1,5 @@
# Copyright 2022 ACSONE SA/NV
+# Copyright 2024 Michael Tietz (MT Software)
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
from odoo import _, api, fields, models
@@ -67,6 +68,6 @@ def _enqueue_auto_assign(self, product, locations, **job_options):
)
job_options.setdefault("identity_key", identity_exact)
delayable = product.delayable(**job_options)
- release_job = delayable.moves_auto_release()
+ release_job = delayable.pickings_auto_release()
job.on_done(release_job)
return job
diff --git a/stock_move_auto_assign_auto_release/models/stock_picking.py b/stock_move_auto_assign_auto_release/models/stock_picking.py
index 8b40daf6f1c4..1d63d5bcac5b 100644
--- a/stock_move_auto_assign_auto_release/models/stock_picking.py
+++ b/stock_move_auto_assign_auto_release/models/stock_picking.py
@@ -1,9 +1,12 @@
# Copyright 2022 ACSONE SA/NV
+# Copyright 2024 Michael Tietz (MT Software)
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
-from odoo import api, fields, models
+from odoo import _, api, fields, models
from odoo.osv.expression import NEGATIVE_TERM_OPERATORS
+from odoo.addons.queue_job.job import identity_exact
+
class StockPicking(models.Model):
@@ -47,3 +50,17 @@ def _search_is_auto_release_allowed(self, operator, value):
if not is_auto_release_allowed:
domain = [("id", "not in", self.search(domain).ids)]
return domain
+
+ 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
diff --git a/stock_move_auto_assign_auto_release/readme/CONTRIBUTORS.rst b/stock_move_auto_assign_auto_release/readme/CONTRIBUTORS.rst
index 172b2d223cef..fd6329eb2ea9 100644
--- a/stock_move_auto_assign_auto_release/readme/CONTRIBUTORS.rst
+++ b/stock_move_auto_assign_auto_release/readme/CONTRIBUTORS.rst
@@ -1 +1,2 @@
* Laurent Mignon
+* Michael Tietz (MT Software)
diff --git a/stock_move_auto_assign_auto_release/tests/test_assign_auto_release.py b/stock_move_auto_assign_auto_release/tests/test_assign_auto_release.py
index 614af12ffd1a..46db1231f7db 100644
--- a/stock_move_auto_assign_auto_release/tests/test_assign_auto_release.py
+++ b/stock_move_auto_assign_auto_release/tests/test_assign_auto_release.py
@@ -1,4 +1,5 @@
# Copyright 2022 ACSONE SA/NV
+# Copyright 2024 Michael Tietz (MT Software)
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
from datetime import datetime
@@ -82,7 +83,7 @@ def _receive_product(self, product=None, qty=None):
move.move_line_ids.location_dest_id = self.loc_bin1.id
move._action_done()
- def test_product_moves_auto_release(self):
+ def test_product_pickings_auto_release(self):
"""Test job method, update qty available and launch auto release on
the product"""
self.assertEqual(1, len(self.unreleased_move))
@@ -90,7 +91,13 @@ def test_product_moves_auto_release(self):
self.assertEqual(5, self.picking.move_lines.product_qty)
# put stock in Stock/Shelf 1, the move has a source location in Stock
self._update_qty_in_location(self.loc_bin1, self.product1, 100)
- self.product1.moves_auto_release()
+ with trap_jobs() as trap:
+ self.product1.pickings_auto_release()
+ job = self._get_job_for_method(
+ trap.enqueued_jobs,
+ self.unreleased_move.picking_id.auto_release_available_to_promise,
+ )
+ job.perform()
self.assertFalse(self.unreleased_move.need_release)
self.assertEqual(1, len(self.picking.move_lines))
self.assertEqual(10, self.picking.move_lines.product_qty)
@@ -115,7 +122,7 @@ def test_move_done_enqueue_job(self):
)
# and a second one to auto release
trap.assert_enqueued_job(
- self.product1.moves_auto_release,
+ self.product1.pickings_auto_release,
args=(),
kwargs={},
properties=dict(
@@ -127,7 +134,7 @@ def test_move_done_enqueue_job(self):
trap.enqueued_jobs, self.product1.moves_auto_assign
)
job2 = self._get_job_for_method(
- trap.enqueued_jobs, self.product1.moves_auto_release
+ trap.enqueued_jobs, self.product1.pickings_auto_release
)
self.assertIn(job1, job2.depends_on)
@@ -166,3 +173,54 @@ def test_move_field_is_auto_release_allowed(self):
for domain in NOT_RELEASABLE_DOMAINS:
self.assertIn(move_released, self.env["stock.move"].search(domain))
self.assertNotIn(move_not_released, self.env["stock.move"].search(domain))
+
+ def test_picking_policy_one_async_receive(self):
+ self.shipping.action_cancel()
+ self.picking.action_cancel()
+ shipping = self._out_picking(
+ self._create_picking_chain(
+ self.wh,
+ [(self.product1, 10), (self.product2, 10)],
+ date=datetime(2019, 9, 2, 16, 0),
+ )
+ )
+ shipping.release_policy = "one"
+ shipping.move_type = "one"
+ self.assertTrue(
+ all(
+ move.need_release and not move.release_ready
+ for move in shipping.move_lines
+ )
+ )
+ with trap_jobs() as trap:
+ self._receive_product(self.product1, 100)
+ shipping.invalidate_cache()
+ shipping.move_lines.invalidate_cache()
+ jobs = trap.enqueued_jobs
+ with trap_jobs() as trap:
+ for job in jobs:
+ job.perform()
+ job = self._get_job_for_method(
+ trap.enqueued_jobs, shipping.auto_release_available_to_promise
+ )
+ self.assertFalse(job)
+ with trap_jobs() as trap:
+ self._receive_product(self.product2, 100)
+ shipping.invalidate_cache()
+ shipping.move_lines.invalidate_cache()
+ jobs = trap.enqueued_jobs
+ with trap_jobs() as trap:
+ for job in jobs:
+ job.perform()
+ job = self._get_job_for_method(
+ trap.enqueued_jobs, shipping.auto_release_available_to_promise
+ )
+ job.perform()
+ move_product1 = shipping.move_lines.filtered(
+ lambda m: m.product_id == self.product1
+ )
+ move_product2 = shipping.move_lines - move_product1
+ self.assertFalse(move_product2.release_ready)
+ self.assertFalse(move_product2.need_release)
+ self.assertFalse(move_product1.need_release)
+ self.assertFalse(move_product1.release_ready)