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
60 changes: 28 additions & 32 deletions stock_vertical_lift/README.rst
Original file line number Diff line number Diff line change
@@ -1,7 +1,3 @@
.. image:: https://odoo-community.org/readme-banner-image
:target: https://odoo-community.org/get-involved?utm_source=readme
:alt: Odoo Community Association

=============
Vertical Lift
=============
Expand All @@ -17,7 +13,7 @@ Vertical Lift
.. |badge1| image:: https://img.shields.io/badge/maturity-Alpha-red.png
:target: https://odoo-community.org/page/development-status
:alt: Alpha
.. |badge2| image:: https://img.shields.io/badge/license-AGPL--3-blue.png
.. |badge2| image:: https://img.shields.io/badge/licence-AGPL--3-blue.png
:target: http://www.gnu.org/licenses/agpl-3.0-standalone.html
:alt: License: AGPL-3
.. |badge3| image:: https://img.shields.io/badge/github-OCA%2Fstock--logistics--warehouse-lightgray.png?logo=github
Expand Down Expand Up @@ -54,25 +50,25 @@ General

In Inventory Settings, you must have:

- Storage Locations
- Multi-Warehouses
- Multi-Step Routes
- Storage Locations
- Multi-Warehouses
- Multi-Step Routes

Locations
---------

Additional configuration parameters are added in Locations:

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

Tray types
----------
Expand Down Expand Up @@ -129,15 +125,15 @@ The barcodes used are of the type Code 128 (with the code set B).
Known issues / Roadmap
======================

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

Bug Tracker
===========
Expand All @@ -160,22 +156,22 @@ Authors
Contributors
------------

- Guewen Baconnier <[email protected]>
- Guewen Baconnier <[email protected]>

Trobz

- Dung Tran <[email protected]>
- Dung Tran <[email protected]>

- Nhan Tran <[email protected]>
- Nhan Tran <[email protected]>

- Jacques-Etienne Baudoux (BCIM) <[email protected]>
- Jacques-Etienne Baudoux (BCIM) <[email protected]>

Other credits
-------------

The development of this module has been financially supported by:

- Camptocamp
- Camptocamp

Maintainers
-----------
Expand Down
1 change: 1 addition & 0 deletions stock_vertical_lift/__manifest__.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
"views/vertical_lift_operation_inventory_views.xml",
"views/shuttle_screen_templates.xml",
"views/res_config_settings_views.xml",
"wizards/vertical_lift_select_shuttle.xml",
"security/ir.model.access.csv",
"data/ir_sequence.xml",
],
Expand Down
104 changes: 81 additions & 23 deletions stock_vertical_lift/models/stock_location.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,15 +28,15 @@ class StockLocation(models.Model):
# give the unique shuttle for any location in the tree (whether it's a
# shuttle, a tray or a cell)
inverse_vertical_lift_shuttle_ids = fields.One2many(
comodel_name="vertical.lift.shuttle", inverse_name="location_id"
comodel_name="vertical.lift.shuttle", inverse_name="shared_storage_location_id"
)
# compute the unique shuttle for any shuttle, tray or cell location, by
# going through the parents
vertical_lift_shuttle_id = fields.Many2one(
comodel_name="vertical.lift.shuttle",
compute="_compute_vertical_lift_shuttle_id",
recursive=True,
store=True,
store=False,
)

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

@api.depends(
"inverse_vertical_lift_shuttle_ids", "location_id.vertical_lift_shuttle_id"
)
@api.depends("inverse_vertical_lift_shuttle_ids")
@api.depends_context("shuttle_id")
def _compute_vertical_lift_shuttle_id(self):
shuttle_id = self.env.context.get("shuttle_id")
for location in self:
if location.inverse_vertical_lift_shuttle_ids:
# we have a unique constraint on the other side
assert len(location.inverse_vertical_lift_shuttle_ids) == 1
if len(location.inverse_vertical_lift_shuttle_ids) == 1:
shuttle = location.inverse_vertical_lift_shuttle_ids
else:
shuttle = location.location_id.vertical_lift_shuttle_id
shuttle = self.env["vertical.lift.shuttle"].browse(shuttle_id)
location.vertical_lift_shuttle_id = shuttle

def _hardware_vertical_lift_fetch_tray(self, cell_location=None):
payload = self._hardware_vertical_lift_fetch_tray_payload(cell_location)
return self.vertical_lift_shuttle_id._hardware_send_message(payload)
shuttle = self.vertical_lift_shuttle_id
payload = self._hardware_vertical_lift_fetch_tray_payload(
cell_location=cell_location
)
return shuttle._hardware_send_message(payload)

