mirror of
https://github.com/OCA/stock-logistics-warehouse.git
synced 2025-01-21 14:27:28 +02:00
Add Put-Away workflow
This commit is contained in:
@@ -3,6 +3,7 @@
|
||||
|
||||
<record id="product_running_socks" model="product.product">
|
||||
<field name="default_code">RS200</field>
|
||||
<field name="barcode">4491673293664</field>
|
||||
<field name="name">Running Socks</field>
|
||||
<field name="type">product</field>
|
||||
<field name="categ_id" ref="product.product_category_6"/>
|
||||
@@ -17,6 +18,7 @@
|
||||
|
||||
<record id="product_recovery_socks" model="product.product">
|
||||
<field name="default_code">RS300</field>
|
||||
<field name="barcode">2779891103531</field>
|
||||
<field name="name">Recovery Socks</field>
|
||||
<field name="type">product</field>
|
||||
<field name="categ_id" ref="product.product_category_6"/>
|
||||
|
||||
@@ -27,4 +27,26 @@
|
||||
<value model="stock.picking" eval="[obj().env.ref('stock_vertical_lift.stock_picking_out_demo_vertical_lift_1').id]"/>
|
||||
</function>
|
||||
|
||||
<record id="stock_picking_in_demo_vertical_lift_1" model="stock.picking">
|
||||
<field name="picking_type_id" ref="stock.picking_type_in"/>
|
||||
<field name="origin">Incoming shipment from Vertical Lift (demo)</field>
|
||||
<field name="partner_id" ref="base.res_partner_1"/>
|
||||
<field name="date" eval="DateTime.today()"/>
|
||||
<field name="location_id" ref="stock.stock_location_suppliers"/>
|
||||
<field name="location_dest_id" ref="stock.stock_location_stock"/>
|
||||
<field name="move_lines" model="stock.move" eval="[(0, 0, {
|
||||
'name': obj().env.ref('stock_vertical_lift.product_running_socks').name,
|
||||
'product_id': ref('stock_vertical_lift.product_running_socks'),
|
||||
'product_uom': ref('uom.product_uom_unit'),
|
||||
'product_uom_qty': 15.0,
|
||||
'picking_type_id': ref('stock.picking_type_in'),
|
||||
'location_id': ref('stock.stock_location_suppliers'),
|
||||
'location_dest_id': ref('stock.stock_location_stock'),
|
||||
})]"/>
|
||||
</record>
|
||||
|
||||
<function model="stock.picking" name="action_confirm">
|
||||
<value model="stock.picking" eval="[obj().env.ref('stock_vertical_lift.stock_picking_in_demo_vertical_lift_1').id]"/>
|
||||
</function>
|
||||
|
||||
</odoo>
|
||||
|
||||
@@ -59,7 +59,9 @@ class VerticalLiftOperationTransfer(models.AbstractModel):
|
||||
_inherit = "vertical.lift.operation.base"
|
||||
_description = "Vertical Lift Operation - Transfer"
|
||||
|
||||
current_move_line_id = fields.Many2one(comodel_name="stock.move.line")
|
||||
current_move_line_id = fields.Many2one(
|
||||
comodel_name="stock.move.line", readonly=True
|
||||
)
|
||||
|
||||
number_of_ops = fields.Integer(
|
||||
compute="_compute_number_of_ops", string="Number of Operations"
|
||||
@@ -217,7 +219,6 @@ class VerticalLiftOperationTransfer(models.AbstractModel):
|
||||
|
||||
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"""
|
||||
@@ -245,20 +246,3 @@ class VerticalLiftOperationTransfer(models.AbstractModel):
|
||||
|
||||
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()
|
||||
|
||||
@@ -51,3 +51,24 @@ class VerticalLiftOperationPick(models.Model):
|
||||
if line.state != "done":
|
||||
line.qty_done = line.product_qty
|
||||
line.move_id._action_done()
|
||||
|
||||
def on_screen_open(self):
|
||||
"""Called when the screen is open"""
|
||||
self.select_next_move_line()
|
||||
|
||||
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()
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
# Copyright 2019 Camptocamp SA
|
||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
||||
|
||||
from odoo import _, exceptions, models
|
||||
from odoo import _, fields, models
|
||||
from odoo.osv import expression
|
||||
|
||||
|
||||
class VerticalLiftOperationPut(models.Model):
|
||||
@@ -9,27 +10,227 @@ class VerticalLiftOperationPut(models.Model):
|
||||
_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
|
||||
operation_line_ids = fields.One2many(
|
||||
comodel_name="vertical.lift.operation.put.line",
|
||||
inverse_name="operation_id",
|
||||
readonly=True,
|
||||
)
|
||||
current_operation_line_id = fields.Many2one(
|
||||
comodel_name="vertical.lift.operation.put.line", readonly=True
|
||||
)
|
||||
current_move_line_id = fields.Many2one(
|
||||
related="current_operation_line_id.move_line_id", readonly=True
|
||||
)
|
||||
# TODO think about moving the "steps" to the base model,
|
||||
# integrate 'save' and 'release' in 'next_step()', use states
|
||||
# in 'pick' as well
|
||||
state = fields.Selection(
|
||||
selection=[
|
||||
("scan_product", "Scan Product"),
|
||||
("scan_tray_type", "Scan Tray Type"),
|
||||
("save", "Save"),
|
||||
("release", "Release"),
|
||||
],
|
||||
default="scan_product",
|
||||
)
|
||||
|
||||
def _domain_move_lines_to_do_all(self):
|
||||
shuttle_locations = self.env["stock.location"].search(
|
||||
[("vertical_lift_kind", "=", "view")]
|
||||
next_operation = object()
|
||||
|
||||
def _transitions(self):
|
||||
return {
|
||||
"scan_product": "scan_tray_type",
|
||||
"scan_tray_type": "save",
|
||||
"save": "release",
|
||||
"release": self.next_operation,
|
||||
}
|
||||
|
||||
# The steps cannot be in 'vertical.lift.operation.put.line' because the
|
||||
# state has to be modified by on_barcode_scanned. As this method is an
|
||||
# onchange underneath, it has to be on the same model.
|
||||
def step(self):
|
||||
return self.state
|
||||
|
||||
def next_step(self):
|
||||
next_state = self._transitions().get(self.state)
|
||||
if next_state is not self.next_operation:
|
||||
self.state = next_state
|
||||
self.update_step_description()
|
||||
|
||||
def step_description(self):
|
||||
state_field = self._fields["state"]
|
||||
return state_field.convert_to_export(self.state, self)
|
||||
|
||||
def reset_steps(self):
|
||||
self.state = "scan_product"
|
||||
self.update_step_description()
|
||||
|
||||
def count_move_lines_to_do(self):
|
||||
"""Count move lines to process in current shuttle"""
|
||||
self.ensure_one()
|
||||
return self.env["vertical.lift.operation.put.line"].search_count(
|
||||
[("operation_id", "=", self.id)]
|
||||
)
|
||||
domain = [
|
||||
# TODO check state
|
||||
("state", "=", "assigned"),
|
||||
("location_dest_id", "child_of", shuttle_locations.ids),
|
||||
]
|
||||
return domain
|
||||
|
||||
def count_move_lines_to_do_all(self):
|
||||
"""Count move lines to process in all shuttles"""
|
||||
self.ensure_one()
|
||||
return self.env["vertical.lift.operation.put.line"].search_count([])
|
||||
|
||||
def on_barcode_scanned(self, barcode):
|
||||
self.ensure_one()
|
||||
operation_line = self.current_operation_line_id
|
||||
if operation_line:
|
||||
if self.step() == "scan_product":
|
||||
if self._check_product(barcode):
|
||||
self.next_step()
|
||||
if self.step() == "scan_tray_type":
|
||||
if self._check_tray_type(barcode):
|
||||
self.next_step()
|
||||
|
||||
def _check_product(self, barcode):
|
||||
return barcode == self.current_move_line_id.product_id.barcode
|
||||
|
||||
def _check_tray_type(self, barcode):
|
||||
location = self.current_move_line_id.location_dest_id
|
||||
tray_type = location.cell_in_tray_type_id
|
||||
return barcode == tray_type.code
|
||||
|
||||
def update_step_description(self):
|
||||
if self.current_operation_line_id:
|
||||
descr = self.step_description()
|
||||
else:
|
||||
descr = _("No operations")
|
||||
self.operation_descr = descr
|
||||
|
||||
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"))
|
||||
self.current_operation_line_id.process()
|
||||
|
||||
def button_release(self):
|
||||
self.write(
|
||||
{"operation_line_ids": [(2, self.current_operation_line_id.id)]}
|
||||
)
|
||||
return super().button_release()
|
||||
|
||||
def button_save(self):
|
||||
if not (self and self.current_operation_line_id):
|
||||
return
|
||||
self.ensure_one()
|
||||
self.process_current()
|
||||
self.next_step()
|
||||
|
||||
def on_screen_open(self):
|
||||
"""Called when the screen is open"""
|
||||
if self.operation_line_ids:
|
||||
self.select_next_move_line()
|
||||
else:
|
||||
return self.action_select_operations()
|
||||
|
||||
def select_next_move_line(self):
|
||||
self.ensure_one()
|
||||
next_operation = fields.first(self.operation_line_ids)
|
||||
self.current_operation_line_id = next_operation
|
||||
self.reset_steps()
|
||||
if next_operation:
|
||||
self.fetch_tray()
|
||||
|
||||
def action_select_operations(self):
|
||||
self.ensure_one()
|
||||
menu_xmlid = (
|
||||
"stock_vertical_lift." "vertical_lift_operation_put_select_view"
|
||||
)
|
||||
select_model = self.env["vertical.lift.operation.put.select"]
|
||||
select = select_model.create(
|
||||
{
|
||||
"operation_id": self.id,
|
||||
"move_line_ids": [
|
||||
(6, 0, self.mapped("operation_line_ids.move_line_id.id"))
|
||||
],
|
||||
}
|
||||
)
|
||||
return {
|
||||
"type": "ir.actions.act_window",
|
||||
"res_model": "vertical.lift.operation.put.select",
|
||||
"views": [[self.env.ref(menu_xmlid).id, "form"]],
|
||||
"name": _("Scan Operations"),
|
||||
"target": "new",
|
||||
"res_id": select.id,
|
||||
}
|
||||
|
||||
|
||||
class VerticalLiftOperationPutLine(models.Model):
|
||||
_name = "vertical.lift.operation.put.line"
|
||||
_description = "Vertical Lift Operation Put Line"
|
||||
|
||||
operation_id = fields.Many2one(
|
||||
comodel_name="vertical.lift.operation.put",
|
||||
required=True,
|
||||
readonly=True,
|
||||
)
|
||||
move_line_id = fields.Many2one(
|
||||
comodel_name="stock.move.line", readonly=True
|
||||
)
|
||||
|
||||
def process(self):
|
||||
line = self.move_line_id
|
||||
if line.state != "done":
|
||||
line.qty_done = line.product_qty
|
||||
line.move_id._action_done()
|
||||
|
||||
|
||||
class VerticalLiftOperationPutSelect(models.TransientModel):
|
||||
_name = "vertical.lift.operation.put.select"
|
||||
_inherit = "barcodes.barcode_events_mixin"
|
||||
_description = "Vertical Lift Operation Put Select"
|
||||
|
||||
operation_id = fields.Many2one(
|
||||
comodel_name="vertical.lift.operation.put",
|
||||
required=True,
|
||||
readonly=True,
|
||||
)
|
||||
move_line_ids = fields.Many2many(comodel_name="stock.move.line")
|
||||
|
||||
def _sync_lines(self):
|
||||
self.operation_id.operation_line_ids.unlink()
|
||||
operation_line_model = self.env["vertical.lift.operation.put.line"]
|
||||
operation_line_model.create(
|
||||
[
|
||||
{
|
||||
"operation_id": self.operation_id.id,
|
||||
"move_line_id": move_line.id,
|
||||
}
|
||||
for move_line in self.move_line_ids
|
||||
]
|
||||
)
|
||||
self.operation_id.select_next_move_line()
|
||||
|
||||
def action_validate(self):
|
||||
self._sync_lines()
|
||||
return {"type": "ir.actions.act_window_close"}
|
||||
|
||||
def _move_line_domain(self):
|
||||
return [
|
||||
("state", "=", "assigned"),
|
||||
("location_dest_id", "child_of", self.operation_id.location_id.id),
|
||||
]
|
||||
|
||||
def action_add_all(self):
|
||||
move_lines = self.env["stock.move.line"].search(
|
||||
self._move_line_domain()
|
||||
)
|
||||
self.move_line_ids = move_lines
|
||||
self._sync_lines()
|
||||
return {"type": "ir.actions.act_window_close"}
|
||||
|
||||
def on_barcode_scanned(self, barcode):
|
||||
self.ensure_one()
|
||||
domain = self._move_line_domain()
|
||||
domain = expression.AND(
|
||||
[domain, [("product_id.barcode", "=", barcode)]]
|
||||
)
|
||||
move_lines = self.env["stock.move.line"].search(domain)
|
||||
# note: on_barcode_scanned is called in an onchange, so 'self'
|
||||
# is a NewID, we can't use 'write()' on it.
|
||||
self.move_line_ids |= move_lines
|
||||
|
||||
@@ -3,4 +3,5 @@ access_vertical_lift_shuttle_stock_user,access_vertical_lift_shuttle stock user,
|
||||
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_put_line_stock_user,access_vertical_lift_operation_put_line stock user,model_vertical_lift_operation_put_line,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
|
||||
|
||||
|
@@ -109,7 +109,18 @@
|
||||
}
|
||||
}
|
||||
|
||||
.o_vlift_shuttle_manual_barcode {
|
||||
.o_vlift_shuttle_popup {
|
||||
|
||||
table tr {
|
||||
line-height: 3;
|
||||
font-size: 1.1em;
|
||||
|
||||
.o_list_record_remove {
|
||||
width: 50px;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
|
||||
.o_field_char {
|
||||
padding: 1em;
|
||||
font-size: 2em;
|
||||
@@ -120,5 +131,12 @@
|
||||
font-size: 2em;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
footer .btn {
|
||||
padding: 1em;
|
||||
font-size: 2em;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,2 +1,4 @@
|
||||
from . import test_location
|
||||
from . import test_vertical_lift_shuttle
|
||||
from . import test_inventory
|
||||
from . import test_pick
|
||||
from . import test_put
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
# Copyright 2019 Camptocamp SA
|
||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
||||
|
||||
from odoo import _
|
||||
from odoo.addons.stock_location_tray.tests import common
|
||||
|
||||
|
||||
@@ -14,6 +15,9 @@ class VerticalLiftCase(common.LocationTrayTypeCase):
|
||||
cls.product_socks = cls.env.ref(
|
||||
'stock_vertical_lift.product_running_socks'
|
||||
)
|
||||
cls.product_recovery = cls.env.ref(
|
||||
'stock_vertical_lift.product_recovery_socks'
|
||||
)
|
||||
cls.vertical_lift_loc = cls.env.ref(
|
||||
'stock_vertical_lift.stock_location_vertical_lift'
|
||||
)
|
||||
@@ -47,3 +51,51 @@ class VerticalLiftCase(common.LocationTrayTypeCase):
|
||||
],
|
||||
}
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def _create_simple_picking_in(cls, product, quantity, dest_location):
|
||||
supplier_loc = cls.env.ref('stock.stock_location_suppliers')
|
||||
picking_type = cls.env.ref('stock.picking_type_in')
|
||||
partner = cls.env.ref('base.res_partner_1')
|
||||
return cls.env['stock.picking'].create(
|
||||
{
|
||||
'picking_type_id': picking_type.id,
|
||||
'partner_id': partner.id,
|
||||
'location_id': supplier_loc.id,
|
||||
'location_dest_id': dest_location.id,
|
||||
'move_lines': [
|
||||
(
|
||||
0,
|
||||
0,
|
||||
{
|
||||
'name': product.name,
|
||||
'product_id': product.id,
|
||||
'product_uom': product.uom_id.id,
|
||||
'product_uom_qty': quantity,
|
||||
'picking_type_id': picking_type.id,
|
||||
'location_id': supplier_loc.id,
|
||||
'location_dest_id': dest_location.id,
|
||||
},
|
||||
)
|
||||
],
|
||||
}
|
||||
)
|
||||
|
||||
def _test_button_release(self, move_line):
|
||||
# for the test, we'll consider our last line has been delivered
|
||||
move_line.qty_done = move_line.product_qty
|
||||
move_line.move_id._action_done()
|
||||
# release, no further operation in queue
|
||||
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",
|
||||
}
|
||||
}
|
||||
self.assertEqual(result, expected_result)
|
||||
|
||||
31
stock_vertical_lift/tests/test_inventory.py
Normal file
31
stock_vertical_lift/tests/test_inventory.py
Normal file
@@ -0,0 +1,31 @@
|
||||
# Copyright 2019 Camptocamp SA
|
||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
||||
|
||||
import unittest
|
||||
|
||||
from .common import VerticalLiftCase
|
||||
|
||||
|
||||
class TestInventory(VerticalLiftCase):
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super().setUpClass()
|
||||
cls.picking_out = cls.env.ref(
|
||||
"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
|
||||
cls.out_move_line = cls.picking_out.move_line_ids
|
||||
|
||||
def test_switch_inventory(self):
|
||||
self.shuttle.switch_inventory()
|
||||
self.assertEqual(self.shuttle.mode, "inventory")
|
||||
|
||||
@unittest.skip("Not implemented")
|
||||
def test_inventory_count_move_lines(self):
|
||||
pass
|
||||
|
||||
@unittest.skip("Not implemented")
|
||||
def test_process_current_inventory(self):
|
||||
# test to implement when the code is implemented
|
||||
self.shuttle.switch_inventory()
|
||||
@@ -1,14 +1,12 @@
|
||||
# Copyright 2019 Camptocamp SA
|
||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
||||
|
||||
import unittest
|
||||
|
||||
from odoo import _, exceptions
|
||||
from odoo import _
|
||||
|
||||
from .common import VerticalLiftCase
|
||||
|
||||
|
||||
class TestVerticalLiftTrayType(VerticalLiftCase):
|
||||
class TestPick(VerticalLiftCase):
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super().setUpClass()
|
||||
@@ -27,19 +25,6 @@ class TestVerticalLiftTrayType(VerticalLiftCase):
|
||||
self.out_move_line,
|
||||
)
|
||||
|
||||
def test_switch_put(self):
|
||||
self.shuttle.switch_put()
|
||||
self.assertEqual(self.shuttle.mode, "put")
|
||||
# TODO check that we have an incoming move when switching
|
||||
self.assertEqual(
|
||||
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")
|
||||
|
||||
def test_pick_action_open_screen(self):
|
||||
self.shuttle.switch_pick()
|
||||
action = self.shuttle.action_open_screen()
|
||||
@@ -170,37 +155,21 @@ class TestVerticalLiftTrayType(VerticalLiftCase):
|
||||
self.assertEqual(operation1.number_of_ops_all, 6)
|
||||
self.assertEqual(operation2.number_of_ops_all, 6)
|
||||
|
||||
@unittest.skip("Not implemented")
|
||||
def test_put_count_move_lines(self):
|
||||
pass
|
||||
|
||||
@unittest.skip("Not implemented")
|
||||
def test_inventory_count_move_lines(self):
|
||||
pass
|
||||
|
||||
@unittest.skip("Not implemented")
|
||||
def test_on_barcode_scanned(self):
|
||||
# test to implement when the code is implemented
|
||||
pass
|
||||
self.shuttle.switch_pick()
|
||||
operation = self.shuttle._operation_for_mode()
|
||||
move_line = operation.current_move_line_id
|
||||
current_destination = move_line.location_dest_id
|
||||
stock_location = self.env.ref("stock.stock_location_stock")
|
||||
self.assertEqual(
|
||||
current_destination, self.env.ref("stock.stock_location_customers")
|
||||
)
|
||||
operation.on_barcode_scanned(stock_location.barcode)
|
||||
self.assertEqual(move_line.location_dest_id, stock_location)
|
||||
|
||||
def test_button_release(self):
|
||||
# for the test, we'll consider our last line has been delivered
|
||||
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
|
||||
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",
|
||||
}
|
||||
}
|
||||
self.assertEqual(result, expected_result)
|
||||
self.shuttle.switch_pick()
|
||||
self._test_button_release(self.out_move_line)
|
||||
|
||||
def test_process_current_pick(self):
|
||||
self.shuttle.switch_pick()
|
||||
@@ -211,17 +180,6 @@ class TestVerticalLiftTrayType(VerticalLiftCase):
|
||||
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):
|
||||
operation.process_current()
|
||||
|
||||
def test_process_current_inventory(self):
|
||||
# test to implement when the code is implemented
|
||||
self.shuttle.switch_inventory()
|
||||
|
||||
def test_matrix(self):
|
||||
self.shuttle.switch_pick()
|
||||
operation = self.shuttle._operation_for_mode()
|
||||
190
stock_vertical_lift/tests/test_put.py
Normal file
190
stock_vertical_lift/tests/test_put.py
Normal file
@@ -0,0 +1,190 @@
|
||||
# Copyright 2019 Camptocamp SA
|
||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
||||
|
||||
from .common import VerticalLiftCase
|
||||
|
||||
|
||||
class TestPut(VerticalLiftCase):
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super().setUpClass()
|
||||
cls.picking_in = cls.env.ref(
|
||||
"stock_vertical_lift.stock_picking_in_demo_vertical_lift_1"
|
||||
)
|
||||
cls.picking_in.action_confirm()
|
||||
cls.in_move_line = cls.picking_in.move_line_ids
|
||||
cls.location_1a_x1y1 = cls.env.ref(
|
||||
"stock_vertical_lift."
|
||||
"stock_location_vertical_lift_demo_tray_1a_x1y1"
|
||||
)
|
||||
cls.location_1a_x2y1 = cls.env.ref(
|
||||
"stock_vertical_lift."
|
||||
"stock_location_vertical_lift_demo_tray_1a_x2y1"
|
||||
)
|
||||
cls.location_1a_x3y1 = cls.env.ref(
|
||||
"stock_vertical_lift."
|
||||
"stock_location_vertical_lift_demo_tray_1a_x3y1"
|
||||
)
|
||||
cls.location_1a_x1y2 = cls.env.ref(
|
||||
"stock_vertical_lift."
|
||||
"stock_location_vertical_lift_demo_tray_1a_x1y2"
|
||||
)
|
||||
cls.in_move_line.location_dest_id = cls.location_1a_x3y1
|
||||
|
||||
def _select_move_lines(self, shuttle, move_lines=None):
|
||||
select_model = self.env["vertical.lift.operation.put.select"]
|
||||
operation = shuttle._operation_for_mode()
|
||||
select = select_model.create({"operation_id": operation.id})
|
||||
if move_lines:
|
||||
select.move_line_ids = [(6, 0, move_lines.ids)]
|
||||
else:
|
||||
select.action_add_all()
|
||||
select._sync_lines()
|
||||
|
||||
def test_put_action_open_screen(self):
|
||||
self.shuttle.switch_put()
|
||||
action = self.shuttle.action_open_screen()
|
||||
operation = self.shuttle._operation_for_mode()
|
||||
self.assertEqual(action["type"], "ir.actions.act_window")
|
||||
self.assertEqual(action["res_model"], "vertical.lift.operation.put")
|
||||
self.assertEqual(action["res_id"], operation.id)
|
||||
|
||||
def test_switch_put(self):
|
||||
self.shuttle.switch_put()
|
||||
self.assertEqual(self.shuttle.mode, "put")
|
||||
self.assertEqual(
|
||||
self.shuttle._operation_for_mode().current_move_line_id,
|
||||
self.env["stock.move.line"].browse(),
|
||||
)
|
||||
|
||||
def test_select_from_barcode(self):
|
||||
self.shuttle.switch_put()
|
||||
self.picking_in.action_cancel()
|
||||
put1 = self._create_simple_picking_in(
|
||||
self.product_socks, 10, self.location_1a_x1y1
|
||||
)
|
||||
put1.action_confirm()
|
||||
put2 = self._create_simple_picking_in(
|
||||
self.product_recovery, 10, self.location_1a_x2y1
|
||||
)
|
||||
put2.action_confirm()
|
||||
select_model = self.env["vertical.lift.operation.put.select"]
|
||||
operation = self.shuttle._operation_for_mode()
|
||||
select = select_model.create({"operation_id": operation.id})
|
||||
select.on_barcode_scanned(self.product_socks.barcode)
|
||||
self.assertRecordValues(
|
||||
select, [{"move_line_ids": put1.move_line_ids.ids}]
|
||||
)
|
||||
select.on_barcode_scanned(self.product_recovery.barcode)
|
||||
self.assertRecordValues(
|
||||
select,
|
||||
[{"move_line_ids": (put1.move_line_ids | put2.move_line_ids).ids}],
|
||||
)
|
||||
select.action_validate()
|
||||
self.assertEqual(len(operation.operation_line_ids), 2)
|
||||
self.assertRecordValues(
|
||||
operation.mapped("operation_line_ids"),
|
||||
[
|
||||
{"move_line_id": put1.move_line_ids.id},
|
||||
{"move_line_id": put2.move_line_ids.id},
|
||||
],
|
||||
)
|
||||
|
||||
def test_no_select_from_barcode_outside_location(self):
|
||||
self.shuttle.switch_put()
|
||||
self.picking_in.action_cancel()
|
||||
location = self.env.ref("stock.location_refrigerator_small")
|
||||
put1 = self._create_simple_picking_in(self.product_socks, 10, location)
|
||||
put1.action_confirm()
|
||||
select_model = self.env["vertical.lift.operation.put.select"]
|
||||
operation = self.shuttle._operation_for_mode()
|
||||
select = select_model.create({"operation_id": operation.id})
|
||||
select.on_barcode_scanned(self.product_socks.barcode)
|
||||
# the move line is outside of the vertical lift, should not be
|
||||
# selected
|
||||
self.assertRecordValues(select, [{"move_line_ids": []}])
|
||||
|
||||
def test_put_count_move_lines(self):
|
||||
self.shuttle.switch_put()
|
||||
self.picking_in.action_cancel()
|
||||
location_2a_x1y1 = self.env.ref(
|
||||
"stock_vertical_lift."
|
||||
"stock_location_vertical_lift_demo_tray_2a_x1y1"
|
||||
)
|
||||
put1 = self._create_simple_picking_in(
|
||||
self.product_socks, 10, self.location_1a_x1y1
|
||||
)
|
||||
put1.action_confirm()
|
||||
put2 = self._create_simple_picking_in(
|
||||
self.product_recovery, 10, self.location_1a_x2y1
|
||||
)
|
||||
put2.action_confirm()
|
||||
put3 = self._create_simple_picking_in(
|
||||
self.product_recovery, 10, location_2a_x1y1
|
||||
)
|
||||
put3.action_confirm()
|
||||
operation = self.shuttle._operation_for_mode()
|
||||
self._select_move_lines(self.shuttle)
|
||||
shuttle2 = self.env.ref(
|
||||
"stock_vertical_lift.stock_vertical_lift_demo_shuttle_2"
|
||||
)
|
||||
shuttle2.switch_put()
|
||||
operation2 = shuttle2._operation_for_mode()
|
||||
self._select_move_lines(shuttle2)
|
||||
|
||||
self.assertEqual(operation.number_of_ops, 2)
|
||||
self.assertEqual(operation.number_of_ops_all, 3)
|
||||
self.assertEqual(operation2.number_of_ops, 1)
|
||||
self.assertEqual(operation2.number_of_ops_all, 3)
|
||||
|
||||
def test_process_current_put(self):
|
||||
self.shuttle.switch_put()
|
||||
operation = self.shuttle._operation_for_mode()
|
||||
self._select_move_lines(self.shuttle, self.in_move_line)
|
||||
self.assertEqual(operation.current_move_line_id, self.in_move_line)
|
||||
qty_to_process = self.in_move_line.product_qty
|
||||
operation.process_current()
|
||||
self.assertEqual(self.in_move_line.state, "done")
|
||||
self.assertEqual(self.in_move_line.qty_done, qty_to_process)
|
||||
|
||||
def test_transition_reset(self):
|
||||
self.shuttle.switch_put()
|
||||
operation = self.shuttle._operation_for_mode()
|
||||
self._select_move_lines(self.shuttle, self.in_move_line)
|
||||
operation.state = "scan_tray_type"
|
||||
operation.reset_steps()
|
||||
self.assertEqual(operation.step(), "scan_product")
|
||||
|
||||
def test_transition_scan_product(self):
|
||||
self.shuttle.switch_put()
|
||||
operation = self.shuttle._operation_for_mode()
|
||||
self._select_move_lines(self.shuttle, self.in_move_line)
|
||||
operation.state = "scan_product"
|
||||
# wrong barcode, nothing happens
|
||||
operation.on_barcode_scanned("foo")
|
||||
# product scanned, move to next step
|
||||
operation.on_barcode_scanned(self.product_socks.barcode)
|
||||
self.assertEqual(operation.step(), "scan_tray_type")
|
||||
|
||||
def test_transition_scan_tray_type(self):
|
||||
self.shuttle.switch_put()
|
||||
operation = self.shuttle._operation_for_mode()
|
||||
self._select_move_lines(self.shuttle, self.in_move_line)
|
||||
operation.state = "scan_tray_type"
|
||||
# wrong barcode, nothing happens
|
||||
operation.on_barcode_scanned("foo")
|
||||
# tray type scanned, move to next step
|
||||
operation.on_barcode_scanned(operation.tray_type_id.code)
|
||||
self.assertEqual(operation.step(), "save")
|
||||
|
||||
def test_transition_save(self):
|
||||
self.shuttle.switch_put()
|
||||
operation = self.shuttle._operation_for_mode()
|
||||
self._select_move_lines(self.shuttle, self.in_move_line)
|
||||
operation.state = "save"
|
||||
operation.button_save()
|
||||
self.assertEqual(operation.step(), "release")
|
||||
|
||||
def test_transition_button_release(self):
|
||||
self.shuttle.switch_put()
|
||||
self._test_button_release(self.in_move_line)
|
||||
@@ -40,7 +40,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="o_shuttle_actions">
|
||||
<div class="o_shuttle_content">
|
||||
<div class="o_shuttle_content o_shuttle_content_left">
|
||||
<button name="action_menu" type="object"
|
||||
class="btn-secondary o_shuttle_icon_btn"
|
||||
string="" icon="fa-bars"
|
||||
|
||||
@@ -23,6 +23,63 @@
|
||||
<form position="attributes">
|
||||
<attribute name="string">Put-Away Screen</attribute>
|
||||
</form>
|
||||
<xpath expr="//div[hasclass('o_shuttle_actions')]/div[hasclass('o_shuttle_content_left')]"
|
||||
position="inside" >
|
||||
<button name="action_select_operations" type="object"
|
||||
class="btn-primary"
|
||||
string="Select Operations"
|
||||
aria-label="Select Operations"
|
||||
title="Select Operations"/>
|
||||
</xpath>
|
||||
<field name="_barcode_scanned" position="before">
|
||||
<!-- these fields have to be in the view otherwise they
|
||||
would be empty in the record sent to the _barcode_scanned
|
||||
onchange method
|
||||
-->
|
||||
<field name="state" invisible="1"/>
|
||||
<field name="current_operation_line_id" invisible="1"/>
|
||||
</field>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="vertical_lift_operation_put_select_view" model="ir.ui.view">
|
||||
<field name="name">vertical.lift.operation.put.select.view</field>
|
||||
<field name="model">vertical.lift.operation.put.select</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Select" class="o_vlift_shuttle_popup">
|
||||
<field name="move_line_ids">
|
||||
<tree create="0" edit="0">
|
||||
<field name="product_id" />
|
||||
</tree>
|
||||
<form>
|
||||
<group>
|
||||
<field name="product_id" readonly="1" />
|
||||
<field name="product_uom_qty" readonly="1" />
|
||||
<field name="qty_done" readonly="1" />
|
||||
</group>
|
||||
</form>
|
||||
</field>
|
||||
<!-- this field has to be in the view otherwise it
|
||||
would be empty in the record sent to the _barcode_scanned
|
||||
onchange method
|
||||
-->
|
||||
<field name="operation_id" invisible="1" />
|
||||
<field name="_barcode_scanned" widget="barcode_handler"/>
|
||||
<footer>
|
||||
<button name="action_validate"
|
||||
string="Validate"
|
||||
type="object"
|
||||
class="btn-primary"/>
|
||||
<button string="Cancel"
|
||||
class="btn-secondary"
|
||||
special="cancel" />
|
||||
<button name="action_add_all"
|
||||
string="Add all"
|
||||
type="object"
|
||||
groups="base.group_no_one"
|
||||
class="btn-secondary"/>
|
||||
</footer>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
|
||||
@@ -37,7 +37,7 @@
|
||||
<field name="model">vertical.lift.shuttle.manual.barcode</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Shuttle">
|
||||
<div class="row o_vlift_shuttle_manual_barcode">
|
||||
<div class="row o_vlift_shuttle_popup">
|
||||
<div class="col-8">
|
||||
<field name="barcode"/>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user