Skip to content

Commit

Permalink
sale_stock_available_to_promise_release_block: add Unblock Release wi…
Browse files Browse the repository at this point in the history
…zard

This new wizard allows to give more options to the user regarding the
unblocking process, like the scheduled date to set on unblocked moves,
and re-assign automatically a stock operation on them (so they could be
grouped together in the same transfer).
  • Loading branch information
sebalix committed Jan 14, 2025
1 parent 1976a48 commit d936e96
Show file tree
Hide file tree
Showing 16 changed files with 678 additions and 11 deletions.
2 changes: 2 additions & 0 deletions sale_stock_available_to_promise_release_block/__init__.py
Original file line number Diff line number Diff line change
@@ -1 +1,3 @@
from . import models
from . import wizards
from .hooks import post_init_hook
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
{
"name": "Stock Available to Promise Release - Block from Sales",
"summary": """Block release of deliveries from sales orders.""",
"version": "16.0.1.0.0",
"version": "16.0.1.1.0",
"license": "AGPL-3",
"author": "Camptcamp, ACSONE SA/NV, BCIM, Odoo Community Association (OCA)",
"website": "https://github.com/OCA/wms",
Expand All @@ -15,8 +15,12 @@
"stock_available_to_promise_release_block",
],
"data": [
"security/ir.model.access.csv",
"views/sale_order.xml",
"views/sale_order_line.xml",
"views/stock_move.xml",
"wizards/unblock_release.xml",
],
"installable": True,
"post_init_hook": "post_init_hook",
}
18 changes: 18 additions & 0 deletions sale_stock_available_to_promise_release_block/hooks.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Copyright 2024 Camptocamp SA
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl)

import logging

from odoo import SUPERUSER_ID, api

_logger = logging.getLogger(__name__)


def post_init_hook(cr, registry):
_logger.info("Remove original 'Unblock Release' server action...")
env = api.Environment(cr, SUPERUSER_ID, {})
action = env.ref(
"stock_available_to_promise_release_block.action_stock_move_unblock_release",
raise_if_not_found=False,
)
action.unlink()
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# Copyright 2024 Camptocamp SA
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl)
import logging

_logger = logging.getLogger(__name__)


def migrate(cr, version):
if not version:
return
remove_unblock_release_ir_action_server(cr)


def remove_unblock_release_ir_action_server(cr):
# The same XML-ID will be used by a new window action to open a wizard
_logger.info("Remove action 'action_sale_order_line_unblock_release'")
queries = [
"""
DELETE FROM ir_act_server
WHERE id IN (
SELECT res_id
FROM ir_model_data
WHERE module='sale_stock_available_to_promise_release_block'
AND name='action_sale_order_line_unblock_release'
AND model='ir.actions.server'
);
""",
"""
DELETE FROM ir_model_data
WHERE module='sale_stock_available_to_promise_release_block'
AND name='action_sale_order_line_unblock_release';
""",
]
for query in queries:
cr.execute(query)
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
from . import stock_move
from . import stock_rule
from . import sale_order
from . import sale_order_line
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
# Copyright 2024 Camptocamp
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).

from odoo import fields, models
from odoo import api, fields, models


class SaleOrder(models.Model):
Expand All @@ -14,3 +14,93 @@ class SaleOrder(models.Model):
states={"draft": [("readonly", False)]},
help="Block the release of the generated delivery at order confirmation.",
)
available_move_to_unblock_ids = fields.One2many(
comodel_name="stock.move",
compute="_compute_available_move_to_unblock_ids",
string="Available moves to unblock",
help="Available moves to unblock for this order.",
)
available_move_to_unblock_count = fields.Integer(
compute="_compute_available_move_to_unblock_ids"
)
move_to_unblock_ids = fields.One2many(
comodel_name="stock.move",
inverse_name="unblocked_by_order_id",
string="Moves To Unblock",
readonly=True,
help="Moves to unblock when the current order is confirmed.",
)
move_to_unblock_count = fields.Integer(compute="_compute_move_to_unblock_count")

def _domain_available_move_to_unblock(self):
self.ensure_one()
# Returns domain for moves:
# - of type delivery
# - sharing the same shipping address
# - not yet release and blocked
return [
("picking_type_id.code", "=", "outgoing"),
("partner_id", "=", self.partner_shipping_id.id),
("state", "=", "waiting"),
("need_release", "=", True),
("release_blocked", "=", True),
("unblocked_by_order_id", "!=", self.id),
]

@api.depends("order_line.move_ids")
def _compute_available_move_to_unblock_ids(self):
for order in self:
moves = self.env["stock.move"].search(
order._domain_available_move_to_unblock()
)
self.available_move_to_unblock_ids = moves
self.available_move_to_unblock_count = len(moves)

