Skip to content

Commit d31d34f

Browse files
committed
[IMP] stock_vertical_lift: shared stock location for shuttles
1 parent c05e107 commit d31d34f

19 files changed

+618
-92
lines changed

stock_vertical_lift/README.rst

Lines changed: 28 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,3 @@
1-
.. image:: https://odoo-community.org/readme-banner-image
2-
:target: https://odoo-community.org/get-involved?utm_source=readme
3-
:alt: Odoo Community Association
4-
51
=============
62
Vertical Lift
73
=============
@@ -17,7 +13,7 @@ Vertical Lift
1713
.. |badge1| image:: https://img.shields.io/badge/maturity-Alpha-red.png
1814
:target: https://odoo-community.org/page/development-status
1915
:alt: Alpha
20-
.. |badge2| image:: https://img.shields.io/badge/license-AGPL--3-blue.png
16+
.. |badge2| image:: https://img.shields.io/badge/licence-AGPL--3-blue.png
2117
:target: http://www.gnu.org/licenses/agpl-3.0-standalone.html
2218
:alt: License: AGPL-3
2319
.. |badge3| image:: https://img.shields.io/badge/github-OCA%2Fstock--logistics--warehouse-lightgray.png?logo=github
@@ -54,25 +50,25 @@ General
5450

5551
In Inventory Settings, you must have:
5652

57-
- Storage Locations
58-
- Multi-Warehouses
59-
- Multi-Step Routes
53+
- Storage Locations
54+
- Multi-Warehouses
55+
- Multi-Step Routes
6056

6157
Locations
6258
---------
6359

6460
Additional configuration parameters are added in Locations:
6561

66-
- Sub-locations of a location with the "Is a Vertical Lift View
67-
Location" activated are considered as "Shuttles". A shuttle is a
68-
vertical lift shelf.
69-
- Sub-locations of shuttles are considered as "Trays", which is a tier
70-
of a shuttle. When a tray is created, a tray type must be selected.
71-
When saved, the tray location will automatically create as many
72-
sub-locations - called "Cells" - as the tray type contains.
73-
- The tray type of a tray can be changed as long as none of its cell
74-
contains products. When changed, it archives the cells and creates new
75-
ones as configured on the new tray type.
62+
- Sub-locations of a location with the "Is a Vertical Lift View
63+
Location" activated are considered as "Shuttles". A shuttle is a
64+
vertical lift shelf.
65+
- Sub-locations of shuttles are considered as "Trays", which is a tier
66+
of a shuttle. When a tray is created, a tray type must be selected.
67+
When saved, the tray location will automatically create as many
68+
sub-locations - called "Cells" - as the tray type contains.
69+
- The tray type of a tray can be changed as long as none of its cell
70+
contains products. When changed, it archives the cells and creates
71+
new ones as configured on the new tray type.
7672

7773
Tray types
7874
----------
@@ -129,15 +125,15 @@ The barcodes used are of the type Code 128 (with the code set B).
129125
Known issues / Roadmap
130126
======================
131127

132-
- Complete screen workflows (currently enough for a demo, not for
133-
production)
134-
- Inventory: find a way to have a nice autofocus for quantity, still
135-
compatible with barcode scanner (Odoo disables the autofocus when
136-
using barcode, which makes sense)
137-
- Put-away: handle packages
138-
- Handle "multi-shuttle" put-away
139-
- Create glue module for product_expiry
140-
- Challenge the save + release buttons and workflow
128+
- Complete screen workflows (currently enough for a demo, not for
129+
production)
130+
- Inventory: find a way to have a nice autofocus for quantity, still
131+
compatible with barcode scanner (Odoo disables the autofocus when
132+
using barcode, which makes sense)
133+
- Put-away: handle packages
134+
- Handle "multi-shuttle" put-away
135+
- Create glue module for product_expiry
136+
- Challenge the save + release buttons and workflow
141137

142138
Bug Tracker
143139
===========
@@ -160,22 +156,22 @@ Authors
160156
Contributors
161157
------------
162158

163-
- Guewen Baconnier <guewen.baconnier@camptocamp.com>
159+
- Guewen Baconnier <guewen.baconnier@camptocamp.com>
164160

165161
Trobz
166162

167-
- Dung Tran <dungtd@trobz.com>
163+
- Dung Tran <dungtd@trobz.com>
168164

169-
- Nhan Tran <nhant@trobz.com>
165+
- Nhan Tran <nhant@trobz.com>
170166

