mirror of
https://github.com/OCA/stock-logistics-warehouse.git
synced 2025-01-21 14:27:28 +02:00
Split the shuttle operations in different models/views
Namely, the pick/put/inventory operations are now split in different models. Pick and Put share a model and customize their behavior, which is pretty similar. The inventory operation will have a different view and different workflow. This changes will ease a lot the customization of the different workflows and views.
This commit is contained in:
committed by
Hai Lang
parent
4e00019c7d
commit
4d3ce20810
@@ -26,6 +26,10 @@
|
||||
'views/stock_location_views.xml',
|
||||
'views/stock_move_line_views.xml',
|
||||
'views/vertical_lift_shuttle_views.xml',
|
||||
'views/vertical_lift_operation_base_views.xml',
|
||||
'views/vertical_lift_operation_pick_views.xml',
|
||||
'views/vertical_lift_operation_put_views.xml',
|
||||
'views/vertical_lift_operation_inventory_views.xml',
|
||||
'views/stock_vertical_lift_templates.xml',
|
||||
'views/shuttle_screen_templates.xml',
|
||||
'security/ir.model.access.csv',
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
from . import vertical_lift_shuttle
|
||||
from . import vertical_lift_operation_base
|
||||
from . import vertical_lift_operation_pick
|
||||
from . import vertical_lift_operation_put
|
||||
from . import vertical_lift_operation_inventory
|
||||
from . import stock_location
|
||||
from . import stock_move
|
||||
from . import stock_move_line
|
||||
|
||||
@@ -5,19 +5,24 @@ from odoo import api, models
|
||||
|
||||
|
||||
class StockMove(models.Model):
|
||||
_inherit = 'stock.move'
|
||||
_inherit = "stock.move"
|
||||
|
||||
@api.multi
|
||||
def write(self, vals):
|
||||
result = super().write(vals)
|
||||
if 'state' in vals:
|
||||
if "state" in vals:
|
||||
# We cannot have fields to depends on to invalidate these computed
|
||||
# fields on vertical.lift.shuttle. But we know that when the state
|
||||
# of any move line changes, we can invalidate them as the count of
|
||||
# assigned move lines may change (and we track this in stock.move,
|
||||
# not stock.move.line, becaus the state of the lines is a related
|
||||
# to this one).
|
||||
self.env['vertical.lift.shuttle'].invalidate_cache(
|
||||
['number_of_ops', 'number_of_ops_all']
|
||||
# fields on vertical.lift.operation.*. But we know that when the
|
||||
# state of any move line changes, we can invalidate them as the
|
||||
# count of assigned move lines may change (and we track this in
|
||||
# stock.move, not stock.move.line, because the state of the lines
|
||||
# is a related to this one).
|
||||
models = (
|
||||
"vertical.lift.operation.pick",
|
||||
"vertical.lift.operation.put",
|
||||
)
|
||||
for model in models:
|
||||
self.env[model].invalidate_cache(
|
||||
["number_of_ops", "number_of_ops_all"]
|
||||
)
|
||||
return result
|
||||
|
||||
@@ -10,7 +10,14 @@ class StockQuant(models.Model):
|
||||
def _update_available_quantity(self, *args, **kwargs):
|
||||
result = super()._update_available_quantity(*args, **kwargs)
|
||||
# We cannot have fields to depends on to invalidate this computed
|
||||
# fields on vertical.lift.shuttle. But we know that when the quantity
|
||||
# of quant changes, we can invalidate the field on the shuttles.
|
||||
self.env['vertical.lift.shuttle'].invalidate_cache(['tray_qty'])
|
||||
# fields on vertical.lift.operation.* models. But we know that when the
|
||||
# quantity of quant changes, we can invalidate the field
|
||||
models = (
|
||||
"vertical.lift.operation.pick",
|
||||
"vertical.lift.operation.put",
|
||||
)
|
||||
for model in models:
|
||||
self.env[model].invalidate_cache(
|
||||
["tray_qty"]
|
||||
)
|
||||
return result
|
||||
|
||||
264
stock_vertical_lift/models/vertical_lift_operation_base.py
Normal file
264
stock_vertical_lift/models/vertical_lift_operation_base.py
Normal file
@@ -0,0 +1,264 @@
|
||||
# Copyright 2019 Camptocamp SA
|
||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
||||
|
||||
from odoo import _, api, fields, models
|
||||
from odoo.addons.base_sparse_field.models.fields import Serialized
|
||||
|
||||
|
||||
class VerticalLiftOperationBase(models.AbstractModel):
|
||||
"""Base model for shuttle operations (pick, put, inventory)"""
|
||||
|
||||
_name = "vertical.lift.operation.base"
|
||||
_inherit = "barcodes.barcode_events_mixin"
|
||||
_description = "Vertical Lift Operation - Base"
|
||||
|
||||
name = fields.Char(related="shuttle_id.name", readonly=True)
|
||||
shuttle_id = fields.Many2one(
|
||||
comodel_name="vertical.lift.shuttle", required=True, readonly=True
|
||||
)
|
||||
location_id = fields.Many2one(
|
||||
related="shuttle_id.location_id", readonly=True
|
||||
)
|
||||
mode = fields.Selection(related="shuttle_id.mode", readonly=True)
|
||||
operation_descr = fields.Char(
|
||||
string="Operation", default="...", readonly=True
|
||||
)
|
||||
|
||||
_sql_constraints = [
|
||||
(
|
||||
"shuttle_id_unique",
|
||||
"UNIQUE(shuttle_id)",
|
||||
"One pick can be run at a time for a shuttle.",
|
||||
)
|
||||
]
|
||||
|
||||
def on_barcode_scanned(self, barcode):
|
||||
self.ensure_one()
|
||||
# to implement in sub-classes
|
||||
|
||||
def on_screen_open(self):
|
||||
"""Called when the screen is open
|
||||
|
||||
To implement in sub-classes.
|
||||
"""
|
||||
|
||||
def action_open_screen(self):
|
||||
return self.shuttle_id.action_open_screen()
|
||||
|
||||
def action_menu(self):
|
||||
return self.shuttle_id.action_menu()
|
||||
|
||||
def action_manual_barcode(self):
|
||||
return self.shuttle_id.action_manual_barcode()
|
||||
|
||||
|
||||
class VerticalLiftOperationTransfer(models.AbstractModel):
|
||||
"""Base model for shuttle pick and put operations"""
|
||||
|
||||
_name = "vertical.lift.operation.transfer"
|
||||
_inherit = "vertical.lift.operation.base"
|
||||
_description = "Vertical Lift Operation - Transfer"
|
||||
|
||||
current_move_line_id = fields.Many2one(comodel_name="stock.move.line")
|
||||
|
||||
number_of_ops = fields.Integer(
|
||||
compute="_compute_number_of_ops", string="Number of Operations"
|
||||
)
|
||||
number_of_ops_all = fields.Integer(
|
||||
compute="_compute_number_of_ops_all",
|
||||
string="Number of Operations in all shuttles",
|
||||
)
|
||||
|
||||
tray_location_id = fields.Many2one(
|
||||
comodel_name="stock.location",
|
||||
compute="_compute_tray_data",
|
||||
string="Tray Location",
|
||||
)
|
||||
tray_name = fields.Char(compute="_compute_tray_data", string="Tray Name")
|
||||
tray_type_id = fields.Many2one(
|
||||
comodel_name="stock.location.tray.type",
|
||||
compute="_compute_tray_data",
|
||||
string="Tray Type",
|
||||
)
|
||||
tray_type_code = fields.Char(
|
||||
compute="_compute_tray_data", string="Tray Code"
|
||||
)
|
||||
tray_x = fields.Integer(string="X", compute="_compute_tray_data")
|
||||
tray_y = fields.Integer(string="Y", compute="_compute_tray_data")
|
||||
tray_matrix = Serialized(string="Cells", compute="_compute_tray_data")
|
||||
tray_qty = fields.Float(
|
||||
string="Stock Quantity", compute="_compute_tray_qty"
|
||||
)
|
||||
|
||||
# current operation information
|
||||
picking_id = fields.Many2one(
|
||||
related="current_move_line_id.picking_id", readonly=True
|
||||
)
|
||||
picking_origin = fields.Char(
|
||||
related="current_move_line_id.picking_id.origin", readonly=True
|
||||
)
|
||||
picking_partner_id = fields.Many2one(
|
||||
related="current_move_line_id.picking_id.partner_id", readonly=True
|
||||
)
|
||||
product_id = fields.Many2one(
|
||||
related="current_move_line_id.product_id", readonly=True
|
||||
)
|
||||
product_uom_id = fields.Many2one(
|
||||
related="current_move_line_id.product_uom_id", readonly=True
|
||||
)
|
||||
product_uom_qty = fields.Float(
|
||||
related="current_move_line_id.product_uom_qty", readonly=True
|
||||
)
|
||||
product_packagings = fields.Html(
|
||||
string="Packaging", compute="_compute_product_packagings"
|
||||
)
|
||||
qty_done = fields.Float(
|
||||
related="current_move_line_id.qty_done", readonly=True
|
||||
)
|
||||
lot_id = fields.Many2one(
|
||||
related="current_move_line_id.lot_id", readonly=True
|
||||
)
|
||||
location_dest_id = fields.Many2one(
|
||||
string="Destination",
|
||||
related="current_move_line_id.location_dest_id",
|
||||
readonly=True,
|
||||
)
|
||||
# TODO add a glue addon with product_expiry to add the field
|
||||
|
||||
def on_barcode_scanned(self, barcode):
|
||||
self.ensure_one()
|
||||
self.env.user.notify_info(
|
||||
"Scanned barcode: {}. Not implemented.".format(barcode)
|
||||
)
|
||||
|
||||
@api.depends("current_move_line_id.product_id.packaging_ids")
|
||||
def _compute_product_packagings(self):
|
||||
for record in self:
|
||||
if not record.current_move_line_id:
|
||||
continue
|
||||
product = record.current_move_line_id.product_id
|
||||
values = {
|
||||
"packagings": [
|
||||
{
|
||||
"name": pkg.name,
|
||||
"qty": pkg.qty,
|
||||
"unit": product.uom_id.name,
|
||||
}
|
||||
for pkg in product.packaging_ids
|
||||
]
|
||||
}
|
||||
content = self.env["ir.qweb"].render(
|
||||
"stock_vertical_lift.packagings", values
|
||||
)
|
||||
record.product_packagings = content
|
||||
|
||||
@api.depends()
|
||||
def _compute_number_of_ops(self):
|
||||
for record in self:
|
||||
record.number_of_ops = record.count_move_lines_to_do()
|
||||
|
||||
@api.depends()
|
||||
def _compute_number_of_ops_all(self):
|
||||
for record in self:
|
||||
record.number_of_ops_all = record.count_move_lines_to_do_all()
|
||||
|
||||
@api.depends("tray_location_id", "current_move_line_id.product_id")
|
||||
def _compute_tray_qty(self):
|
||||
for record in self:
|
||||
if not (record.tray_location_id and record.current_move_line_id):
|
||||
continue
|
||||
product = record.current_move_line_id.product_id
|
||||
quants = self.env["stock.quant"].search(
|
||||
[
|
||||
("location_id", "=", record.tray_location_id.id),
|
||||
("product_id", "=", product.id),
|
||||
]
|
||||
)
|
||||
record.tray_qty = sum(quants.mapped("quantity"))
|
||||
|
||||
# depends of the quantity so we can't have all triggers
|
||||
@api.depends("current_move_line_id")
|
||||
def _compute_tray_data(self):
|
||||
for record in self:
|
||||
modes = {"pick": "location_id", "put": "location_dest_id"}
|
||||
location = record.current_move_line_id[modes[record.mode]]
|
||||
tray_type = location.location_id.tray_type_id
|
||||
# this is the current cell
|
||||
record.tray_location_id = location.id
|
||||
# name of the tray where the cell is
|
||||
record.tray_name = location.location_id.name
|
||||
record.tray_type_id = tray_type.id
|
||||
record.tray_type_code = tray_type.code
|
||||
record.tray_x = location.posx
|
||||
record.tray_y = location.posy
|
||||
record.tray_matrix = location.tray_matrix
|
||||
|
||||
def _domain_move_lines_to_do(self):
|
||||
# to implement in sub-classes
|
||||
return [("id", "=", 0)]
|
||||
|
||||
def _domain_move_lines_to_do_all(self):
|
||||
# to implement in sub-classes
|
||||
return [("id", "=", 0)]
|
||||
|
||||
def count_move_lines_to_do(self):
|
||||
"""Count move lines to process in current shuttles"""
|
||||
self.ensure_one()
|
||||
return self.env["stock.move.line"].search_count(
|
||||
self._domain_move_lines_to_do()
|
||||
)
|
||||
|
||||
def count_move_lines_to_do_all(self):
|
||||
"""Count move lines to process in all shuttles"""
|
||||
self.ensure_one()
|
||||
return self.env["stock.move.line"].search_count(
|
||||
self._domain_move_lines_to_do_all()
|
||||
)
|
||||
|
||||
def on_screen_open(self):
|
||||
"""Called when the screen is open"""
|
||||
self.select_next_move_line()
|
||||
|
||||
def button_release(self):
|
||||
"""Release the operation, go to the next"""
|
||||
self.select_next_move_line()
|
||||
if not self.current_move_line_id:
|
||||
# sorry not sorry
|
||||
return {
|
||||
"effect": {
|
||||
"fadeout": "slow",
|
||||
"message": _("Congrats, you cleared the queue!"),
|
||||
"img_url": "/web/static/src/img/smile.svg",
|
||||
"type": "rainbow_man",
|
||||
}
|
||||
}
|
||||
|
||||
def process_current(self):
|
||||
raise NotImplementedError
|
||||
|
||||
def button_save(self):
|
||||
if not (self and self.current_move_line_id):
|
||||
return
|
||||
self.ensure_one()
|
||||
self.process_current()
|
||||
self.operation_descr = _("Release")
|
||||
|
||||
def fetch_tray(self):
|
||||
return
|
||||
|
||||
def select_next_move_line(self):
|
||||
self.ensure_one()
|
||||
next_move_line = self.env["stock.move.line"].search(
|
||||
self._domain_move_lines_to_do(), limit=1
|
||||
)
|
||||
self.current_move_line_id = next_move_line
|
||||
# TODO use a state machine to define next steps and
|
||||
# description?
|
||||
descr = (
|
||||
_("Scan New Destination Location")
|
||||
if next_move_line
|
||||
else _("No operations")
|
||||
)
|
||||
self.operation_descr = descr
|
||||
if next_move_line:
|
||||
self.fetch_tray()
|
||||
@@ -0,0 +1,10 @@
|
||||
# Copyright 2019 Camptocamp SA
|
||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
||||
|
||||
from odoo import models
|
||||
|
||||
|
||||
class VerticalLiftOperationInventory(models.Model):
|
||||
_name = 'vertical.lift.operation.inventory'
|
||||
_inherit = 'vertical.lift.operation.base'
|
||||
_description = 'Vertical Lift Operation Inventory'
|
||||
53
stock_vertical_lift/models/vertical_lift_operation_pick.py
Normal file
53
stock_vertical_lift/models/vertical_lift_operation_pick.py
Normal file
@@ -0,0 +1,53 @@
|
||||
# Copyright 2019 Camptocamp SA
|
||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
||||
|
||||
from odoo import _, models
|
||||
|
||||
|
||||
class VerticalLiftOperationPick(models.Model):
|
||||
_name = "vertical.lift.operation.pick"
|
||||
_inherit = "vertical.lift.operation.transfer"
|
||||
_description = "Vertical Lift Operation Pick"
|
||||
|
||||
def on_barcode_scanned(self, barcode):
|
||||
self.ensure_one()
|
||||
location = self.env["stock.location"].search(
|
||||
[("barcode", "=", barcode)]
|
||||
)
|
||||
if location:
|
||||
self.current_move_line_id.location_dest_id = location
|
||||
self.operation_descr = _("Save")
|
||||
else:
|
||||
self.env.user.notify_warning(
|
||||
_("No location found for barcode {}").format(barcode)
|
||||
)
|
||||
|
||||
def _domain_move_lines_to_do(self):
|
||||
# TODO check domain
|
||||
domain = [
|
||||
("state", "=", "assigned"),
|
||||
("location_id", "child_of", self.location_id.id),
|
||||
]
|
||||
return domain
|
||||
|
||||
def _domain_move_lines_to_do_all(self):
|
||||
shuttle_locations = self.env["stock.location"].search(
|
||||
[("vertical_lift_kind", "=", "view")]
|
||||
)
|
||||
# TODO check domain
|
||||
domain = [
|
||||
("state", "=", "assigned"),
|
||||
("location_id", "child_of", shuttle_locations.ids),
|
||||
]
|
||||
return domain
|
||||
|
||||
def fetch_tray(self):
|
||||
self.current_move_line_id.fetch_vertical_lift_tray_source()
|
||||
|
||||
def process_current(self):
|
||||
# test code, TODO the smart one
|
||||
# (scan of barcode increments qty, save calls action_done?)
|
||||
line = self.current_move_line_id
|
||||
if line.state != "done":
|
||||
line.qty_done = line.product_qty
|
||||
line.move_id._action_done()
|
||||
35
stock_vertical_lift/models/vertical_lift_operation_put.py
Normal file
35
stock_vertical_lift/models/vertical_lift_operation_put.py
Normal file
@@ -0,0 +1,35 @@
|
||||
# Copyright 2019 Camptocamp SA
|
||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
||||
|
||||
from odoo import _, exceptions, models
|
||||
|
||||
|
||||
class VerticalLiftOperationPut(models.Model):
|
||||
_name = "vertical.lift.operation.put"
|
||||
_inherit = "vertical.lift.operation.transfer"
|
||||
_description = "Vertical Lift Operation Put"
|
||||
|
||||
def _domain_move_lines_to_do(self):
|
||||
# TODO check domain
|
||||
domain = [
|
||||
("state", "=", "assigned"),
|
||||
("location_dest_id", "child_of", self.location_id.id),
|
||||
]
|
||||
return domain
|
||||
|
||||
def _domain_move_lines_to_do_all(self):
|
||||
shuttle_locations = self.env["stock.location"].search(
|
||||
[("vertical_lift_kind", "=", "view")]
|
||||
)
|
||||
domain = [
|
||||
# TODO check state
|
||||
("state", "=", "assigned"),
|
||||
("location_dest_id", "child_of", shuttle_locations.ids),
|
||||
]
|
||||
return domain
|
||||
|
||||
def fetch_tray(self):
|
||||
self.current_move_line_id.fetch_vertical_lift_tray_dest()
|
||||
|
||||
def process_current(self):
|
||||
raise exceptions.UserError(_("Put workflow not implemented"))
|
||||
@@ -1,384 +1,141 @@
|
||||
# Copyright 2019 Camptocamp SA
|
||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
||||
|
||||
from odoo import _, api, exceptions, fields, models
|
||||
from odoo.addons.base_sparse_field.models.fields import Serialized
|
||||
from odoo import _, api, fields, models
|
||||
|
||||
|
||||
class VerticalLiftShuttle(models.Model):
|
||||
_name = 'vertical.lift.shuttle'
|
||||
_inherit = 'barcodes.barcode_events_mixin'
|
||||
_description = 'Vertical Lift Shuttle'
|
||||
_name = "vertical.lift.shuttle"
|
||||
_inherit = "barcodes.barcode_events_mixin"
|
||||
_description = "Vertical Lift Shuttle"
|
||||
|
||||
name = fields.Char()
|
||||
mode = fields.Selection(
|
||||
[('pick', 'Pick'), ('put', 'Put'), ('inventory', 'Inventory')],
|
||||
default='pick',
|
||||
[("pick", "Pick"), ("put", "Put"), ("inventory", "Inventory")],
|
||||
default="pick",
|
||||
required=True,
|
||||
)
|
||||
location_id = fields.Many2one(
|
||||
comodel_name='stock.location',
|
||||
comodel_name="stock.location",
|
||||
required=True,
|
||||
domain="[('vertical_lift_kind', '=', 'shuttle')]",
|
||||
ondelete='restrict',
|
||||
ondelete="restrict",
|
||||
help="The Shuttle source location for Pick operations "
|
||||
"and destination location for Put operations.",
|
||||
)
|
||||
hardware = fields.Selection(
|
||||
selection='_selection_hardware', default='simulation', required=True
|
||||
)
|
||||
current_move_line_id = fields.Many2one(comodel_name='stock.move.line')
|
||||
|
||||
number_of_ops = fields.Integer(
|
||||
compute='_compute_number_of_ops', string='Number of Operations'
|
||||
)
|
||||
number_of_ops_all = fields.Integer(
|
||||
compute='_compute_number_of_ops_all',
|
||||
string='Number of Operations in all shuttles',
|
||||
)
|
||||
|
||||
operation_descr = fields.Char(
|
||||
string="Operation",
|
||||
default="Scan New Destination Location",
|
||||
readonly=True,
|
||||
)
|
||||
|
||||
# tray information (will come from stock.location or a new tray model)
|
||||
tray_location_id = fields.Many2one(
|
||||
comodel_name='stock.location',
|
||||
compute='_compute_tray_matrix',
|
||||
string='Tray Location',
|
||||
)
|
||||
tray_name = fields.Char(compute='_compute_tray_matrix', string='Tray Name')
|
||||
tray_type_id = fields.Many2one(
|
||||
comodel_name='stock.location.tray.type',
|
||||
compute='_compute_tray_matrix',
|
||||
string='Tray Type',
|
||||
)
|
||||
tray_type_code = fields.Char(
|
||||
compute='_compute_tray_matrix', string='Tray Code'
|
||||
)
|
||||
tray_x = fields.Integer(string='X', compute='_compute_tray_matrix')
|
||||
tray_y = fields.Integer(string='Y', compute='_compute_tray_matrix')
|
||||
tray_matrix = Serialized(string='Cells', compute='_compute_tray_matrix')
|
||||
tray_qty = fields.Float(
|
||||
string='Stock Quantity', compute='_compute_tray_qty'
|
||||
)
|
||||
|
||||
# current operation information
|
||||
picking_id = fields.Many2one(
|
||||
related='current_move_line_id.picking_id', readonly=True
|
||||
)
|
||||
picking_origin = fields.Char(
|
||||
related='current_move_line_id.picking_id.origin', readonly=True
|
||||
)
|
||||
picking_partner_id = fields.Many2one(
|
||||
related='current_move_line_id.picking_id.partner_id', readonly=True
|
||||
)
|
||||
product_id = fields.Many2one(
|
||||
related='current_move_line_id.product_id', readonly=True
|
||||
)
|
||||
product_uom_id = fields.Many2one(
|
||||
related='current_move_line_id.product_uom_id', readonly=True
|
||||
)
|
||||
product_uom_qty = fields.Float(
|
||||
related='current_move_line_id.product_uom_qty', readonly=True
|
||||
)
|
||||
product_packagings = fields.Html(
|
||||
string='Packaging', compute='_compute_product_packagings'
|
||||
)
|
||||
qty_done = fields.Float(
|
||||
related='current_move_line_id.qty_done', readonly=True
|
||||
)
|
||||
lot_id = fields.Many2one(
|
||||
related='current_move_line_id.lot_id', readonly=True
|
||||
)
|
||||
location_dest_id = fields.Many2one(
|
||||
string="Destination",
|
||||
related='current_move_line_id.location_dest_id',
|
||||
readonly=True,
|
||||
)
|
||||
|
||||
# TODO add a glue addon with product_expiry to add the field
|
||||
|
||||
_barcode_scanned = fields.Char(
|
||||
"Barcode Scanned",
|
||||
help="Value of the last barcode scanned.",
|
||||
store=False,
|
||||
selection="_selection_hardware", default="simulation", required=True
|
||||
)
|
||||
|
||||
_sql_constraints = [
|
||||
('location_id_unique', 'UNIQUE(location_id)',
|
||||
'You cannot have two shuttles using the same location.'),
|
||||
]
|
||||
|
||||
def on_barcode_scanned(self, barcode):
|
||||
self.ensure_one()
|
||||
# FIXME notify_info is only for the demo
|
||||
self.env.user.notify_info('Scanned barcode: {}'.format(barcode))
|
||||
method = 'on_barcode_scanned_{}'.format(self.mode)
|
||||
getattr(self, method)(barcode)
|
||||
|
||||
def on_barcode_scanned_pick(self, barcode):
|
||||
location = self.env['stock.location'].search(
|
||||
[('barcode', '=', barcode)]
|
||||
(
|
||||
"location_id_unique",
|
||||
"UNIQUE(location_id)",
|
||||
"You cannot have two shuttles using the same location.",
|
||||
)
|
||||
if location:
|
||||
self.current_move_line_id.location_dest_id = location
|
||||
self.operation_descr = _('Save')
|
||||
else:
|
||||
self.env.user.notify_warning(
|
||||
_('No location found for barcode {}').format(barcode)
|
||||
)
|
||||
|
||||
def on_barcode_scanned_put(self, barcode):
|
||||
pass
|
||||
|
||||
def on_barcode_scanned_inventory(self, barcode):
|
||||
pass
|
||||
]
|
||||
|
||||
@api.model
|
||||
def _selection_hardware(self):
|
||||
return [('simulation', 'Simulation')]
|
||||
return [("simulation", "Simulation")]
|
||||
|
||||
@api.depends('current_move_line_id.product_id.packaging_ids')
|
||||
def _compute_product_packagings(self):
|
||||
for record in self:
|
||||
if not record.current_move_line_id:
|
||||
continue
|
||||
product = record.current_move_line_id.product_id
|
||||
values = {
|
||||
'packagings': [
|
||||
{
|
||||
'name': pkg.name,
|
||||
'qty': pkg.qty,
|
||||
'unit': product.uom_id.name,
|
||||
}
|
||||
for pkg in product.packaging_ids
|
||||
]
|
||||
}
|
||||
content = self.env['ir.qweb'].render(
|
||||
'stock_vertical_lift.packagings', values
|
||||
)
|
||||
record.product_packagings = content
|
||||
|
||||
@api.depends()
|
||||
def _compute_number_of_ops(self):
|
||||
for record in self:
|
||||
record.number_of_ops = record.count_move_lines_to_do()
|
||||
|
||||
@api.depends()
|
||||
def _compute_number_of_ops_all(self):
|
||||
for record in self:
|
||||
record.number_of_ops_all = record.count_move_lines_to_do_all()
|
||||
|
||||
@api.depends('tray_location_id', 'current_move_line_id.product_id')
|
||||
def _compute_tray_qty(self):
|
||||
for record in self:
|
||||
if not (record.tray_location_id and record.current_move_line_id):
|
||||
continue
|
||||
product = record.current_move_line_id.product_id
|
||||
quants = self.env['stock.quant'].search(
|
||||
[
|
||||
('location_id', '=', record.tray_location_id.id),
|
||||
('product_id', '=', product.id),
|
||||
]
|
||||
)
|
||||
record.tray_qty = sum(quants.mapped('quantity'))
|
||||
|
||||
@api.depends()
|
||||
def _compute_tray_matrix(self):
|
||||
for record in self:
|
||||
modes = {
|
||||
'pick': 'location_id',
|
||||
'put': 'location_dest_id',
|
||||
# TODO what to do for inventory?
|
||||
'inventory': 'location_id',
|
||||
}
|
||||
location = record.current_move_line_id[modes[record.mode]]
|
||||
tray_type = location.location_id.tray_type_id
|
||||
selected = []
|
||||
cells = []
|
||||
if location:
|
||||
selected = location._tray_cell_coords()
|
||||
cells = location._tray_cell_matrix()
|
||||
|
||||
# this is the current cell
|
||||
record.tray_location_id = location.id
|
||||
# name of the tray where the cell is
|
||||
record.tray_name = location.location_id.name
|
||||
record.tray_type_id = tray_type.id
|
||||
record.tray_type_code = tray_type.code
|
||||
record.tray_x = location.posx
|
||||
record.tray_y = location.posy
|
||||
record.tray_matrix = {
|
||||
# x, y: position of the selected cell
|
||||
'selected': selected,
|
||||
# 0 is empty, 1 is not
|
||||
'cells': cells,
|
||||
}
|
||||
|
||||
def _domain_move_lines_to_do(self):
|
||||
domain = [
|
||||
# TODO check state
|
||||
('state', '=', 'assigned')
|
||||
]
|
||||
domain_extensions = {
|
||||
'pick': [('location_id', 'child_of', self.location_id.id)],
|
||||
# TODO ensure that we cannot have the same ml in 2 shuttles (cannot
|
||||
# happen with 'pick' as they are in the shuttle's location)
|
||||
'put': [('location_dest_id', 'child_of', self.location_id.id)],
|
||||
# TODO
|
||||
'inventory': [('id', '=', 0)],
|
||||
@property
|
||||
def _model_for_mode(self):
|
||||
return {
|
||||
"pick": "vertical.lift.operation.pick",
|
||||
"put": "vertical.lift.operation.put",
|
||||
"inventory": "vertical.lift.operation.inventory",
|
||||
}
|
||||
return domain + domain_extensions[self.mode]
|
||||
|
||||
def _domain_move_lines_to_do_all(self):
|
||||
domain = [
|
||||
# TODO check state
|
||||
('state', '=', 'assigned')
|
||||
]
|
||||
# TODO search only in the view being a parent of shuttle's location
|
||||
shuttle_locations = self.env['stock.location'].search(
|
||||
[('vertical_lift_kind', '=', 'view')]
|
||||
)
|
||||
domain_extensions = {
|
||||
'pick': [('location_id', 'child_of', shuttle_locations.ids)],
|
||||
'put': [('location_dest_id', 'child_of', shuttle_locations.ids)],
|
||||
# TODO
|
||||
'inventory': [('id', '=', 0)],
|
||||
@property
|
||||
def _screen_view_for_mode(self):
|
||||
return {
|
||||
"pick": (
|
||||
"stock_vertical_lift."
|
||||
"vertical_lift_operation_pick_screen_view"
|
||||
),
|
||||
"put": (
|
||||
"stock_vertical_lift."
|
||||
"vertical_lift_operation_put_screen_view"
|
||||
),
|
||||
"inventory": (
|
||||
"stock_vertical_lift."
|
||||
"vertical_lift_operation_inventory_screen_view"
|
||||
),
|
||||
}
|
||||
return domain + domain_extensions[self.mode]
|
||||
|
||||
def count_move_lines_to_do(self):
|
||||
self.ensure_one()
|
||||
return self.env['stock.move.line'].search_count(
|
||||
self._domain_move_lines_to_do()
|
||||
)
|
||||
|
||||
def count_move_lines_to_do_all(self):
|
||||
self.ensure_one()
|
||||
return self.env['stock.move.line'].search_count(
|
||||
self._domain_move_lines_to_do_all()
|
||||
)
|
||||
|
||||
def button_release(self):
|
||||
self.select_next_move_line()
|
||||
if not self.current_move_line_id:
|
||||
# sorry not sorry
|
||||
return {
|
||||
'effect': {
|
||||
'fadeout': 'slow',
|
||||
'message': _('Congrats, you cleared the queue!'),
|
||||
'img_url': '/web/static/src/img/smile.svg',
|
||||
'type': 'rainbow_man',
|
||||
}
|
||||
}
|
||||
|
||||
def process_current_pick(self):
|
||||
# test code, TODO the smart one
|
||||
# (scan of barcode increments qty, save calls action_done?)
|
||||
line = self.current_move_line_id
|
||||
if line.state != 'done':
|
||||
line.qty_done = line.product_qty
|
||||
line.move_id._action_done()
|
||||
|
||||
def process_current_put(self):
|
||||
raise exceptions.UserError(_('Put workflow not implemented'))
|
||||
|
||||
def process_current_inventory(self):
|
||||
raise exceptions.UserError(_('Inventory workflow not implemented'))
|
||||
|
||||
def button_save(self):
|
||||
if not (self and self.current_move_line_id):
|
||||
return
|
||||
self.ensure_one()
|
||||
method = 'process_current_{}'.format(self.mode)
|
||||
getattr(self, method)()
|
||||
self.operation_descr = _('Release')
|
||||
|
||||
def select_next_move_line(self):
|
||||
self.ensure_one()
|
||||
next_move_line = self.env['stock.move.line'].search(
|
||||
self._domain_move_lines_to_do(), limit=1
|
||||
)
|
||||
self.current_move_line_id = next_move_line
|
||||
# TODO use a state machine to define next steps and
|
||||
# description?
|
||||
descr = (
|
||||
_('Scan New Destination Location')
|
||||
if next_move_line
|
||||
else _('No operations')
|
||||
)
|
||||
self.operation_descr = descr
|
||||
if next_move_line:
|
||||
# TODO different method (source vs dest) on pick/put scenario
|
||||
next_move_line.fetch_vertical_lift_tray_source()
|
||||
def _operation_for_mode(self):
|
||||
model = self._model_for_mode[self.mode]
|
||||
record = self.env[model].search([("shuttle_id", "=", self.id)])
|
||||
if not record:
|
||||
record = self.env[model].create({"shuttle_id": self.id})
|
||||
return record
|
||||
|
||||
def action_open_screen(self):
|
||||
self.select_next_move_line()
|
||||
self.ensure_one()
|
||||
screen_xmlid = (
|
||||
'stock_vertical_lift.vertical_lift_shuttle_view_form_screen'
|
||||
)
|
||||
assert self.mode in ("pick", "put", "inventory")
|
||||
screen_xmlid = self._screen_view_for_mode[self.mode]
|
||||
operation = self._operation_for_mode()
|
||||
operation.on_screen_open()
|
||||
return {
|
||||
'type': 'ir.actions.act_window',
|
||||
'res_model': self._name,
|
||||
'views': [[self.env.ref(screen_xmlid).id, 'form']],
|
||||
'res_id': self.id,
|
||||
'target': 'fullscreen',
|
||||
'flags': {
|
||||
'headless': True,
|
||||
'form_view_initial_mode': 'edit',
|
||||
'no_breadcrumbs': True,
|
||||
"type": "ir.actions.act_window",
|
||||
"res_model": operation._name,
|
||||
"views": [[self.env.ref(screen_xmlid).id, "form"]],
|
||||
"res_id": operation.id,
|
||||
"target": "fullscreen",
|
||||
"flags": {
|
||||
"headless": True,
|
||||
"form_view_initial_mode": "edit",
|
||||
"no_breadcrumbs": True,
|
||||
},
|
||||
}
|
||||
|
||||
def action_menu(self):
|
||||
menu_xmlid = 'stock_vertical_lift.vertical_lift_shuttle_form_menu'
|
||||
menu_xmlid = "stock_vertical_lift.vertical_lift_shuttle_form_menu"
|
||||
return {
|
||||
'type': 'ir.actions.act_window',
|
||||
'res_model': 'vertical.lift.shuttle',
|
||||
'views': [[self.env.ref(menu_xmlid).id, 'form']],
|
||||
'name': _('Menu'),
|
||||
'target': 'new',
|
||||
'res_id': self.id,
|
||||
"type": "ir.actions.act_window",
|
||||
"res_model": "vertical.lift.shuttle",
|
||||
"views": [[self.env.ref(menu_xmlid).id, "form"]],
|
||||
"name": _("Menu"),
|
||||
"target": "new",
|
||||
"res_id": self.id,
|
||||
}
|
||||
|
||||
def action_manual_barcode(self):
|
||||
return {
|
||||
'type': 'ir.actions.act_window',
|
||||
'res_model': 'vertical.lift.shuttle.manual.barcode',
|
||||
'view_mode': 'form',
|
||||
'name': _('Barcode'),
|
||||
'target': 'new',
|
||||
"type": "ir.actions.act_window",
|
||||
"res_model": "vertical.lift.shuttle.manual.barcode",
|
||||
"view_mode": "form",
|
||||
"name": _("Barcode"),
|
||||
"target": "new",
|
||||
}
|
||||
|
||||
# TODO: should the mode be changed on all the shuttles at the same time?
|
||||
def switch_pick(self):
|
||||
self.mode = 'pick'
|
||||
self.select_next_move_line()
|
||||
self.mode = "pick"
|
||||
return self.action_open_screen()
|
||||
|
||||
def switch_put(self):
|
||||
self.mode = 'put'
|
||||
self.select_next_move_line()
|
||||
self.mode = "put"
|
||||
return self.action_open_screen()
|
||||
|
||||
def switch_inventory(self):
|
||||
self.mode = 'inventory'
|
||||
self.select_next_move_line()
|
||||
self.mode = "inventory"
|
||||
return self.action_open_screen()
|
||||
|
||||
|
||||
class VerticalLiftShuttleManualBarcode(models.TransientModel):
|
||||
_name = 'vertical.lift.shuttle.manual.barcode'
|
||||
_description = 'Action to input a barcode'
|
||||
_name = "vertical.lift.shuttle.manual.barcode"
|
||||
_description = "Action to input a barcode"
|
||||
|
||||
barcode = fields.Char(string="Barcode")
|
||||
|
||||
@api.multi
|
||||
def button_save(self):
|
||||
shuttle_id = self.env.context.get('active_id')
|
||||
shuttle = self.env['vertical.lift.shuttle'].browse(shuttle_id).exists()
|
||||
if not shuttle:
|
||||
active_id = self.env.context.get("active_id")
|
||||
model = self.env.context.get("active_model")
|
||||
record = self.env[model].browse(active_id).exists()
|
||||
if not record:
|
||||
return
|
||||
if self.barcode:
|
||||
shuttle.on_barcode_scanned(self.barcode)
|
||||
record.on_barcode_scanned(self.barcode)
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
|
||||
access_vertical_lift_shuttle_stock_user,access_vertical_lift_shuttle stock user,model_vertical_lift_shuttle,stock.group_stock_user,1,0,0,0
|
||||
access_vertical_lift_shuttle_manager,access_vertical_lift_shuttle stock manager,model_vertical_lift_shuttle,stock.group_stock_manager,1,1,1,1
|
||||
access_vertical_lift_operation_pick_stock_user,access_vertical_lift_operation_pick stock user,model_vertical_lift_operation_pick,stock.group_stock_user,1,1,1,1
|
||||
access_vertical_lift_operation_put_stock_user,access_vertical_lift_operation_put stock user,model_vertical_lift_operation_put,stock.group_stock_user,1,1,1,1
|
||||
access_vertical_lift_operation_inventory_stock_user,access_vertical_lift_operation_inventory stock user,model_vertical_lift_operation_inventory,stock.group_stock_user,1,1,1,1
|
||||
|
||||
|
@@ -13,7 +13,7 @@ class TestVerticalLiftTrayType(VerticalLiftCase):
|
||||
def setUpClass(cls):
|
||||
super().setUpClass()
|
||||
cls.picking_out = cls.env.ref(
|
||||
'stock_vertical_lift.stock_picking_out_demo_vertical_lift_1'
|
||||
"stock_vertical_lift.stock_picking_out_demo_vertical_lift_1"
|
||||
)
|
||||
# we have a move line to pick created by demo picking
|
||||
# stock_picking_out_demo_vertical_lift_1
|
||||
@@ -21,114 +21,112 @@ class TestVerticalLiftTrayType(VerticalLiftCase):
|
||||
|
||||
def test_switch_pick(self):
|
||||
self.shuttle.switch_pick()
|
||||
self.assertEqual(self.shuttle.mode, 'pick')
|
||||
self.assertEqual(self.shuttle.current_move_line_id, self.out_move_line)
|
||||
self.assertEqual(self.shuttle.mode, "pick")
|
||||
self.assertEqual(
|
||||
self.shuttle._operation_for_mode().current_move_line_id,
|
||||
self.out_move_line,
|
||||
)
|
||||
|
||||
def test_switch_put(self):
|
||||
self.shuttle.switch_put()
|
||||
self.assertEqual(self.shuttle.mode, 'put')
|
||||
self.assertEqual(self.shuttle.mode, "put")
|
||||
# TODO check that we have an incoming move when switching
|
||||
self.assertEqual(
|
||||
self.shuttle.current_move_line_id,
|
||||
self.env['stock.move.line'].browse(),
|
||||
self.shuttle._operation_for_mode().current_move_line_id,
|
||||
self.env["stock.move.line"].browse(),
|
||||
)
|
||||
|
||||
def test_switch_inventory(self):
|
||||
self.shuttle.switch_inventory()
|
||||
self.assertEqual(self.shuttle.mode, 'inventory')
|
||||
# TODO check that we have what we should (what?)
|
||||
self.assertEqual(
|
||||
self.shuttle.current_move_line_id,
|
||||
self.env['stock.move.line'].browse(),
|
||||
)
|
||||
self.assertEqual(self.shuttle.mode, "inventory")
|
||||
|
||||
def test_pick_action_open_screen(self):
|
||||
self.shuttle.switch_pick()
|
||||
action = self.shuttle.action_open_screen()
|
||||
self.assertTrue(self.shuttle.current_move_line_id)
|
||||
self.assertEqual(action['type'], 'ir.actions.act_window')
|
||||
self.assertEqual(action['res_model'], 'vertical.lift.shuttle')
|
||||
self.assertEqual(action['res_id'], self.shuttle.id)
|
||||
operation = self.shuttle._operation_for_mode()
|
||||
self.assertTrue(operation.current_move_line_id)
|
||||
self.assertEqual(action["type"], "ir.actions.act_window")
|
||||
self.assertEqual(action["res_model"], "vertical.lift.operation.pick")
|
||||
self.assertEqual(action["res_id"], operation.id)
|
||||
|
||||
def test_pick_select_next_move_line(self):
|
||||
self.shuttle.switch_pick()
|
||||
self.shuttle.select_next_move_line()
|
||||
self.assertEqual(self.shuttle.current_move_line_id, self.out_move_line)
|
||||
self.shuttle.mode = "pick"
|
||||
operation = self.shuttle._operation_for_mode()
|
||||
operation.select_next_move_line()
|
||||
self.assertEqual(operation.current_move_line_id, self.out_move_line)
|
||||
self.assertEqual(
|
||||
self.shuttle.operation_descr,
|
||||
_('Scan New Destination Location')
|
||||
operation.operation_descr, _("Scan New Destination Location")
|
||||
)
|
||||
|
||||
def test_pick_save(self):
|
||||
self.shuttle.switch_pick()
|
||||
self.shuttle.current_move_line_id = self.out_move_line
|
||||
self.shuttle.button_save()
|
||||
self.assertEqual(
|
||||
self.shuttle.current_move_line_id.state,
|
||||
'done'
|
||||
)
|
||||
self.assertEqual(self.shuttle.operation_descr, _('Release'))
|
||||
operation = self.shuttle._operation_for_mode()
|
||||
operation.current_move_line_id = self.out_move_line
|
||||
operation.button_save()
|
||||
self.assertEqual(operation.current_move_line_id.state, "done")
|
||||
self.assertEqual(operation.operation_descr, _("Release"))
|
||||
|
||||
def test_pick_related_fields(self):
|
||||
self.shuttle.switch_pick()
|
||||
ml = self.shuttle.current_move_line_id = self.out_move_line
|
||||
operation = self.shuttle._operation_for_mode()
|
||||
ml = operation.current_move_line_id = self.out_move_line
|
||||
|
||||
# Trays related fields
|
||||
# For pick, this is the source location, which is the cell where the
|
||||
# product is.
|
||||
self.assertEqual(self.shuttle.tray_location_id, ml.location_id)
|
||||
self.assertEqual(operation.tray_location_id, ml.location_id)
|
||||
self.assertEqual(
|
||||
self.shuttle.tray_name,
|
||||
operation.tray_name,
|
||||
# parent = tray
|
||||
ml.location_id.location_id.name,
|
||||
)
|
||||
self.assertEqual(
|
||||
self.shuttle.tray_type_id,
|
||||
operation.tray_type_id,
|
||||
# the tray type is on the parent of the cell (on the tray)
|
||||
ml.location_id.location_id.tray_type_id,
|
||||
)
|
||||
self.assertEqual(
|
||||
self.shuttle.tray_type_code,
|
||||
operation.tray_type_code,
|
||||
ml.location_id.location_id.tray_type_id.code,
|
||||
)
|
||||
self.assertEqual(self.shuttle.tray_x, ml.location_id.posx)
|
||||
self.assertEqual(self.shuttle.tray_y, ml.location_id.posy)
|
||||
self.assertEqual(operation.tray_x, ml.location_id.posx)
|
||||
self.assertEqual(operation.tray_y, ml.location_id.posy)
|
||||
|
||||
# Move line related fields
|
||||
self.assertEqual(self.shuttle.picking_id, ml.picking_id)
|
||||
self.assertEqual(self.shuttle.picking_origin, ml.picking_id.origin)
|
||||
self.assertEqual(operation.picking_id, ml.picking_id)
|
||||
self.assertEqual(operation.picking_origin, ml.picking_id.origin)
|
||||
self.assertEqual(
|
||||
self.shuttle.picking_partner_id, ml.picking_id.partner_id
|
||||
operation.picking_partner_id, ml.picking_id.partner_id
|
||||
)
|
||||
self.assertEqual(self.shuttle.product_id, ml.product_id)
|
||||
self.assertEqual(self.shuttle.product_uom_id, ml.product_uom_id)
|
||||
self.assertEqual(self.shuttle.product_uom_qty, ml.product_uom_qty)
|
||||
self.assertEqual(self.shuttle.qty_done, ml.qty_done)
|
||||
self.assertEqual(self.shuttle.lot_id, ml.lot_id)
|
||||
self.assertEqual(operation.product_id, ml.product_id)
|
||||
self.assertEqual(operation.product_uom_id, ml.product_uom_id)
|
||||
self.assertEqual(operation.product_uom_qty, ml.product_uom_qty)
|
||||
self.assertEqual(operation.qty_done, ml.qty_done)
|
||||
self.assertEqual(operation.lot_id, ml.lot_id)
|
||||
|
||||
def test_pick_count_move_lines(self):
|
||||
product1 = self.env.ref('stock_vertical_lift.product_running_socks')
|
||||
product2 = self.env.ref('stock_vertical_lift.product_recovery_socks')
|
||||
product1 = self.env.ref("stock_vertical_lift.product_running_socks")
|
||||
product2 = self.env.ref("stock_vertical_lift.product_recovery_socks")
|
||||
# cancel the picking from demo data to start from a clean state
|
||||
self.env.ref(
|
||||
'stock_vertical_lift.stock_picking_out_demo_vertical_lift_1'
|
||||
"stock_vertical_lift.stock_picking_out_demo_vertical_lift_1"
|
||||
).action_cancel()
|
||||
|
||||
# ensure that we have stock in some cells, we'll put product1
|
||||
# in the first Shuttle and product2 in the second
|
||||
cell1 = self.env.ref(
|
||||
'stock_vertical_lift.'
|
||||
'stock_location_vertical_lift_demo_tray_1a_x3y2'
|
||||
"stock_vertical_lift."
|
||||
"stock_location_vertical_lift_demo_tray_1a_x3y2"
|
||||
)
|
||||
self._update_quantity_in_cell(cell1, product1, 50)
|
||||
cell2 = self.env.ref(
|
||||
'stock_vertical_lift.'
|
||||
'stock_location_vertical_lift_demo_tray_2a_x1y1'
|
||||
"stock_vertical_lift."
|
||||
"stock_location_vertical_lift_demo_tray_2a_x1y1"
|
||||
)
|
||||
self._update_quantity_in_cell(cell2, product2, 50)
|
||||
|
||||
# create pickings (we already have an existing one from demo data)
|
||||
pickings = self.env['stock.picking'].browse()
|
||||
pickings = self.env["stock.picking"].browse()
|
||||
pickings |= self._create_simple_picking_out(product1, 1)
|
||||
pickings |= self._create_simple_picking_out(product1, 1)
|
||||
pickings |= self._create_simple_picking_out(product1, 1)
|
||||
@@ -144,41 +142,43 @@ class TestVerticalLiftTrayType(VerticalLiftCase):
|
||||
pickings.action_assign()
|
||||
|
||||
shuttle1 = self.shuttle
|
||||
operation1 = shuttle1._operation_for_mode()
|
||||
shuttle2 = self.env.ref(
|
||||
'stock_vertical_lift.stock_vertical_lift_demo_shuttle_2'
|
||||
"stock_vertical_lift.stock_vertical_lift_demo_shuttle_2"
|
||||
)
|
||||
operation2 = shuttle2._operation_for_mode()
|
||||
|
||||
self.assertEqual(shuttle1.number_of_ops, 4)
|
||||
self.assertEqual(shuttle2.number_of_ops, 2)
|
||||
self.assertEqual(shuttle1.number_of_ops_all, 6)
|
||||
self.assertEqual(shuttle2.number_of_ops_all, 6)
|
||||
self.assertEqual(operation1.number_of_ops, 4)
|
||||
self.assertEqual(operation2.number_of_ops, 2)
|
||||
self.assertEqual(operation1.number_of_ops_all, 6)
|
||||
self.assertEqual(operation2.number_of_ops_all, 6)
|
||||
|
||||
# Process a line, should change the numbers.
|
||||
shuttle1.select_next_move_line()
|
||||
shuttle1.process_current_pick()
|
||||
self.assertEqual(shuttle1.number_of_ops, 3)
|
||||
self.assertEqual(shuttle2.number_of_ops, 2)
|
||||
self.assertEqual(shuttle1.number_of_ops_all, 5)
|
||||
self.assertEqual(shuttle2.number_of_ops_all, 5)
|
||||
operation1.select_next_move_line()
|
||||
operation1.process_current()
|
||||
self.assertEqual(operation1.number_of_ops, 3)
|
||||
self.assertEqual(operation2.number_of_ops, 2)
|
||||
self.assertEqual(operation1.number_of_ops_all, 5)
|
||||
self.assertEqual(operation2.number_of_ops_all, 5)
|
||||
|
||||
# add stock and make the last one assigned to check the number is
|
||||
# updated
|
||||
self._update_quantity_in_cell(cell2, product2, 10)
|
||||
unassigned.action_assign()
|
||||
self.assertEqual(shuttle1.number_of_ops, 3)
|
||||
self.assertEqual(shuttle2.number_of_ops, 3)
|
||||
self.assertEqual(shuttle1.number_of_ops_all, 6)
|
||||
self.assertEqual(shuttle2.number_of_ops_all, 6)
|
||||
self.assertEqual(operation1.number_of_ops, 3)
|
||||
self.assertEqual(operation2.number_of_ops, 3)
|
||||
self.assertEqual(operation1.number_of_ops_all, 6)
|
||||
self.assertEqual(operation2.number_of_ops_all, 6)
|
||||
|
||||
@unittest.skip('Not implemented')
|
||||
@unittest.skip("Not implemented")
|
||||
def test_put_count_move_lines(self):
|
||||
pass
|
||||
|
||||
@unittest.skip('Not implemented')
|
||||
@unittest.skip("Not implemented")
|
||||
def test_inventory_count_move_lines(self):
|
||||
pass
|
||||
|
||||
@unittest.skip('Not implemented')
|
||||
@unittest.skip("Not implemented")
|
||||
def test_on_barcode_scanned(self):
|
||||
# test to implement when the code is implemented
|
||||
pass
|
||||
@@ -188,48 +188,52 @@ class TestVerticalLiftTrayType(VerticalLiftCase):
|
||||
self.out_move_line.qty_done = self.out_move_line.product_qty
|
||||
self.out_move_line.move_id._action_done()
|
||||
# release, no further operation in queue
|
||||
result = self.shuttle.button_release()
|
||||
self.assertFalse(self.shuttle.current_move_line_id)
|
||||
self.assertEqual(self.shuttle.operation_descr, _('No operations'))
|
||||
operation = self.shuttle._operation_for_mode()
|
||||
result = operation.button_release()
|
||||
self.assertFalse(operation.current_move_line_id)
|
||||
self.assertEqual(operation.operation_descr, _("No operations"))
|
||||
expected_result = {
|
||||
'effect': {
|
||||
'fadeout': 'slow',
|
||||
'message': _('Congrats, you cleared the queue!'),
|
||||
'img_url': '/web/static/src/img/smile.svg',
|
||||
'type': 'rainbow_man',
|
||||
"effect": {
|
||||
"fadeout": "slow",
|
||||
"message": _("Congrats, you cleared the queue!"),
|
||||
"img_url": "/web/static/src/img/smile.svg",
|
||||
"type": "rainbow_man",
|
||||
}
|
||||
}
|
||||
self.assertEqual(result, expected_result)
|
||||
|
||||
def test_process_current_pick(self):
|
||||
self.shuttle.switch_pick()
|
||||
self.shuttle.current_move_line_id = self.out_move_line
|
||||
operation = self.shuttle._operation_for_mode()
|
||||
operation.current_move_line_id = self.out_move_line
|
||||
qty_to_process = self.out_move_line.product_qty
|
||||
self.shuttle.process_current_pick()
|
||||
self.assertEqual(self.out_move_line.state, 'done')
|
||||
operation.process_current()
|
||||
self.assertEqual(self.out_move_line.state, "done")
|
||||
self.assertEqual(self.out_move_line.qty_done, qty_to_process)
|
||||
|
||||
def test_process_current_put(self):
|
||||
# test to implement when the code is implemented
|
||||
self.shuttle.switch_put()
|
||||
operation = self.shuttle._operation_for_mode()
|
||||
with self.assertRaises(exceptions.UserError):
|
||||
self.shuttle.process_current_put()
|
||||
operation.process_current()
|
||||
|
||||
def test_process_current_inventory(self):
|
||||
# test to implement when the code is implemented
|
||||
with self.assertRaises(exceptions.UserError):
|
||||
self.shuttle.process_current_inventory()
|
||||
self.shuttle.switch_inventory()
|
||||
|
||||
def test_matrix(self):
|
||||
self.shuttle.switch_pick()
|
||||
self.shuttle.current_move_line_id = self.out_move_line
|
||||
operation = self.shuttle._operation_for_mode()
|
||||
operation.current_move_line_id = self.out_move_line
|
||||
location = self.out_move_line.location_id
|
||||
# offset by -1 because the fields are for humans
|
||||
expected_x = location.posx - 1
|
||||
expected_y = location.posy - 1
|
||||
self.assertEqual(
|
||||
self.shuttle.tray_matrix,
|
||||
operation.tray_matrix,
|
||||
{
|
||||
'selected': [expected_x, expected_y],
|
||||
"selected": [expected_x, expected_y],
|
||||
# fmt: off
|
||||
'cells': [
|
||||
[0, 0, 0, 0, 0, 0, 0, 0],
|
||||
@@ -241,12 +245,13 @@ class TestVerticalLiftTrayType(VerticalLiftCase):
|
||||
|
||||
def test_tray_qty(self):
|
||||
cell = self.env.ref(
|
||||
'stock_vertical_lift.'
|
||||
'stock_location_vertical_lift_demo_tray_1a_x3y2'
|
||||
"stock_vertical_lift."
|
||||
"stock_location_vertical_lift_demo_tray_1a_x3y2"
|
||||
)
|
||||
self.out_move_line.location_id = cell
|
||||
self.shuttle.current_move_line_id = self.out_move_line
|
||||
operation = self.shuttle._operation_for_mode()
|
||||
operation.current_move_line_id = self.out_move_line
|
||||
self._update_quantity_in_cell(cell, self.out_move_line.product_id, 50)
|
||||
self.assertEqual(self.shuttle.tray_qty, 50)
|
||||
self.assertEqual(operation.tray_qty, 50)
|
||||
self._update_quantity_in_cell(cell, self.out_move_line.product_id, -20)
|
||||
self.assertEqual(self.shuttle.tray_qty, 30)
|
||||
self.assertEqual(operation.tray_qty, 30)
|
||||
|
||||
147
stock_vertical_lift/views/vertical_lift_operation_base_views.xml
Normal file
147
stock_vertical_lift/views/vertical_lift_operation_base_views.xml
Normal file
@@ -0,0 +1,147 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
|
||||
<record id="vertical_lift_operation_base_screen_view" model="ir.ui.view">
|
||||
<field name="name">vertical.lift.operation.base.screen.view</field>
|
||||
<field name="model">vertical.lift.operation.base</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Operations" delete="0" create="0" class="o_vlift_shuttle">
|
||||
<div class="o_shuttle_header">
|
||||
<div class="o_shuttle_header_left o_shuttle_header_content">
|
||||
<field name="name" readonly="1"/>
|
||||
</div>
|
||||
<div class="o_shuttle_header_center o_shuttle_header_content">
|
||||
<label for="mode"/>
|
||||
<field name="mode" readonly="1"/>
|
||||
</div>
|
||||
<div class="o_shuttle_header_right o_shuttle_header_content">
|
||||
</div>
|
||||
</div>
|
||||
<div class="o_shuttle_actions">
|
||||
<div class="o_shuttle_content">
|
||||
<button name="action_menu" type="object"
|
||||
class="btn-secondary o_shuttle_icon_btn"
|
||||
string="" icon="fa-bars"
|
||||
aria-label="Dropdown menu" title="Dropdown menu"/>
|
||||
<button name="action_manual_barcode" type="object"
|
||||
class="btn-secondary o_shuttle_icon_btn"
|
||||
string="" icon="fa-terminal"
|
||||
aria-label="Barcode Input" title="Barcode Input"/>
|
||||
</div>
|
||||
<div class="o_shuttle_content o_shuttle_content_right">
|
||||
<div>
|
||||
<!-- will react on barcode 'O-BTN.save' -->
|
||||
<button name="button_save"
|
||||
type="object"
|
||||
string="Save"
|
||||
icon="fa-check"
|
||||
class="btn-primary"
|
||||
barcode_trigger="save"/>
|
||||
<!-- will react on barcode 'O-BTN.release -->
|
||||
<button name="button_release"
|
||||
type="object"
|
||||
string="Release"
|
||||
class="btn-primary"
|
||||
barcode_trigger="release"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="o_shuttle_operation bg-primary jumbotron jumbotron-fluid">
|
||||
<div class="container">
|
||||
<field name="operation_descr"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="o_shuttle_data">
|
||||
</div>
|
||||
<field name="_barcode_scanned" widget="barcode_handler"/>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="vertical_lift_operation_transfer_screen_view" model="ir.ui.view">
|
||||
<field name="name">vertical.lift.operation.transfer.screen.view</field>
|
||||
<field name="model">vertical.lift.operation.transfer</field>
|
||||
<field name="inherit_id" ref="vertical_lift_operation_base_screen_view"/>
|
||||
<field name="mode">primary</field>
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//div[hasclass('o_shuttle_header_right')]" position="inside" >
|
||||
<label for="number_of_ops"/>
|
||||
<field name="number_of_ops" readonly="1"/> /
|
||||
<field name="number_of_ops_all" readonly="1"/>
|
||||
</xpath>
|
||||
<xpath expr="//div[hasclass('o_shuttle_data')]" position="attributes" >
|
||||
<attribute name="attrs" >{'invisible': [('current_move_line_id', '=', False)]}</attribute>
|
||||
</xpath>
|
||||
|
||||
<xpath expr="//div[hasclass('o_shuttle_data')]" position="inside" >
|
||||
<!-- on the left of the screen -->
|
||||
<div class="o_shuttle_data_content o_shuttle_move">
|
||||
<div>
|
||||
<group>
|
||||
<field name="current_move_line_id" invisible="1"/>
|
||||
<label for="picking_id"/>
|
||||
<div>
|
||||
<div>
|
||||
<field name="picking_id" options="{'no_open': True}" class="mr8"/>
|
||||
<span>/</span>
|
||||
<field name="picking_origin" class="oe_inline ml8"/>
|
||||
</div>
|
||||
</div>
|
||||
<label for="picking_partner_id"/>
|
||||
<div>
|
||||
<field name="picking_partner_id" options="{'no_open': True}"/>
|
||||
</div>
|
||||
<!-- TODO change for pick and put? -->
|
||||
<label for="location_dest_id"/>
|
||||
<div>
|
||||
<field name="location_dest_id"
|
||||
class="bg-primary o_shuttle_highlight"
|
||||
options="{'no_open': True}"/>
|
||||
</div>
|
||||
<label for="product_id"/>
|
||||
<div colspan="2" class="oe_title">
|
||||
<h1>
|
||||
<field name="product_id" options="{'no_open': True}"/>
|
||||
</h1>
|
||||
</div>
|
||||
<div colspan="2">
|
||||
<field name="product_packagings"/>
|
||||
</div>
|
||||
<field name="lot_id"/>
|
||||
<label for="product_uom_qty" string="Quantity" class="ml32"/>
|
||||
<div colspan="2" class="ml32">
|
||||
<h1 class="bg-primary o_shuttle_highlight">
|
||||
<field name="product_uom_qty" class="oe_inline text-white"/>
|
||||
<field name="product_uom_id" options="{'no_open': True}" class="oe_inline ml8 text-white"/>
|
||||
</h1>
|
||||
</div>
|
||||
</group>
|
||||
</div>
|
||||
</div>
|
||||
<!-- on the right of the screen -->
|
||||
<div class="o_shuttle_data_content o_shuttle_tray">
|
||||
<group col="1">
|
||||
<field name="tray_name"/>
|
||||
<field name="tray_type_code"/>
|
||||
<field name="tray_y"/>
|
||||
<field name="tray_x"/>
|
||||
<label for="tray_qty"/>
|
||||
<div colspan="2" class="oe_title">
|
||||
<h1>
|
||||
<field name="tray_qty"/>
|
||||
</h1>
|
||||
</div>
|
||||
</group>
|
||||
<group>
|
||||
<div>
|
||||
<field name="tray_matrix"
|
||||
widget="location_tray_matrix"
|
||||
/>
|
||||
</div>
|
||||
</group>
|
||||
</div>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
</odoo>
|
||||
@@ -0,0 +1,19 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
|
||||
<record id="vertical_lift_operation_inventory_screen_view" model="ir.ui.view">
|
||||
<field name="name">vertical.lift.operation.inventory.screen.view</field>
|
||||
<field name="model">vertical.lift.operation.inventory</field>
|
||||
<field name="inherit_id" ref="vertical_lift_operation_base_screen_view"/>
|
||||
<field name="mode">primary</field>
|
||||
<field name="arch" type="xml">
|
||||
<form position="attributes">
|
||||
<attribute name="string">Inventory Screen</attribute>
|
||||
</form>
|
||||
<xpath expr="//div[hasclass('o_shuttle_data')]" position="inside" >
|
||||
<span>Not implemented</span>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
</odoo>
|
||||
@@ -0,0 +1,16 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
|
||||
<record id="vertical_lift_operation_pick_screen_view" model="ir.ui.view">
|
||||
<field name="name">vertical.lift.operation.pick.screen.view</field>
|
||||
<field name="model">vertical.lift.operation.pick</field>
|
||||
<field name="inherit_id" ref="vertical_lift_operation_transfer_screen_view"/>
|
||||
<field name="mode">primary</field>
|
||||
<field name="arch" type="xml">
|
||||
<form position="attributes">
|
||||
<attribute name="string">Pick Screen</attribute>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
</odoo>
|
||||
@@ -0,0 +1,16 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
|
||||
<record id="vertical_lift_operation_put_screen_view" model="ir.ui.view">
|
||||
<field name="name">vertical.lift.operation.put.screen.view</field>
|
||||
<field name="model">vertical.lift.operation.put</field>
|
||||
<field name="inherit_id" ref="vertical_lift_operation_transfer_screen_view"/>
|
||||
<field name="mode">primary</field>
|
||||
<field name="arch" type="xml">
|
||||
<form position="attributes">
|
||||
<attribute name="string">Put Screen</attribute>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
</odoo>
|
||||
@@ -1,133 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
|
||||
<record id="vertical_lift_shuttle_view_form_screen" model="ir.ui.view">
|
||||
<field name="name">vertical.lift.shuttle.view.form.screen</field>
|
||||
<field name="model">vertical.lift.shuttle</field>
|
||||
<field name="priority">99</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Operations" delete="0" create="0" class="o_vlift_shuttle">
|
||||
<!-- <field name="product_id" invisible="1"/> -->
|
||||
<div class="o_shuttle_header">
|
||||
<div class="o_shuttle_header_left o_shuttle_header_content">
|
||||
<field name="name" readonly="1"/>
|
||||
</div>
|
||||
<div class="o_shuttle_header_center o_shuttle_header_content">
|
||||
<label for="mode"/>
|
||||
<field name="mode" readonly="1"/>
|
||||
</div>
|
||||
<div class="o_shuttle_header_right o_shuttle_header_content">
|
||||
<label for="number_of_ops"/>
|
||||
<field name="number_of_ops" readonly="1"/> /
|
||||
<field name="number_of_ops_all" readonly="1"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="o_shuttle_actions">
|
||||
<div class="o_shuttle_content">
|
||||
<button name="action_menu" type="object"
|
||||
class="btn-secondary o_shuttle_icon_btn"
|
||||
string="" icon="fa-bars"
|
||||
aria-label="Dropdown menu" title="Dropdown menu"/>
|
||||
<button name="action_manual_barcode" type="object"
|
||||
class="btn-secondary o_shuttle_icon_btn"
|
||||
string="" icon="fa-terminal"
|
||||
aria-label="Barcode Input" title="Barcode Input"/>
|
||||
</div>
|
||||
<div class="o_shuttle_content o_shuttle_content_right">
|
||||
<div>
|
||||
<!-- will react on barcode 'O-BTN.save' -->
|
||||
<button name="button_save"
|
||||
type="object"
|
||||
string="Save"
|
||||
icon="fa-check"
|
||||
class="btn-primary"
|
||||
barcode_trigger="save"/>
|
||||
<!-- will react on barcode 'O-BTN.release -->
|
||||
<button name="button_release"
|
||||
type="object"
|
||||
string="Release"
|
||||
class="btn-primary"
|
||||
barcode_trigger="release"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="o_shuttle_operation bg-primary jumbotron jumbotron-fluid">
|
||||
<div class="container">
|
||||
<field name="operation_descr"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="o_shuttle_data" attrs="{'invisible': [('current_move_line_id', '=', False)]}">
|
||||
<!-- on the left of the screen -->
|
||||
<div class="o_shuttle_data_content o_shuttle_move">
|
||||
<div>
|
||||
<group>
|
||||
<field name="current_move_line_id" invisible="1"/>
|
||||
<label for="picking_id"/>
|
||||
<div>
|
||||
<div>
|
||||
<field name="picking_id" options="{'no_open': True}" class="mr8"/>
|
||||
<span>/</span>
|
||||
<field name="picking_origin" class="oe_inline ml8"/>
|
||||
</div>
|
||||
</div>
|
||||
<label for="picking_partner_id"/>
|
||||
<div>
|
||||
<field name="picking_partner_id" options="{'no_open': True}"/>
|
||||
</div>
|
||||
<label for="location_dest_id"/>
|
||||
<div>
|
||||
<field name="location_dest_id"
|
||||
class="bg-primary o_shuttle_highlight"
|
||||
options="{'no_open': True}"/>
|
||||
</div>
|
||||
<label for="product_id"/>
|
||||
<div colspan="2" class="oe_title">
|
||||
<h1>
|
||||
<field name="product_id" options="{'no_open': True}"/>
|
||||
</h1>
|
||||
</div>
|
||||
<div colspan="2">
|
||||
<field name="product_packagings"/>
|
||||
</div>
|
||||
<field name="lot_id"/>
|
||||
<label for="product_uom_qty" string="Quantity" class="ml32"/>
|
||||
<div colspan="2" class="ml32">
|
||||
<h1 class="bg-primary o_shuttle_highlight">
|
||||
<field name="product_uom_qty" class="oe_inline text-white"/>
|
||||
<field name="product_uom_id" options="{'no_open': True}" class="oe_inline ml8 text-white"/>
|
||||
</h1>
|
||||
</div>
|
||||
</group>
|
||||
</div>
|
||||
</div>
|
||||
<!-- on the right of the screen -->
|
||||
<div class="o_shuttle_data_content o_shuttle_tray">
|
||||
<group col="1">
|
||||
<field name="tray_name"/>
|
||||
<field name="tray_type_code"/>
|
||||
<field name="tray_y"/>
|
||||
<field name="tray_x"/>
|
||||
<label for="tray_qty"/>
|
||||
<div colspan="2" class="oe_title">
|
||||
<h1>
|
||||
<field name="tray_qty"/>
|
||||
</h1>
|
||||
</div>
|
||||
</group>
|
||||
<group>
|
||||
<div>
|
||||
<field name="tray_matrix"
|
||||
widget="location_tray_matrix"
|
||||
/>
|
||||
</div>
|
||||
</group>
|
||||
</div>
|
||||
</div>
|
||||
<field name="_barcode_scanned" widget="barcode_handler"/>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="vertical_lift_shuttle_form_menu" model="ir.ui.view">
|
||||
<field name="name">vertical.lift.shuttle.view.form.menu</field>
|
||||
<field name="model">vertical.lift.shuttle</field>
|
||||
@@ -190,9 +63,6 @@
|
||||
<field name="mode"/>
|
||||
<field name="location_id"/>
|
||||
<field name="hardware"/>
|
||||
<!-- move line visible and editable for prototyping purposes, to
|
||||
remove later -->
|
||||
<field name="current_move_line_id" groups="base.group_no_one"/>
|
||||
</group>
|
||||
</form>
|
||||
</field>
|
||||
@@ -205,8 +75,8 @@ remove later -->
|
||||
<kanban class="o_kanban_dashboard">
|
||||
<field name="name"/>
|
||||
<field name="mode"/>
|
||||
<field name="number_of_ops"/>
|
||||
<field name="number_of_ops_all"/>
|
||||
<!-- <field name="number_of_ops"/> -->
|
||||
<!-- <field name="number_of_ops_all"/> -->
|
||||
<templates>
|
||||
<t t-name="kanban-box">
|
||||
<div class="oe_kanban_global_click o_has_icon open_shuttle_screen">
|
||||
@@ -224,14 +94,14 @@ remove later -->
|
||||
Mode:
|
||||
<field name="mode"/>
|
||||
</div>
|
||||
<div class="col-8">
|
||||
Operations:
|
||||
<field name="number_of_ops"/>
|
||||
</div>
|
||||
<div class="col-8">
|
||||
All <t t-esc="record.mode.value"/> Operations:
|
||||
<field name="number_of_ops_all"/>
|
||||
</div>
|
||||
<!-- <div class="col-8"> -->
|
||||
<!-- Operations: -->
|
||||
<!-- <field name="number_of_ops"/> -->
|
||||
<!-- </div> -->
|
||||
<!-- <div class="col-8"> -->
|
||||
<!-- All <t t-esc="record.mode.value"/> Operations: -->
|
||||
<!-- <field name="number_of_ops_all"/> -->
|
||||
<!-- </div> -->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user