@api.depends("move_to_unblock_ids")
def _compute_move_to_unblock_count(self):
for order in self:
order.move_to_unblock_count = len(order.move_to_unblock_ids)

def action_open_move_need_release(self):
action = super().action_open_move_need_release()

Check warning on line 65 in sale_stock_available_to_promise_release_block/models/sale_order.py

View check run for this annotation

Codecov / codecov/patch

sale_stock_available_to_promise_release_block/models/sale_order.py#L65

Added line #L65 was not covered by tests
if not action.get("context"):
action["context"] = {}
action["context"].update(from_sale_order_id=self.id)
return action

Check warning on line 69 in sale_stock_available_to_promise_release_block/models/sale_order.py

View check run for this annotation

Codecov / codecov/patch

sale_stock_available_to_promise_release_block/models/sale_order.py#L67-L69

Added lines #L67 - L69 were not covered by tests

def action_open_available_move_to_unblock(self):
self.ensure_one()

Check warning on line 72 in sale_stock_available_to_promise_release_block/models/sale_order.py

View check run for this annotation

Codecov / codecov/patch

sale_stock_available_to_promise_release_block/models/sale_order.py#L72

Added line #L72 was not covered by tests
if not self.available_move_to_unblock_count:
return
xmlid = "stock_available_to_promise_release.stock_move_release_action"
action = self.env["ir.actions.act_window"]._for_xml_id(xmlid)
action["domain"] = [("id", "in", self.available_move_to_unblock_ids.ids)]
action["context"] = {"from_sale_order_id": self.id}
return action

Check warning on line 79 in sale_stock_available_to_promise_release_block/models/sale_order.py

View check run for this annotation

Codecov / codecov/patch

sale_stock_available_to_promise_release_block/models/sale_order.py#L74-L79

Added lines #L74 - L79 were not covered by tests

def action_open_move_to_unblock(self):
self.ensure_one()

Check warning on line 82 in sale_stock_available_to_promise_release_block/models/sale_order.py

View check run for this annotation

Codecov / codecov/patch

sale_stock_available_to_promise_release_block/models/sale_order.py#L82

Added line #L82 was not covered by tests
if not self.move_to_unblock_count:
return
xmlid = "stock_available_to_promise_release.stock_move_release_action"
action = self.env["ir.actions.act_window"]._for_xml_id(xmlid)
action["domain"] = [("id", "in", self.move_to_unblock_ids.ids)]
action["context"] = {}
return action

Check warning on line 89 in sale_stock_available_to_promise_release_block/models/sale_order.py

View check run for this annotation

Codecov / codecov/patch

sale_stock_available_to_promise_release_block/models/sale_order.py#L84-L89

Added lines #L84 - L89 were not covered by tests

def action_confirm(self):
# Reschedule the blocked moves when confirming the order
# NOTE: If a module like 'stock_picking_group_by_partner_by_carrier_by_date'
# is installed, these moves + the new ones generated by the current order
# will all be grouped in the same delivery order as soon as they share
# the same grouping criteria (partner, date, carrier...).
for order in self:
if order.move_to_unblock_ids:
date_deadline = order.commitment_date or order.expected_date
self.env["unblock.release"]._reschedule_moves(
order.move_to_unblock_ids, date_deadline, from_order=order
)
# Unblock the release
if not order.block_release:
order.move_to_unblock_ids.action_unblock_release()
return super().action_confirm()
16 changes: 16 additions & 0 deletions sale_stock_available_to_promise_release_block/models/stock_move.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# Copyright 2024 Camptocamp SA
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl)

from odoo import fields, models


class StockMove(models.Model):
_inherit = "stock.move"

unblocked_by_order_id = fields.Many2one(
comodel_name="sale.order",
ondelete="set null",
string="Unblocked by order",
readonly=True,
index=True,
)
Original file line number Diff line number Diff line change
@@ -1 +1,12 @@
Block release of deliveries from sale orders.
Block and unblock release of deliveries from sale orders.

Release of deliveries can be blocked right after the sale order confirmation.

When encoding a new order sharing the same delivery address, the user can
list the existing blocked deliveries (backorders) and plan to unblock them
when this new order is confirmed, making the existing deliveries and the new
ones sharing the same scheduled dates and deadlines.

As a side-effect, this will leverage the module
`stock_picking_group_by_partner_by_carrier_by_date` if this one is installed,
by grouping all delivery lines within the same delivery order.
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_unblock_release_sale,access.unblock.release,model_unblock_release,sales_team.group_sale_salesman,1,1,1,0
access_unblock_release_stock,access.unblock.release,model_unblock_release,stock.group_stock_user,1,1,1,0
Loading

0 comments on commit d936e96

Please sign in to comment.