171-
- Jacques-Etienne Baudoux (BCIM) <je@bcim.be>
167+
- Jacques-Etienne Baudoux (BCIM) <je@bcim.be>
172168

173169
Other credits
174170
-------------
175171

176172
The development of this module has been financially supported by:
177173

178-
- Camptocamp
174+
- Camptocamp
179175

180176
Maintainers
181177
-----------

stock_vertical_lift/__manifest__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
"views/vertical_lift_operation_inventory_views.xml",
3333
"views/shuttle_screen_templates.xml",
3434
"views/res_config_settings_views.xml",
35+
"wizards/vertical_lift_select_shuttle.xml",
3536
"security/ir.model.access.csv",
3637
"data/ir_sequence.xml",
3738
],

stock_vertical_lift/models/stock_location.py

Lines changed: 81 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -28,15 +28,15 @@ class StockLocation(models.Model):
2828
# give the unique shuttle for any location in the tree (whether it's a
2929
# shuttle, a tray or a cell)
3030
inverse_vertical_lift_shuttle_ids = fields.One2many(
31-
comodel_name="vertical.lift.shuttle", inverse_name="location_id"
31+
comodel_name="vertical.lift.shuttle", inverse_name="shared_storage_location_id"
3232
)
3333
# compute the unique shuttle for any shuttle, tray or cell location, by
3434
# going through the parents
3535
vertical_lift_shuttle_id = fields.Many2one(
3636
comodel_name="vertical.lift.shuttle",
3737
compute="_compute_vertical_lift_shuttle_id",
3838
recursive=True,
39-
store=True,
39+
store=False,
4040
)
4141

