Skip to content
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
2 changes: 1 addition & 1 deletion stock_available_to_promise_release_block/__manifest__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
{
"name": "Stock Available to Promise Release - Block",
"summary": """Block Release of Operations""",
"version": "16.0.1.1.1",
"version": "16.0.1.2.0",
"license": "AGPL-3",
"author": "Camptocamp, ACSONE SA/NV, BCIM, Odoo Community Association (OCA)",
"website": "https://github.com/OCA/wms",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# Copyright 2026 ACSONE SA/NV
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).


def migrate(cr, version):
cr.execute(
"""
UPDATE stock_route
SET autoblock_release_on_backorder =
CASE
WHEN autoblock_release_on_backorder_legacy
THEN 'always'
ELSE 'never'
END
"""
)

cr.execute(
"""
ALTER TABLE stock_route
DROP COLUMN IF EXISTS
autoblock_release_on_backorder_legacy
"""
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# Copyright 2026 ACSONE SA/NV
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).


def migrate(cr, version):
cr.execute(
"""
ALTER TABLE stock_route
ADD COLUMN IF NOT EXISTS
autoblock_release_on_backorder_legacy boolean
"""
)

cr.execute(
"""
UPDATE stock_route
SET autoblock_release_on_backorder_legacy =
autoblock_release_on_backorder
"""
)
35 changes: 32 additions & 3 deletions stock_available_to_promise_release_block/models/stock_move.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ class StockMove(models.Model):

release_blocked = fields.Boolean(readonly=True)
release_blocked_label = fields.Char(
string="Release Blocked",
compute="_compute_release_blocked_label",
)

Expand All @@ -31,8 +30,38 @@ def _is_release_ready(self):
return False

def _blocked_on_backorder(self):
"""Hook that aims to be overridden."""
return True
self.ensure_one()
mode = self.rule_id.autoblock_release_on_backorder

if mode == "always":
return True

if mode == "never":
return False

if mode == "single_customer_outgoing_move":
if self.picking_code != "outgoing":
return False

partner = self.partner_id
if not partner:
return False

# Block if no other out moves for this client
other_move_exists = bool(
self.search_count(
[
("id", "!=", self.id),
("partner_id", "=", partner.id),
("state", "in", ["confirmed", "waiting", "assigned"]),
("picking_code", "=", "outgoing"),
],
limit=1,
)
)
return not other_move_exists

return False

def action_block_release(self):
"""Block the release."""
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ class StockPicking(models.Model):
tracking=True,
)
release_blocked_label = fields.Char(
string="Release Blocked",
compute="_compute_release_blocked_label",
)

Expand Down Expand Up @@ -54,8 +53,7 @@ def _create_backorder(self):
backorders = super()._create_backorder()
# Auto-block backorders
for move in backorders.move_ids:
if move.rule_id.autoblock_release_on_backorder:
move.release_blocked = move._blocked_on_backorder()
move.release_blocked = move._blocked_on_backorder()
return backorders

def action_block_release(self):
Expand Down
10 changes: 8 additions & 2 deletions stock_available_to_promise_release_block/models/stock_route.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,13 @@
class StockRoute(models.Model):
_inherit = "stock.route"

autoblock_release_on_backorder = fields.Boolean(
autoblock_release_on_backorder = fields.Selection(
string="Auto-block Release on Backorders",
default=False,
selection=[
("never", "Never"),
("always", "Always"),
("single_customer_outgoing_move", "On single OUT move for customer"),
],
default="never",
required=True,
)
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,6 @@
class StockRule(models.Model):
_inherit = "stock.rule"

autoblock_release_on_backorder = fields.Boolean(
autoblock_release_on_backorder = fields.Selection(
related="route_id.autoblock_release_on_backorder", store=True
)
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
* ACSONE SA/NV
* Nicolas Delbovier <nicolas.delbovier@acsone.eu> (https://www.acsone.eu/)
* BCIM:
* Jacques-Etienne Baudoux <je@bcim.be>
* Camptocamp:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ def test_block_release_allowed(self):
self.assertFalse(picking.block_release_allowed)

def test_autoblock_release_on_backorder(self):
self.wh.delivery_route_id.autoblock_release_on_backorder = True
self.wh.delivery_route_id.autoblock_release_on_backorder = "always"
picking = self._out_picking(
self._create_picking_chain(
self.wh,
Expand All @@ -118,3 +118,60 @@ def test_autoblock_release_on_backorder(self):
# Backorder is not release ready and is automatically blocked
self.assertFalse(backorder.release_ready)
self.assertTrue(backorder.release_blocked)

def test_autoblock_release_on_backorder_single_customer_move_block(self):
"""Backorder is blocked when it becomes the only outgoing move."""
self.wh.delivery_route_id.autoblock_release_on_backorder = (
"single_customer_outgoing_move"
)

picking = self._out_picking(
self._create_picking_chain(
self.wh,
[(self.product1, 3), (self.product2, 5)],
)
)

self._update_qty_in_location(self.loc_bin1, self.product1, 3.0)

move = picking.move_ids.filtered(lambda m: m.product_id == self.product1)
move.quantity_done = move.product_uom_qty
picking.move_ids._action_done()

backorder = picking.backorder_ids

self.assertTrue(backorder.release_blocked)
self.assertFalse(backorder.release_ready)

def test_autoblock_release_on_backorder_single_customer_move_not_blocked(self):
"""Backorder is not blocked when another outgoing move exists."""
self.wh.delivery_route_id.autoblock_release_on_backorder = (
"single_customer_outgoing_move"
)

picking = self._out_picking(
self._create_picking_chain(
self.wh,
[(self.product1, 3), (self.product2, 5)],
)
)

# Create another outgoing picking for the same customer
other_picking = self._out_picking(
self._create_picking_chain(
self.wh,
[(self.product3, 1)],
)
)

self.assertTrue(other_picking)

self._update_qty_in_location(self.loc_bin1, self.product1, 3.0)

move = picking.move_ids.filtered(lambda m: m.product_id == self.product1)
move.quantity_done = move.product_uom_qty
picking.move_ids._action_done()

backorder = picking.backorder_ids

self.assertFalse(backorder.release_blocked)
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,12 @@
ref="stock_available_to_promise_release.stock_route_form_view"
/>
<field name="arch" type="xml">
<field name="no_backorder_at_release" position="after">
<xpath expr="//field[@name='no_backorder_at_release']" position="after">
<field
name="autoblock_release_on_backorder"
attrs="{'invisible': [('available_to_promise_defer_pull', '=', False)]}"
/>
</field>
</xpath>
</field>
</record>

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# Copyright 2026 ACSONE SA/NV
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).

from odoo import models


class StockMove(models.Model):

_inherit = "stock.move"

def _blocked_on_backorder(self):
res = super()._blocked_on_backorder()

...

return res
Loading