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

[16.0] sale_stock_available_to_promise_release_block: improve Unblock Release process #906

Open
wants to merge 11 commits into
base: 16.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
12 changes: 8 additions & 4 deletions sale_stock_available_to_promise_release/tests/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,13 @@ def setUpClassProduct(cls):
}
)

@classmethod
def _create_sale_order(cls):
return cls.env["sale.order"].create({"partner_id": cls.customer.id})

@classmethod
def setUpClassSale(cls):
customer = cls.env["res.partner"].create(
{"name": "Partner who loves storable products"}
)
cls.sale = cls.env["sale.order"].create({"partner_id": customer.id})
cls.sale = cls._create_sale_order()
cls.line = cls.env["sale.order.line"].create(
{
"order_id": cls.sale.id,
Expand All @@ -45,6 +46,9 @@ def setUpClassStock(cls):
def setUpClass(cls):
super().setUpClass()
cls.env = cls.env(context=dict(cls.env.context, tracking_disable=True))
cls.customer = cls.env["res.partner"].create(
{"name": "Partner who loves storable products"}
)
cls.setUpClassProduct()
cls.setUpClassSale()
cls.setUpClassStock()
Expand Down
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 @@
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)

Check warning on line 62 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#L62

Added line #L62 was not covered by tests

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
Loading