mirror of
https://github.com/OCA/stock-logistics-warehouse.git
synced 2025-01-21 14:27:28 +02:00
Implement inventory screen
This commit is contained in:
@@ -3,6 +3,7 @@ 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_inventory
|
||||
from . import stock_location
|
||||
from . import stock_move
|
||||
from . import stock_move_line
|
||||
|
||||
27
stock_vertical_lift/models/stock_inventory.py
Normal file
27
stock_vertical_lift/models/stock_inventory.py
Normal file
@@ -0,0 +1,27 @@
|
||||
# Copyright 2019 Camptocamp SA
|
||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
||||
|
||||
from odoo import api, fields, models
|
||||
|
||||
|
||||
class InventoryLine(models.Model):
|
||||
_inherit = "stock.inventory.line"
|
||||
|
||||
vertical_lift_done = fields.Boolean(default=False)
|
||||
# Field used to sort lines by tray on the inventory scan screen, so entire
|
||||
# trays are processed one after the other
|
||||
vertical_lift_tray_id = fields.Many2one(
|
||||
comodel_name="stock.location",
|
||||
compute="_compute_vertical_lift_tray_id",
|
||||
readonly=True,
|
||||
store=True,
|
||||
)
|
||||
|
||||
@api.depends("location_id.vertical_lift_kind")
|
||||
def _compute_vertical_lift_tray_id(self):
|
||||
for line in self:
|
||||
if line.location_id.vertical_lift_kind == "cell":
|
||||
# The parent of the cell is the tray.
|
||||
line.vertical_lift_tray_id = line.location_id.location_id
|
||||
else:
|
||||
line.vertical_lift_tray_id = False
|
||||
@@ -19,6 +19,13 @@ class VerticalLiftOperationBase(models.AbstractModel):
|
||||
location_id = fields.Many2one(
|
||||
related="shuttle_id.location_id", readonly=True
|
||||
)
|
||||
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",
|
||||
)
|
||||
mode = fields.Selection(related="shuttle_id.mode", readonly=True)
|
||||
operation_descr = fields.Char(
|
||||
string="Operation", default="...", readonly=True
|
||||
@@ -32,6 +39,16 @@ class VerticalLiftOperationBase(models.AbstractModel):
|
||||
)
|
||||
]
|
||||
|
||||
@api.depends()
|
||||
def _compute_number_of_ops(self):
|
||||
for record in self:
|
||||
record.number_of_ops = 0
|
||||
|
||||
@api.depends()
|
||||
def _compute_number_of_ops_all(self):
|
||||
for record in self:
|
||||
record.number_of_ops_all = 0
|
||||
|
||||
def on_barcode_scanned(self, barcode):
|
||||
self.ensure_one()
|
||||
# to implement in sub-classes
|
||||
@@ -51,6 +68,35 @@ class VerticalLiftOperationBase(models.AbstractModel):
|
||||
def action_manual_barcode(self):
|
||||
return self.shuttle_id.action_manual_barcode()
|
||||
|
||||
def button_save(self):
|
||||
"""Process the action (pick, put, ...)"""
|
||||
raise NotImplementedError
|
||||
|
||||
def button_release(self):
|
||||
"""Release the operation, go to the next"""
|
||||
raise NotImplementedError
|
||||
|
||||
def _render_product_packagings(self, product):
|
||||
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
|
||||
)
|
||||
return content
|
||||
|
||||
def _get_tray_qty(self, product, location):
|
||||
quants = self.env["stock.quant"].search(
|
||||
[
|
||||
("location_id", "=", location.id),
|
||||
("product_id", "=", product.id),
|
||||
]
|
||||
)
|
||||
return sum(quants.mapped("quantity"))
|
||||
|
||||
|
||||
class VerticalLiftOperationTransfer(models.AbstractModel):
|
||||
"""Base model for shuttle pick and put operations"""
|
||||
@@ -63,14 +109,6 @@ class VerticalLiftOperationTransfer(models.AbstractModel):
|
||||
comodel_name="stock.move.line", readonly=True
|
||||
)
|
||||
|
||||
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",
|
||||
@@ -137,21 +175,10 @@ class VerticalLiftOperationTransfer(models.AbstractModel):
|
||||
def _compute_product_packagings(self):
|
||||
for record in self:
|
||||
if not record.current_move_line_id:
|
||||
record.product_packagings = ""
|
||||
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
|
||||
)
|
||||
content = self._render_product_packagings(product)
|
||||
record.product_packagings = content
|
||||
|
||||
@api.depends()
|
||||
@@ -168,17 +195,12 @@ class VerticalLiftOperationTransfer(models.AbstractModel):
|
||||
def _compute_tray_qty(self):
|
||||
for record in self:
|
||||
if not (record.tray_location_id and record.current_move_line_id):
|
||||
record.tray_qty = 0.
|
||||
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"))
|
||||
location = record.tray_location_id
|
||||
record.tray_qty = self._get_tray_qty(product, location)
|
||||
|
||||
# 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:
|
||||
@@ -245,4 +267,4 @@ class VerticalLiftOperationTransfer(models.AbstractModel):
|
||||
self.operation_descr = _("Release")
|
||||
|
||||
def fetch_tray(self):
|
||||
return
|
||||
raise NotImplementedError
|
||||
|
||||
@@ -1,10 +1,261 @@
|
||||
# Copyright 2019 Camptocamp SA
|
||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
||||
|
||||
from odoo import models
|
||||
from odoo import _, api, fields, models
|
||||
from odoo.addons.base_sparse_field.models.fields import Serialized
|
||||
from odoo.tools import float_compare
|
||||
|
||||
# TODO handle autofocus + easy way to validate for the input field
|
||||
|
||||
|
||||
class VerticalLiftOperationInventory(models.Model):
|
||||
_name = 'vertical.lift.operation.inventory'
|
||||
_inherit = 'vertical.lift.operation.base'
|
||||
_description = 'Vertical Lift Operation Inventory'
|
||||
_name = "vertical.lift.operation.inventory"
|
||||
_inherit = "vertical.lift.operation.base"
|
||||
_description = "Vertical Lift Operation Inventory"
|
||||
|
||||
current_inventory_line_id = fields.Many2one(
|
||||
comodel_name="stock.inventory.line", readonly=True
|
||||
)
|
||||
|
||||
quantity_input = fields.Float()
|
||||
# if the quantity is wrong, user has to write 2 times
|
||||
# the same quantity to really confirm it's correct
|
||||
last_quantity_input = fields.Float()
|
||||
state = fields.Selection(
|
||||
selection=[
|
||||
("quantity", "Inventory, please enter the amount"),
|
||||
(
|
||||
"confirm_wrong_quantity",
|
||||
"The quantity does not match, are you sure?",
|
||||
),
|
||||
("save", "Save"),
|
||||
],
|
||||
default="quantity",
|
||||
)
|
||||
|
||||
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
|
||||
inventory_id = fields.Many2one(
|
||||
related="current_inventory_line_id.inventory_id", readonly=True
|
||||
)
|
||||
product_id = fields.Many2one(
|
||||
related="current_inventory_line_id.product_id", readonly=True
|
||||
)
|
||||
product_uom_id = fields.Many2one(
|
||||
related="current_inventory_line_id.product_uom_id", readonly=True
|
||||
)
|
||||
product_qty = fields.Float(
|
||||
related="current_inventory_line_id.product_qty", readonly=True
|
||||
)
|
||||
product_packagings = fields.Html(
|
||||
string="Packaging", compute="_compute_product_packagings"
|
||||
)
|
||||
package_id = fields.Many2one(
|
||||
related="current_inventory_line_id.package_id", readonly=True
|
||||
)
|
||||
lot_id = fields.Many2one(
|
||||
related="current_inventory_line_id.prod_lot_id", readonly=True
|
||||
)
|
||||
|
||||
@api.depends("current_inventory_line_id")
|
||||
def _compute_tray_data(self):
|
||||
for record in self:
|
||||
location = record.current_inventory_line_id.location_id
|
||||
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
|
||||
|
||||
@api.depends("current_inventory_line_id.product_id.packaging_ids")
|
||||
def _compute_product_packagings(self):
|
||||
for record in self:
|
||||
if not record.current_inventory_line_id:
|
||||
record.product_packagings = ""
|
||||
continue
|
||||
product = record.current_inventory_line_id.product_id
|
||||
content = self._render_product_packagings(product)
|
||||
record.product_packagings = content
|
||||
|
||||
@api.depends("tray_location_id", "current_inventory_line_id.product_id")
|
||||
def _compute_tray_qty(self):
|
||||
for record in self:
|
||||
if not (
|
||||
record.tray_location_id and record.current_inventory_line_id
|
||||
):
|
||||
record.tray_qty = 0.
|
||||
continue
|
||||
product = record.current_inventory_line_id.product_id
|
||||
location = record.tray_location_id
|
||||
record.tray_qty = self._get_tray_qty(product, location)
|
||||
|
||||
def _compute_number_of_ops(self):
|
||||
for record in self:
|
||||
line_model = self.env["stock.inventory.line"]
|
||||
record.number_of_ops = line_model.search_count(
|
||||
self._domain_inventory_lines_to_do()
|
||||
)
|
||||
|
||||
def _compute_number_of_ops_all(self):
|
||||
for record in self:
|
||||
line_model = self.env["stock.inventory.line"]
|
||||
record.number_of_ops_all = line_model.search_count(
|
||||
self._domain_inventory_lines_to_do_all()
|
||||
)
|
||||
|
||||
def _domain_inventory_lines_to_do(self):
|
||||
return [
|
||||
("location_id", "child_of", self.location_id.id),
|
||||
("state", "=", "confirm"),
|
||||
("vertical_lift_done", "=", False),
|
||||
]
|
||||
|
||||
def _domain_inventory_lines_to_do_all(self):
|
||||
shuttle_locations = self.env["stock.location"].search(
|
||||
[("vertical_lift_kind", "=", "view")]
|
||||
)
|
||||
return [
|
||||
("location_id", "child_of", shuttle_locations.ids),
|
||||
("state", "=", "confirm"),
|
||||
("vertical_lift_done", "=", False),
|
||||
]
|
||||
|
||||
def on_screen_open(self):
|
||||
"""Called when the screen is open"""
|
||||
self.select_next_inventory_line()
|
||||
|
||||
def reset(self):
|
||||
self.write(
|
||||
{
|
||||
"quantity_input": 0.,
|
||||
"last_quantity_input": 0.,
|
||||
"state": "quantity",
|
||||
}
|
||||
)
|
||||
self.update_step_description()
|
||||
|
||||
def step(self):
|
||||
return self.state
|
||||
|
||||
def step_to(self, state):
|
||||
self.state = state
|
||||
self.update_step_description()
|
||||
|
||||
def step_description(self):
|
||||
state_field = self._fields["state"]
|
||||
return state_field.convert_to_export(self.state, self)
|
||||
|
||||
def update_step_description(self):
|
||||
if self.current_inventory_line_id:
|
||||
descr = self.step_description()
|
||||
else:
|
||||
descr = _("No operations")
|
||||
self.operation_descr = descr
|
||||
|
||||
def button_save(self):
|
||||
if not self.current_inventory_line_id:
|
||||
return
|
||||
self.ensure_one()
|
||||
self.process_current()
|
||||
if self.step() == "save":
|
||||
self.select_next_inventory_line()
|
||||
if not self.current_inventory_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 button_release(self):
|
||||
raise NotImplementedError
|
||||
|
||||
def _has_identical_quantity(self):
|
||||
line = self.current_inventory_line_id
|
||||
return (
|
||||
float_compare(
|
||||
line.theoretical_qty,
|
||||
self.quantity_input,
|
||||
precision_rounding=line.product_uom_id.rounding,
|
||||
)
|
||||
== 0
|
||||
)
|
||||
|
||||
def _process_quantity(self):
|
||||
if self.step() == "quantity":
|
||||
if self._has_identical_quantity():
|
||||
self.step_to("save")
|
||||
return True
|
||||
else:
|
||||
self.last_quantity_input = self.quantity_input
|
||||
self.quantity_input = 0.
|
||||
self.step_to("confirm_wrong_quantity")
|
||||
return False
|
||||
if self.step() == "confirm_wrong_quantity":
|
||||
if self.quantity_input == self.last_quantity_input:
|
||||
# confirms the previous input
|
||||
self.step_to("save")
|
||||
return True
|
||||
else:
|
||||
# cycle back to the first quantity check
|
||||
self.step_to("quantity")
|
||||
return self._process_quantity()
|
||||
|
||||
def process_current(self):
|
||||
line = self.current_inventory_line_id
|
||||
if self._process_quantity() and not line.vertical_lift_done:
|
||||
line.vertical_lift_done = True
|
||||
if self.quantity_input != line.product_qty:
|
||||
line.product_qty = self.quantity_input
|
||||
inventory = line.inventory_id
|
||||
if all(line.vertical_lift_done for line in inventory.line_ids):
|
||||
inventory.action_validate()
|
||||
|
||||
def fetch_tray(self):
|
||||
location = self.current_inventory_line_id.location_id
|
||||
location.fetch_vertical_lift_tray()
|
||||
|
||||
def select_next_inventory_line(self):
|
||||
self.ensure_one()
|
||||
next_line = self.env["stock.inventory.line"].search(
|
||||
self._domain_inventory_lines_to_do(),
|
||||
limit=1,
|
||||
order="vertical_lift_tray_id, location_id, id",
|
||||
)
|
||||
previous_line = self.current_inventory_line_id
|
||||
self.current_inventory_line_id = next_line
|
||||
self.reset()
|
||||
if (
|
||||
next_line
|
||||
and previous_line.vertical_lift_tray_id
|
||||
!= next_line.vertical_lift_tray_id
|
||||
):
|
||||
self.fetch_tray()
|
||||
|
||||
@@ -1,3 +1,8 @@
|
||||
* Complete Pick screen and workflow (currently enough for a demo, not for production)
|
||||
* Implement Put-away screen and workflow
|
||||
* Implement Inventory screen and workflow
|
||||
* Complete screen workflows (currently enough for a demo, not for production)
|
||||
* Inventory: find a way to have a nice autofocus for quantity, still compatible
|
||||
with barcode scanner (Odoo disables the autofocus when using barcode, which
|
||||
makes sense)
|
||||
* Put-away: handle packages
|
||||
* Handle "multi-shuttle" put-away
|
||||
* Create glue module for product_expiry
|
||||
* Challenge the save + release buttons and workflow
|
||||
|
||||
@@ -21,6 +21,47 @@ class VerticalLiftCase(common.LocationTrayTypeCase):
|
||||
cls.vertical_lift_loc = cls.env.ref(
|
||||
'stock_vertical_lift.stock_location_vertical_lift'
|
||||
)
|
||||
cls.location_1a = cls.env.ref(
|
||||
"stock_vertical_lift."
|
||||
"stock_location_vertical_lift_demo_tray_1a"
|
||||
)
|
||||
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.location_1b_x1y1 = cls.env.ref(
|
||||
"stock_vertical_lift."
|
||||
"stock_location_vertical_lift_demo_tray_1b_x1y1"
|
||||
)
|
||||
cls.location_1b_x1y2 = cls.env.ref(
|
||||
"stock_vertical_lift."
|
||||
"stock_location_vertical_lift_demo_tray_1b_x1y2"
|
||||
)
|
||||
cls.location_2a = cls.env.ref(
|
||||
"stock_vertical_lift."
|
||||
"stock_location_vertical_lift_demo_tray_2a"
|
||||
)
|
||||
cls.location_2a_x1y1 = cls.env.ref(
|
||||
"stock_vertical_lift."
|
||||
"stock_location_vertical_lift_demo_tray_2a_x1y1"
|
||||
)
|
||||
|
||||
def _update_qty_in_location(self, location, product, quantity):
|
||||
self.env["stock.quant"]._update_available_quantity(
|
||||
product, location, quantity
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def _create_simple_picking_out(cls, product, quantity):
|
||||
@@ -81,6 +122,25 @@ class VerticalLiftCase(common.LocationTrayTypeCase):
|
||||
}
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def _create_inventory(self, products):
|
||||
"""Create a draft inventory
|
||||
|
||||
Products is a list of tuples (bin location, product).
|
||||
"""
|
||||
values = {
|
||||
'name': 'Test Inventory',
|
||||
'filter': 'partial',
|
||||
'line_ids': [(0, 0, {
|
||||
'product_id': product.id,
|
||||
'product_uom_id': product.uom_id.id,
|
||||
'location_id': location.id
|
||||
}) for location, product in products]
|
||||
}
|
||||
inventory = self.env['stock.inventory'].create(values)
|
||||
inventory.action_start()
|
||||
return inventory
|
||||
|
||||
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
|
||||
|
||||
@@ -1,31 +1,110 @@
|
||||
# Copyright 2019 Camptocamp SA
|
||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
||||
|
||||
import unittest
|
||||
|
||||
from odoo import _
|
||||
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")
|
||||
self.assertEqual(
|
||||
self.shuttle._operation_for_mode().current_inventory_line_id,
|
||||
self.env["stock.inventory.line"].browse(),
|
||||
)
|
||||
|
||||
@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
|
||||
def test_inventory_action_open_screen(self):
|
||||
self.shuttle.switch_inventory()
|
||||
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.inventory"
|
||||
)
|
||||
self.assertEqual(action["res_id"], operation.id)
|
||||
|
||||
def test_inventory_count_ops(self):
|
||||
self._update_qty_in_location(
|
||||
self.location_1a_x1y1, self.product_socks, 10
|
||||
)
|
||||
self._update_qty_in_location(
|
||||
self.location_1a_x2y1, self.product_recovery, 10
|
||||
)
|
||||
self._create_inventory(
|
||||
[
|
||||
(self.location_1a_x1y1, self.product_socks),
|
||||
(self.location_1a_x2y1, self.product_recovery),
|
||||
]
|
||||
)
|
||||
self._update_qty_in_location(
|
||||
self.location_2a_x1y1, self.product_socks, 10
|
||||
)
|
||||
self._create_inventory([(self.location_2a_x1y1, self.product_socks)])
|
||||
|
||||
self.shuttle.switch_inventory()
|
||||
operation = self.shuttle._operation_for_mode()
|
||||
self.assertEqual(operation.number_of_ops, 2)
|
||||
self.assertEqual(operation.number_of_ops_all, 3)
|
||||
|
||||
def test_process_current_inventory(self):
|
||||
self._update_qty_in_location(
|
||||
self.location_1a_x1y1, self.product_socks, 10
|
||||
)
|
||||
inventory = self._create_inventory(
|
||||
[(self.location_1a_x1y1, self.product_socks)]
|
||||
)
|
||||
self.shuttle.switch_inventory()
|
||||
operation = self.shuttle._operation_for_mode()
|
||||
self.assertEqual(
|
||||
operation.current_inventory_line_id, inventory.line_ids
|
||||
)
|
||||
# test the happy path, quantity is correct
|
||||
operation.quantity_input = 10.0
|
||||
result = operation.button_save()
|
||||
# state is reset
|
||||
self.assertEqual(operation.state, "quantity")
|
||||
self.assertFalse(operation.current_inventory_line_id)
|
||||
self.assertEqual(operation.operation_descr, _("No operations"))
|
||||
self.assertTrue(inventory.line_ids.vertical_lift_done)
|
||||
self.assertEqual(inventory.state, "done")
|
||||
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)
|
||||
|
||||
def test_wrong_quantity(self):
|
||||
self._update_qty_in_location(
|
||||
self.location_1a_x1y1, self.product_socks, 10
|
||||
)
|
||||
inventory = self._create_inventory(
|
||||
[(self.location_1a_x1y1, self.product_socks)]
|
||||
)
|
||||
self.shuttle.switch_inventory()
|
||||
operation = self.shuttle._operation_for_mode()
|
||||
line = operation.current_inventory_line_id
|
||||
self.assertEqual(line, inventory.line_ids)
|
||||
|
||||
operation.quantity_input = 12.0
|
||||
operation.button_save()
|
||||
self.assertEqual(operation.last_quantity_input, 12.0)
|
||||
self.assertEqual(operation.quantity_input, 0.0)
|
||||
self.assertEqual(operation.state, "confirm_wrong_quantity")
|
||||
self.assertEqual(operation.current_inventory_line_id, line)
|
||||
self.assertEqual(
|
||||
operation.operation_descr,
|
||||
_("The quantity does not match, are you sure?"),
|
||||
)
|
||||
|
||||
# entering the same quantity a second time validates
|
||||
operation.quantity_input = 12.0
|
||||
operation.button_save()
|
||||
self.assertFalse(operation.current_inventory_line_id)
|
||||
|
||||
self.assertTrue(inventory.line_ids.vertical_lift_done)
|
||||
self.assertEqual(inventory.state, "done")
|
||||
|
||||
@@ -13,22 +13,6 @@ class TestPut(VerticalLiftCase):
|
||||
)
|
||||
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):
|
||||
@@ -107,10 +91,6 @@ class TestPut(VerticalLiftCase):
|
||||
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
|
||||
)
|
||||
@@ -120,7 +100,7 @@ class TestPut(VerticalLiftCase):
|
||||
)
|
||||
put2.action_confirm()
|
||||
put3 = self._create_simple_picking_in(
|
||||
self.product_recovery, 10, location_2a_x1y1
|
||||
self.product_recovery, 10, self.location_2a_x1y1
|
||||
)
|
||||
put3.action_confirm()
|
||||
operation = self.shuttle._operation_for_mode()
|
||||
|
||||
@@ -37,6 +37,9 @@
|
||||
<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">
|
||||
@@ -86,11 +89,6 @@
|
||||
<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>
|
||||
|
||||
@@ -23,8 +23,68 @@
|
||||
<form position="attributes">
|
||||
<attribute name="string">Inventory Screen</attribute>
|
||||
</form>
|
||||
|
||||
<form position="inside">
|
||||
<field name="state" invisible="1"/>
|
||||
</form>
|
||||
|
||||
<button name="button_release" position="attributes">
|
||||
<attribute name="invisible">1</attribute>
|
||||
</button>
|
||||
|
||||
<xpath expr="//div[hasclass('o_shuttle_data')]" position="attributes" >
|
||||
<attribute name="attrs" >{'invisible': [('current_inventory_line_id', '=', False)]}</attribute>
|
||||
</xpath>
|
||||
<xpath expr="//div[hasclass('o_shuttle_data')]" position="inside" >
|
||||
<span>Not implemented</span>
|
||||
<!-- on the left of the screen -->
|
||||
<div class="o_shuttle_data_content o_shuttle_move">
|
||||
<div>
|
||||
<group>
|
||||
<field name="current_inventory_line_id" invisible="1"/>
|
||||
<div>
|
||||
<div>
|
||||
<field name="inventory_id" options="{'no_open': True}" class="mr8"/>
|
||||
</div>
|
||||
</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"/>
|
||||
<field name="package_id"/>
|
||||
<label for="quantity_input" string="Quantity" class="ml32"/>
|
||||
<div colspan="2" class="ml32">
|
||||
<h1 class="bg-primary o_shuttle_highlight">
|
||||
<field name="quantity_input"
|
||||
default_focus="1"
|
||||
class="oe_inline"/>
|
||||
<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"/>
|
||||
</group>
|
||||
<group>
|
||||
<div>
|
||||
<field name="tray_matrix"
|
||||
widget="location_tray_matrix"
|
||||
/>
|
||||
</div>
|
||||
</group>
|
||||
</div>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
Reference in New Issue
Block a user