Skip to content

Commit a52a5e7

Browse files
committed
[MIG] stock_available_to_promise_release: Migration to 18.0
1 parent 40c8193 commit a52a5e7

21 files changed

Lines changed: 134 additions & 158 deletions

stock_available_to_promise_release/__manifest__.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,12 @@
33

44
{
55
"name": "Stock Available to Promise Release",
6-
"version": "16.0.3.7.1",
6+
"version": "18.0.1.0.0",
77
"summary": "Release Operations based on available to promise",
88
"author": "Camptocamp, BCIM, Odoo Community Association (OCA)",
99
"website": "https://github.com/OCA/wms",
1010
"category": "Stock Management",
11-
"depends": ["stock"],
11+
"depends": ["stock", "stock_warehouse_out_pull"],
1212
"data": [
1313
"security/ir.model.access.csv",
1414
"views/product_product_views.xml",

stock_available_to_promise_release/hooks.py

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -5,17 +5,17 @@
55
from openupgradelib import openupgrade
66

77
from odoo import SUPERUSER_ID, api
8-
from odoo.tools import sql
8+
from odoo.tools.sql import column_exists
99

1010
_logger = logging.getLogger(__name__)
1111

1212

13-
def init_release_policy(cr):
14-
if not sql.column_exists(cr, "stock_picking", "release_policy"):
13+
def init_release_policy(env):
14+
if not column_exists(env.cr, "stock_picking", "release_policy"):
1515
# Use the default sql query instead relying on ORM as all records will
1616
# be updated.
1717
_logger.info("Creating 'release_policy' field on stock.picking")
18-
env = api.Environment(cr, SUPERUSER_ID, {})
18+
env = api.Environment(env.cr, SUPERUSER_ID, {})
1919
field_spec = [
2020
(
2121
"release_policy",
@@ -30,23 +30,23 @@ def init_release_policy(cr):
3030
openupgrade.add_fields(env, field_spec=field_spec)
3131

3232

33-
def pre_init_hook(cr):
33+
def pre_init_hook(env):
3434
"""create and initialize the date priority column on the stock move"""
35-
if not sql.column_exists(cr, "stock_move", "date_priority"):
35+
if not column_exists(env.cr, "stock_move", "date_priority"):
3636
_logger.info("Create date_priority column")
37-
cr.execute(
37+
env.cr.execute(
3838
"""
3939
ALTER TABLE stock_move
4040
ADD COLUMN date_priority timestamp;
4141
"""
4242
)
4343
_logger.info("Initialize date_priority field")
44-
cr.execute(
44+
env.cr.execute(
4545
"""
4646
UPDATE stock_move
4747
SET date_priority = create_date
4848
where state not in ('done', 'cancel')
4949
"""
5050
)
51-
_logger.info(f"{cr.rowcount} rows updated")
52-
init_release_policy(cr)
51+
_logger.info(f"{env.cr.rowcount} rows updated")
52+
init_release_policy(env)

stock_available_to_promise_release/migrations/16.0.2.0.0/post-migrate.py

Lines changed: 0 additions & 14 deletions
This file was deleted.

stock_available_to_promise_release/migrations/16.0.3.1.0/post-migrate.py

Lines changed: 0 additions & 18 deletions
This file was deleted.

stock_available_to_promise_release/migrations/16.0.3.1.0/pre-migrate.py

Lines changed: 0 additions & 15 deletions
This file was deleted.

stock_available_to_promise_release/models/stock_location.py

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,8 @@ class StockLocation(models.Model):
1010
def _get_available_to_promise_domain(self):
1111
location_domain = []
1212
for location in self:
13-
location_domain = expression.OR(
14-
[
15-
location_domain,
16-
[("location_id.parent_path", "=like", location.parent_path + "%")],
17-
]
13+
paths_domain = expression.OR(
14+
[[("parent_path", "=like", location.parent_path + "%")]]
1815
)
16+
location_domain = [("location_id", "any", paths_domain)]
1917
return location_domain

stock_available_to_promise_release/models/stock_move.py

Lines changed: 16 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,8 @@ def _in_progress_for_unrelease(self) -> StockMoveBase:
111111
if printed_pickings:
112112
return printed_pickings
113113
return unreleasable_moves.filtered(
114-
lambda m: not m.rule_id.allow_unrelease_return_done_move and m.quantity_done
114+
lambda m: not m.rule_id.allow_unrelease_return_done_move
115+
and any(ml.quantity > 0 and ml.picked for ml in m.move_line_ids)
115116
)
116117

117118
def _is_unrelease_allowed_on_origin_moves(self, origin_moves):
@@ -127,7 +128,7 @@ def _is_unrelease_allowed_on_origin_moves(self, origin_moves):
127128
)
128129
origin_qty_done = sum(
129130
m.product_uom._compute_quantity(
130-
m.quantity_done,
131+
m.quantity,
131132
m.product_id.uom_id,
132133
rounding_method="HALF-UP",
133134
)
@@ -136,7 +137,7 @@ def _is_unrelease_allowed_on_origin_moves(self, origin_moves):
136137
dest_done_moves = origin_done_moves.move_dest_ids
137138
dest_qty_done = sum(
138139
m.product_uom._compute_quantity(
139-
m.quantity_done,
140+
m.quantity,
140141
m.product_id.uom_id,
141142
rounding_method="HALF-UP",
142143
)
@@ -160,7 +161,7 @@ def _unrelease_not_allowed_error(self):
160161
forbidden_moves_by_picking = self.browse().concat(
161162
*forbidden_moves_by_picking
162163
)
163-
message += "\n\t- %s" % picking.name
164+
message += f"\n\t- {picking.name}"
164165
forbidden_origin_pickings = self.picking_id.browse()
165166
for move in forbidden_moves_by_picking:
166167
iterator = move._get_chained_moves_iterator("move_orig_ids")
@@ -288,7 +289,7 @@ def _group_by_warehouse(self):
288289

289290
def _get_previous_promised_qties(self):
290291
self.env.flush_all()
291-
self.env["stock.move.line"].flush_model(["move_id", "reserved_qty"])
292+
self.env["stock.move.line"].flush_model(["move_id", "quantity"])
292293
self.env["stock.location"].flush_model(["parent_path"])
293294
previous_promised_qties = {}
294295
for warehouse, moves in self._group_by_warehouse():
@@ -396,7 +397,7 @@ def _get_ordered_available_to_promise_by_warehouse(self, warehouse):
396397
[[("product_id", "in", self.product_id.ids)], location_domain]
397398
)
398399
location_quants = self.env["stock.quant"].read_group(
399-
domain_quant, ["product_id", "quantity"], ["product_id"], orderby="id"
400+
domain_quant, ["product_id", "quantity"], ["product_id"]
400401
)
401402
quants_available = {
402403
item["product_id"][0]: item["quantity"] for item in location_quants
@@ -476,7 +477,7 @@ def _search_ordered_available_to_promise_uom_qty(self, operator, value):
476477
def _should_compute_ordered_available_to_promise(self):
477478
return (
478479
self.picking_code == "outgoing"
479-
and not self.product_id.type == "consu"
480+
and self.product_id.is_storable
480481
and not self.location_id.should_bypass_reservation()
481482
)
482483

@@ -565,6 +566,7 @@ def _run_stock_rule(self):
565566
for move in released_moves:
566567
move._before_release()
567568
values = move._prepare_procurement_values()
569+
values["move_dest_ids"] = self
568570
procurement_requests.append(
569571
self.env["procurement.group"].Procurement(
570572
move.product_id,
@@ -688,7 +690,6 @@ def _return_quantity_in_stock(self, qty_to_return_per_move):
688690
return_type = picking_type.return_picking_type_id
689691
wiz_values = {
690692
"picking_id": fields.first(pickings).id,
691-
"location_id": return_type.default_location_dest_id.id,
692693
}
693694
product_return_moves = []
694695
if not return_type:
@@ -717,7 +718,7 @@ def _return_quantity_in_stock(self, qty_to_return_per_move):
717718
if product_return_moves:
718719
wiz_values["product_return_moves"] = product_return_moves
719720
return_wiz = self.env["stock.return.picking"].create(wiz_values)
720-
action = return_wiz.create_returns()
721+
action = return_wiz.action_create_returns()
721722

722723
cancel_picking = self.picking_id.browse(action["res_id"])
723724
# Do not copy the responsible user from the source picking as somebody
@@ -899,7 +900,9 @@ def _search_picking_for_assignation_domain(self):
899900

900901
def _get_new_picking_values(self):
901902
values = super()._get_new_picking_values()
902-
values["release_policy"] = values["move_type"]
903+
# Ensure move_type exists in values before accessing it
904+
if "move_type" in values:
905+
values["release_policy"] = values["move_type"]
903906
return values
904907

905908
def write(self, vals):
@@ -939,8 +942,9 @@ def _update_candidate_moves_list(self, candidate_moves):
939942
)
940943
for candidates in candidate_moves
941944
]
942-
# filter given list of moves to keep only the new ones
943-
candidate_moves[:] = new_candidate_moves
945+
# clear and update the candidate set
946+
candidate_moves.clear()
947+
candidate_moves.update(new_candidate_moves)
944948
return res
945949

946950
def _merge_moves(self, merge_into=False):

stock_available_to_promise_release/models/stock_picking.py

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -134,12 +134,7 @@ def release_available_to_promise(self):
134134
def _release_link_backorder(self, origin_picking):
135135
self.backorder_id = origin_picking
136136
origin_picking.message_post(
137-
body=_(
138-
"The backorder <a href=# data-oe-model=stock.picking"
139-
" data-oe-id=%(id)s>%(name)s</a> has been created.",
140-
name=self.name,
141-
id=self.id,
142-
)
137+
body=_("The backorder %s has been created.") % self._get_html_link()
143138
)
144139

145140
def _after_release_update_chain(self):

stock_available_to_promise_release/tests/common.py

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -17,21 +17,22 @@ def setUpClass(cls):
1717
"reception_steps": "one_step",
1818
"delivery_steps": "pick_ship",
1919
"code": "WHTEST",
20+
"delivery_pull": True,
2021
}
2122
)
2223
cls.loc_stock = cls.wh.lot_stock_id
2324
cls.loc_customer = cls.env.ref("stock.stock_location_customers")
2425
cls.product1 = cls.env["product.product"].create(
25-
{"name": "Product 1", "type": "product"}
26+
{"name": "Product 1", "type": "consu", "is_storable": True}
2627
)
2728
cls.product2 = cls.env["product.product"].create(
28-
{"name": "Product 2", "type": "product"}
29+
{"name": "Product 2", "type": "consu", "is_storable": True}
2930
)
3031
cls.product3 = cls.env["product.product"].create(
31-
{"name": "Product 3", "type": "product"}
32+
{"name": "Product 3", "type": "consu", "is_storable": True}
3233
)
3334
cls.product4 = cls.env["product.product"].create(
34-
{"name": "Product 4", "type": "product"}
35+
{"name": "Product 4", "type": "consu", "is_storable": True}
3536
)
3637
cls.uom_unit = cls.env.ref("uom.product_uom_unit")
3738
cls.partner_delta = cls.env.ref("base.res_partner_4")
@@ -141,9 +142,13 @@ def _deliver(cls, picking, product_qty=None):
141142
if product_qty:
142143
lines = picking.move_ids.move_line_ids
143144
for product, qty in product_qty:
144-
line = lines.filtered(lambda m: m.product_id == product)
145-
line.qty_done = qty
145+
line = lines.filtered(
146+
lambda m, product=product: m.product_id == product
147+
)
148+
line.quantity = qty
149+
line.is_inventory = True
150+
line.picked = True
146151
else:
147152
for line in picking.mapped("move_ids.move_line_ids"):
148-
line.qty_done = line.reserved_qty
153+
line.picked = True
149154
picking._action_done()

stock_available_to_promise_release/tests/test_merge_moves.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,6 @@ def setUpClass(cls):
2626
)
2727
cls.shipping1.release_available_to_promise()
2828
cls.picking1 = cls._prev_picking(cls.shipping1)
29-
cls.picking1.action_assign()
3029

3130
@classmethod
3231
def _out_picking(cls, pickings):
@@ -46,7 +45,7 @@ def _procure(self, qty):
4645
values = {
4746
"company_id": self.wh.company_id,
4847
"group_id": self.shipping1.group_id,
49-
"date_planned": self.shipping1.move_ids.date,
48+
"date_planned": self.shipping1.move_ids[0].date,
5049
"warehouse_id": self.wh,
5150
}
5251
self.env["procurement.group"].run(
@@ -111,7 +110,8 @@ def test_unrelease_at_move_merge_merged(self):
111110

112111
# partially process the picking
113112
move = self.picking1.move_ids
114-
move.move_line_ids.qty_done = 2
113+
move._action_assign()
114+
move.move_line_ids.quantity = 2
115115
# run a new procurement for the same product in the shipment 1
116116
self._procure(2)
117117

@@ -126,7 +126,7 @@ def test_unrelease_at_move_merge_merged(self):
126126
# the pick should still contain a move with the processed qty
127127
# and the qty to do should be the one from shipping2
128128
move = self.picking1.move_ids.filtered(lambda m: m.state == "assigned")
129-
self.assertEqual(2, move.move_line_ids.qty_done)
129+
self.assertEqual(2, move.move_line_ids.quantity)
130130
self.assertEqual(5, move.product_uom_qty)
131131

132132
# if we release the ship 1 again, a new move should be created
@@ -135,7 +135,7 @@ def test_unrelease_at_move_merge_merged(self):
135135
move = self.picking1.move_ids.filtered(lambda m: m.state == "assigned")
136136
self.assertEqual(1, len(move))
137137
self.assertEqual(2 + original_qty_1 + original_qty_2, move.product_uom_qty)
138-
self.assertEqual(2, move.move_line_ids.qty_done)
138+
self.assertEqual(2, move.move_line_ids.quantity)
139139

140140
def test_default_merge(self):
141141
# check that the merge is still working when the available_to_promise_defer_pull

0 commit comments

Comments
 (0)