4242
@api.depends(
@@ -51,22 +51,23 @@ def _compute_vertical_lift_kind(self):
5151
kind = tree.get(location.location_id.vertical_lift_kind, False)
5252
location.vertical_lift_kind = kind
5353

54-
@api.depends(
55-
"inverse_vertical_lift_shuttle_ids", "location_id.vertical_lift_shuttle_id"
56-
)
54+
@api.depends("inverse_vertical_lift_shuttle_ids")
55+
@api.depends_context("shuttle_id")
5756
def _compute_vertical_lift_shuttle_id(self):
57+
shuttle_id = self.env.context.get("shuttle_id")
5858
for location in self:
59-
if location.inverse_vertical_lift_shuttle_ids:
60-
# we have a unique constraint on the other side
61-
assert len(location.inverse_vertical_lift_shuttle_ids) == 1
59+
if len(location.inverse_vertical_lift_shuttle_ids) == 1:
6260
shuttle = location.inverse_vertical_lift_shuttle_ids
6361
else:
64-
shuttle = location.location_id.vertical_lift_shuttle_id
62+
shuttle = self.env["vertical.lift.shuttle"].browse(shuttle_id)
6563
location.vertical_lift_shuttle_id = shuttle
6664

6765
def _hardware_vertical_lift_fetch_tray(self, cell_location=None):
68-
payload = self._hardware_vertical_lift_fetch_tray_payload(cell_location)
69-
return self.vertical_lift_shuttle_id._hardware_send_message(payload)
66+
shuttle = self.vertical_lift_shuttle_id
67+
payload = self._hardware_vertical_lift_fetch_tray_payload(
68+
cell_location=cell_location
69+
)
70+
return shuttle._hardware_send_message(payload)
7071

7172
def _hardware_vertical_lift_fetch_tray_payload(self, cell_location=None):
7273
"""Prepare "fetch" message to be sent to the vertical lift hardware
@@ -107,7 +108,8 @@ def _hardware_vertical_lift_fetch_tray_payload(self, cell_location=None):
107108
Returns a message in bytes, that will be sent through
108109
``VerticalLiftShuttle._hardware_send_message()``.
109110
"""
110-
if self.vertical_lift_shuttle_id.hardware == "simulation":
111+
shuttle = self.vertical_lift_shuttle_id
112+
if shuttle.hardware == "simulation":
111113
message = self.env._("Opening tray %(name)s.", name=self.name)
112114
if cell_location:
113115
from_left, from_bottom = cell_location.tray_cell_center_position()
@@ -136,13 +138,20 @@ def fetch_vertical_lift_tray(self, cell_location=None):
136138
``_hardware_vertical_lift_fetch_tray()``.
137139
"""
138140
self.ensure_one()
139-
if self.vertical_lift_kind == "cell":
140-
if cell_location:
141-
raise ValueError(
142-
"cell_location cannot be set when the location is a cell."
141+
if self.vertical_lift_kind == "cell" and cell_location:
142+
raise ValueError("cell_location cannot be set when the location is a cell.")
143+
if not (shuttle := self.vertical_lift_shuttle_id):
144+
raise exceptions.UserError(
145+
self.env._(
146+
"Cannot determine which shuttle to use on location %s",
147+
self.name,
143148
)
149+
)
150+
if self.vertical_lift_kind == "cell":
144151
tray = self.location_id
145-
tray.fetch_vertical_lift_tray(cell_location=self)
152+
tray.with_context(shuttle_id=shuttle.id).fetch_vertical_lift_tray(
153+
cell_location=self
154+
)
146155
elif self.vertical_lift_kind == "tray":
147156
self._hardware_vertical_lift_fetch_tray(cell_location=cell_location)
148157
else:
@@ -154,14 +163,63 @@ def fetch_vertical_lift_tray(self, cell_location=None):
154163
)
155164
return True
156165

166+
def _get_shuttles(self):
167+
self.ensure_one()
168+
# Reached the top of hierarchy without finding a shuttle
169+
if not self:
170+
return self.env["vertical.lift.shuttle"]
171+
# Found a location linked to a shuttle
172+
if shuttles := self.inverse_vertical_lift_shuttle_ids:
173+
return shuttles
174+
# Check the parent location
175+
return self.location_id._get_shuttles()
176+
157177
def button_fetch_vertical_lift_tray(self):
158178
self.ensure_one()
159-
if self.vertical_lift_kind in ("cell", "tray"):
160-
self.fetch_vertical_lift_tray()
161-
return True
179+
if self.vertical_lift_kind not in ("cell", "tray"):
180+
return True
181+
182+
# If shuttle is explicitly provided (e.g. from wizard), use it
183+
if self.vertical_lift_shuttle_id:
184+
return self.fetch_vertical_lift_tray()
185+
186+
# Otherwise, check for a unique link
187+
shuttles = self._get_shuttles()
188+
if len(shuttles) == 1:
189+
return self.with_context(shuttle_id=shuttles.id).fetch_vertical_lift_tray()
190+
191+
# If shared (len > 1) or no link (len == 0), open shuttle selector
192+
return self._open_shuttle_selector("button_fetch_vertical_lift_tray")
162193

163194
def button_release_vertical_lift_tray(self):
164195
self.ensure_one()
165-
if self.vertical_lift_kind:
166-
self.vertical_lift_shuttle_id.release_vertical_lift_tray()
167-
return True
196+
if not self.vertical_lift_kind:
197+
return True
198+
199+
# If shuttle is explicitly provided (e.g. from wizard), use it
200+
if shuttle := self.vertical_lift_shuttle_id:
201+
return shuttle.release_vertical_lift_tray()
202+
203+
# Otherwise, check for a unique link
204+
shuttles = self._get_shuttles()
205+
if len(shuttles) == 1:
206+
return shuttles.release_vertical_lift_tray()
207+
208+
# If shared (len > 1) or no link (len == 0), open shuttle selector
209+
return self._open_shuttle_selector("button_release_vertical_lift_tray")
210+
211+
def _open_shuttle_selector(self, method_name):
212+
self.ensure_one()
213+
return {
214+
"name": self.env._("Select Shuttle for %s", self.name),
215+
"type": "ir.actions.act_window",
216+
"res_model": "vertical.lift.select.shuttle",
217+
"view_mode": "form",
218+
"target": "new",
219+
"context": {
220+
"default_location_id": self.id,
221+
"default_res_model": self._name,
222+
"default_res_id": self.id,
223+
"default_method_name": method_name,
224+
},
225+
}

stock_vertical_lift/models/stock_move_line.py

Lines changed: 58 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,66 @@ class StockMoveLine(models.Model):
1414
"skip its processing.",
1515
)
1616

17+
def _get_shuttles(self, location):
18+
self.ensure_one()
19+
# Reached the top of hierarchy without finding a shuttle
20+
if not location:
21+
return self.env["vertical.lift.shuttle"]
22+
# Found a location linked to a shuttle
23+
if shuttles := location.inverse_vertical_lift_shuttle_ids:
24+
return shuttles
25+
# Check the parent location
26+
return self._get_shuttles(location.location_id)
27+
1728
def fetch_vertical_lift_tray_source(self):
1829
self.ensure_one()
19-
self.location_id.fetch_vertical_lift_tray()
20-
return {"type": "ir.actions.client", "tag": "soft_reload"}
30+
location = self.location_id
31+
32+
# If shuttle is explicitly provided in context (e.g. from wizard confirm)
33+
if self.env.context.get("shuttle_id"):
34+
location.fetch_vertical_lift_tray()
35+
return {"type": "ir.actions.client", "tag": "soft_reload"}
36+
37+
# Otherwise, check for a unique link
38+
shuttles = self._get_shuttles(location)
39+
if len(shuttles) == 1:
40+
# Pass the shuttle_id to the location method via context
41+
location.with_context(shuttle_id=shuttles.id).fetch_vertical_lift_tray()
42+
return {"type": "ir.actions.client", "tag": "soft_reload"}
43+
44+
# If shared (len > 1) or no link (len == 0), open shuttle selector
45+
return self._open_shuttle_selector(location, "fetch_vertical_lift_tray_source")
2146

2247
def fetch_vertical_lift_tray_dest(self):
2348
self.ensure_one()
24-
self.location_dest_id.fetch_vertical_lift_tray()
25-
return {"type": "ir.actions.client", "tag": "soft_reload"}
49+
location = self.location_dest_id
50+
51+
# If shuttle is explicitly provided in context (e.g. from wizard confirm)
52+
if self.env.context.get("shuttle_id"):
53+
location.fetch_vertical_lift_tray()
54+
return {"type": "ir.actions.client", "tag": "soft_reload"}
55+
56+
# Otherwise, check for a unique link
57+
shuttles = self._get_shuttles(location)
58+
if len(shuttles) == 1:
59+
# Pass the shuttle_id to the location method via context
60+
location.with_context(shuttle_id=shuttles.id).fetch_vertical_lift_tray()
61+
return {"type": "ir.actions.client", "tag": "soft_reload"}
62+
63+
# If shared (len > 1) or no link (len == 0), open shuttle selector
64+
return self._open_shuttle_selector(location, "fetch_vertical_lift_tray_dest")
65+
66+
def _open_shuttle_selector(self, location, method_name):
67+
return {
68+
"name": self.env._("Select Shuttle for %s", location.name),
69+
"type": "ir.actions.act_window",
70+
"res_model": "vertical.lift.select.shuttle",
71+
"view_mode": "form",
72+
"target": "new",
73+
"context": {
74+
"default_location_id": location.id,
75+
"default_res_model": self._name,
76+
"default_res_id": self.id,
77+
"default_method_name": method_name,
78+
},
79+
}

stock_vertical_lift/models/vertical_lift_operation_base.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,7 @@ class VerticalLiftOperationBase(models.AbstractModel):
9797
shuttle_id = fields.Many2one(
9898
comodel_name="vertical.lift.shuttle", required=True, readonly=True
9999
)
100-
location_id = fields.Many2one(related="shuttle_id.location_id")
100+
location_id = fields.Many2one(related="shuttle_id.shared_storage_location_id")
101101
number_of_ops = fields.Integer(
102102
compute="_compute_number_of_ops", string="Number of Operations"
103103
)

stock_vertical_lift/models/vertical_lift_operation_inventory.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -210,7 +210,7 @@ def clear_current_inventory_line(self):
210210

211211
def fetch_tray(self):
212212
location = self.quant_id.location_id
213-
location.fetch_vertical_lift_tray()
213+
location.with_context(shuttle_id=self.shuttle_id.id).fetch_vertical_lift_tray()
214214

215215
def select_next_inventory_line(self):
216216
self.ensure_one()

stock_vertical_lift/models/vertical_lift_operation_pick.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,9 @@ def _domain_move_lines_to_do_all(self):
8686
return domain
8787

8888
def fetch_tray(self):
89-
self.current_move_line_id.fetch_vertical_lift_tray_source()
89+
self.current_move_line_id.with_context(
90+
shuttle_id=self.shuttle_id.id
91+
).fetch_vertical_lift_tray_source()
9092

9193
def _get_next_move_line(self, order):
9294
def get_next(move_lines, current_move_line):

stock_vertical_lift/models/vertical_lift_operation_put.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -211,7 +211,9 @@ def _assign_available_cell(self, tray_type):
211211
return False
212212

213213
def fetch_tray(self):
214-
self.current_move_line_id.fetch_vertical_lift_tray_dest()
214+
self.current_move_line_id.with_context(
215+
shuttle_id=self.shuttle_id.id
216+
).fetch_vertical_lift_tray_dest()
215217

216218
def button_release(self):
217219
res = super().button_release()

0 commit comments

Comments
 (0)