def _hardware_vertical_lift_fetch_tray_payload(self, cell_location=None):
"""Prepare "fetch" message to be sent to the vertical lift hardware
Expand Down Expand Up @@ -107,7 +108,8 @@ def _hardware_vertical_lift_fetch_tray_payload(self, cell_location=None):
Returns a message in bytes, that will be sent through
``VerticalLiftShuttle._hardware_send_message()``.
"""
if self.vertical_lift_shuttle_id.hardware == "simulation":
shuttle = self.vertical_lift_shuttle_id
if shuttle.hardware == "simulation":
message = self.env._("Opening tray %(name)s.", name=self.name)
if cell_location:
from_left, from_bottom = cell_location.tray_cell_center_position()
Expand Down Expand Up @@ -136,13 +138,20 @@ def fetch_vertical_lift_tray(self, cell_location=None):
``_hardware_vertical_lift_fetch_tray()``.
"""
self.ensure_one()
if self.vertical_lift_kind == "cell":
if cell_location:
raise ValueError(
"cell_location cannot be set when the location is a cell."
if self.vertical_lift_kind == "cell" and cell_location:
raise ValueError("cell_location cannot be set when the location is a cell.")
if not (shuttle := self.vertical_lift_shuttle_id):
raise exceptions.UserError(
self.env._(
"Cannot determine which shuttle to use on location %s",
self.name,
)
)
if self.vertical_lift_kind == "cell":
tray = self.location_id
tray.fetch_vertical_lift_tray(cell_location=self)
tray.with_context(shuttle_id=shuttle.id).fetch_vertical_lift_tray(
cell_location=self
)
elif self.vertical_lift_kind == "tray":
self._hardware_vertical_lift_fetch_tray(cell_location=cell_location)
else:
Expand All @@ -154,14 +163,63 @@ def fetch_vertical_lift_tray(self, cell_location=None):
)
return True

def _get_shuttles(self):
self.ensure_one()
# Reached the top of hierarchy without finding a shuttle
if not self:
return self.env["vertical.lift.shuttle"]
# Found a location linked to a shuttle
if shuttles := self.inverse_vertical_lift_shuttle_ids:
return shuttles
# Check the parent location
return self.location_id._get_shuttles()

def button_fetch_vertical_lift_tray(self):
self.ensure_one()
if self.vertical_lift_kind in ("cell", "tray"):
self.fetch_vertical_lift_tray()
return True
if self.vertical_lift_kind not in ("cell", "tray"):
return True

# If shuttle is explicitly provided (e.g. from wizard), use it
if self.vertical_lift_shuttle_id:
return self.fetch_vertical_lift_tray()

# Otherwise, check for a unique link
shuttles = self._get_shuttles()
if len(shuttles) == 1:
return self.with_context(shuttle_id=shuttles.id).fetch_vertical_lift_tray()

# If shared (len > 1) or no link (len == 0), open shuttle selector
return self._open_shuttle_selector("button_fetch_vertical_lift_tray")

def button_release_vertical_lift_tray(self):
self.ensure_one()
if self.vertical_lift_kind:
self.vertical_lift_shuttle_id.release_vertical_lift_tray()
return True
if not self.vertical_lift_kind:
return True

# If shuttle is explicitly provided (e.g. from wizard), use it
if shuttle := self.vertical_lift_shuttle_id:
return shuttle.release_vertical_lift_tray()

# Otherwise, check for a unique link
shuttles = self._get_shuttles()
if len(shuttles) == 1:
return shuttles.release_vertical_lift_tray()

# If shared (len > 1) or no link (len == 0), open shuttle selector
return self._open_shuttle_selector("button_release_vertical_lift_tray")

def _open_shuttle_selector(self, method_name):
self.ensure_one()
return {
"name": self.env._("Select Shuttle for %s", self.name),
"type": "ir.actions.act_window",
"res_model": "vertical.lift.select.shuttle",
"view_mode": "form",
"target": "new",
"context": {
"default_location_id": self.id,
"default_res_model": self._name,
"default_res_id": self.id,
"default_method_name": method_name,
},
}
62 changes: 58 additions & 4 deletions stock_vertical_lift/models/stock_move_line.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,66 @@ class StockMoveLine(models.Model):
"skip its processing.",
)

def _get_shuttles(self, location):
self.ensure_one()
# Reached the top of hierarchy without finding a shuttle
if not location:
return self.env["vertical.lift.shuttle"]
# Found a location linked to a shuttle
if shuttles := location.inverse_vertical_lift_shuttle_ids:
return shuttles
# Check the parent location
return self._get_shuttles(location.location_id)

def fetch_vertical_lift_tray_source(self):
self.ensure_one()
self.location_id.fetch_vertical_lift_tray()
return {"type": "ir.actions.client", "tag": "soft_reload"}
location = self.location_id

# If shuttle is explicitly provided in context (e.g. from wizard confirm)
if self.env.context.get("shuttle_id"):
location.fetch_vertical_lift_tray()
return {"type": "ir.actions.client", "tag": "soft_reload"}

# Otherwise, check for a unique link
shuttles = self._get_shuttles(location)
if len(shuttles) == 1:
# Pass the shuttle_id to the location method via context
location.with_context(shuttle_id=shuttles.id).fetch_vertical_lift_tray()
return {"type": "ir.actions.client", "tag": "soft_reload"}

# If shared (len > 1) or no link (len == 0), open shuttle selector
return self._open_shuttle_selector(location, "fetch_vertical_lift_tray_source")

def fetch_vertical_lift_tray_dest(self):
self.ensure_one()
self.location_dest_id.fetch_vertical_lift_tray()
return {"type": "ir.actions.client", "tag": "soft_reload"}
location = self.location_dest_id

# If shuttle is explicitly provided in context (e.g. from wizard confirm)
if self.env.context.get("shuttle_id"):
location.fetch_vertical_lift_tray()
return {"type": "ir.actions.client", "tag": "soft_reload"}

# Otherwise, check for a unique link
shuttles = self._get_shuttles(location)
if len(shuttles) == 1:
# Pass the shuttle_id to the location method via context
location.with_context(shuttle_id=shuttles.id).fetch_vertical_lift_tray()
return {"type": "ir.actions.client", "tag": "soft_reload"}

# If shared (len > 1) or no link (len == 0), open shuttle selector
return self._open_shuttle_selector(location, "fetch_vertical_lift_tray_dest")

def _open_shuttle_selector(self, location, method_name):
return {
"name": self.env._("Select Shuttle for %s", location.name),
"type": "ir.actions.act_window",
"res_model": "vertical.lift.select.shuttle",
"view_mode": "form",
"target": "new",
"context": {
"default_location_id": location.id,
"default_res_model": self._name,
"default_res_id": self.id,
"default_method_name": method_name,
},
}
2 changes: 1 addition & 1 deletion stock_vertical_lift/models/vertical_lift_operation_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ class VerticalLiftOperationBase(models.AbstractModel):
shuttle_id = fields.Many2one(
comodel_name="vertical.lift.shuttle", required=True, readonly=True
)
location_id = fields.Many2one(related="shuttle_id.location_id")
location_id = fields.Many2one(related="shuttle_id.shared_storage_location_id")
number_of_ops = fields.Integer(
compute="_compute_number_of_ops", string="Number of Operations"
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -210,7 +210,7 @@ def clear_current_inventory_line(self):

def fetch_tray(self):
location = self.quant_id.location_id
location.fetch_vertical_lift_tray()
location.with_context(shuttle_id=self.shuttle_id.id).fetch_vertical_lift_tray()

def select_next_inventory_line(self):
self.ensure_one()
Expand Down
4 changes: 3 additions & 1 deletion stock_vertical_lift/models/vertical_lift_operation_pick.py
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,9 @@ def _domain_move_lines_to_do_all(self):
return domain

def fetch_tray(self):
self.current_move_line_id.fetch_vertical_lift_tray_source()
self.current_move_line_id.with_context(
shuttle_id=self.shuttle_id.id
).fetch_vertical_lift_tray_source()

def _get_next_move_line(self, order):
def get_next(move_lines, current_move_line):
Expand Down
4 changes: 3 additions & 1 deletion stock_vertical_lift/models/vertical_lift_operation_put.py
Original file line number Diff line number Diff line change
Expand Up @@ -211,7 +211,9 @@ def _assign_available_cell(self, tray_type):
return False

def fetch_tray(self):
self.current_move_line_id.fetch_vertical_lift_tray_dest()
self.current_move_line_id.with_context(
shuttle_id=self.shuttle_id.id
).fetch_vertical_lift_tray_dest()

def button_release(self):
res = super().button_release()
Expand Down
Loading