mirror of
https://github.com/OCA/stock-logistics-warehouse.git
synced 2025-01-21 14:27:28 +02:00
@@ -299,7 +299,7 @@ class VerticalLiftOperationBase(models.AbstractModel):
|
||||
self.ensure_one()
|
||||
if not self.step() == "release":
|
||||
return
|
||||
self.next_step()
|
||||
return self.next_step()
|
||||
|
||||
def _render_product_packagings(self, product):
|
||||
if not product:
|
||||
|
||||
@@ -105,14 +105,15 @@ class VerticalLiftOperationPick(models.Model):
|
||||
|
||||
def button_release(self):
|
||||
"""Release the operation, go to the next"""
|
||||
super().button_release()
|
||||
res = super().button_release()
|
||||
if self.step() == "noop":
|
||||
# we don't need to release (close) the tray until we have reached
|
||||
# the last line: the release is implicit when a next line is
|
||||
# fetched
|
||||
self.shuttle_id.release_vertical_lift_tray()
|
||||
# sorry not sorry
|
||||
return self._rainbow_man()
|
||||
res = self._rainbow_man()
|
||||
return res
|
||||
|
||||
def button_skip(self):
|
||||
"""Skip the operation, go to the next"""
|
||||
|
||||
@@ -172,11 +172,12 @@ class VerticalLiftOperationPut(models.Model):
|
||||
self.current_move_line_id.fetch_vertical_lift_tray_dest()
|
||||
|
||||
def button_release(self):
|
||||
super().button_release()
|
||||
res = super().button_release()
|
||||
if self.count_move_lines_to_do_all() == 0:
|
||||
# we don't need to release (close) the tray until we have reached
|
||||
# the last line: the release is implicit when a next line is
|
||||
# fetched if the tray change
|
||||
self.shuttle_id.release_vertical_lift_tray()
|
||||
# sorry not sorry
|
||||
return self._rainbow_man()
|
||||
res = self._rainbow_man()
|
||||
return res
|
||||
|
||||
3
stock_vertical_lift_empty_tray_check/__init__.py
Normal file
3
stock_vertical_lift_empty_tray_check/__init__.py
Normal file
@@ -0,0 +1,3 @@
|
||||
# Copyright 2021 Camptocamp SA
|
||||
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html)
|
||||
from . import models
|
||||
18
stock_vertical_lift_empty_tray_check/__manifest__.py
Normal file
18
stock_vertical_lift_empty_tray_check/__manifest__.py
Normal file
@@ -0,0 +1,18 @@
|
||||
# Copyright 2021 Camptocamp SA
|
||||
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html)
|
||||
{
|
||||
"name": "Vertical Lift Empty Tray Check",
|
||||
"summary": "Checks if the tray is actually empty.",
|
||||
"version": "13.0.1.0.0",
|
||||
"category": "Stock",
|
||||
"author": "Camptocamp, Odoo Community Association (OCA)",
|
||||
"license": "AGPL-3",
|
||||
"depends": ["stock", "stock_vertical_lift"],
|
||||
"website": "https://github.com/OCA/stock-logistics-warehouse",
|
||||
"data": [
|
||||
"views/res_config_setting_views.xml",
|
||||
"views/vertical_lift_operation_pick_zero_check_views.xml",
|
||||
],
|
||||
"installable": True,
|
||||
"development_status": "Alpha",
|
||||
}
|
||||
5
stock_vertical_lift_empty_tray_check/models/__init__.py
Normal file
5
stock_vertical_lift_empty_tray_check/models/__init__.py
Normal file
@@ -0,0 +1,5 @@
|
||||
# Copyright 2021 Camptocamp SA
|
||||
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl)
|
||||
from . import res_config_settings
|
||||
from . import vertical_lift_operation_pick
|
||||
from . import vertical_lift_operation_pick_zero_check
|
||||
@@ -0,0 +1,11 @@
|
||||
from odoo import fields, models
|
||||
|
||||
|
||||
class ResConfigSettings(models.TransientModel):
|
||||
_inherit = "res.config.settings"
|
||||
|
||||
vertical_lift_empty_tray_check = fields.Boolean(
|
||||
"Vertical lift: Check Empty Tray",
|
||||
default=False,
|
||||
config_parameter="vertical_lift_empty_tray_check",
|
||||
)
|
||||
@@ -0,0 +1,46 @@
|
||||
# Copyright 2019 Camptocamp SA
|
||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
||||
|
||||
from odoo import _, models
|
||||
from odoo.tools import float_is_zero
|
||||
|
||||
|
||||
class VerticalLiftOperationPick(models.Model):
|
||||
_inherit = "vertical.lift.operation.pick"
|
||||
|
||||
def button_release(self):
|
||||
"""Release the operation, go to the next
|
||||
|
||||
By default it asks the user to inspect visually if the tray is empty.
|
||||
"""
|
||||
icp = self.env["ir.config_parameter"].sudo()
|
||||
tray_check = icp.get_param("vertical_lift_empty_tray_check")
|
||||
skip_zero_quantity_check = self.env.context.get("skip_zero_quantity_check")
|
||||
if not skip_zero_quantity_check and tray_check:
|
||||
uom_rounding = self.product_id.uom_id.rounding
|
||||
if float_is_zero(self.tray_qty, precision_rounding=uom_rounding):
|
||||
return self._check_zero_quantity()
|
||||
|
||||
return super().button_release()
|
||||
|
||||
def _check_zero_quantity(self):
|
||||
"""Show the wizard to check for real-zero quantity."""
|
||||
view = self.env.ref(
|
||||
"stock_vertical_lift_empty_tray_check."
|
||||
"vertical_lift_operation_pick_zero_check_view_form"
|
||||
)
|
||||
wizard_model = "vertical.lift.operation.pick.zero.check"
|
||||
wizard = self.env[wizard_model].create(
|
||||
{"vertical_lift_operation_pick_id": self.id}
|
||||
)
|
||||
return {
|
||||
"name": _("Is the tray empty?"),
|
||||
"type": "ir.actions.act_window",
|
||||
"view_mode": "form",
|
||||
"target": "new",
|
||||
"views": [(view.id, "form")],
|
||||
"view_id": view.id,
|
||||
"res_model": wizard_model,
|
||||
"res_id": wizard.id,
|
||||
"context": self.env.context,
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
# Copyright 2021 Camptocamp SA
|
||||
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl)
|
||||
|
||||
from odoo import _, fields, models
|
||||
|
||||
|
||||
class VerticalLiftOperationPickZeroCheck(models.TransientModel):
|
||||
_name = "vertical.lift.operation.pick.zero.check"
|
||||
_description = "Make sure the tray location is empty"
|
||||
|
||||
vertical_lift_operation_pick_id = fields.Many2one("vertical.lift.operation.pick")
|
||||
|
||||
def _get_data_from_operation(self):
|
||||
"""Return picking, location and product from the operation shuttle"""
|
||||
operation = self.vertical_lift_operation_pick_id
|
||||
|
||||
# If the move is split into several move lines, it is
|
||||
# moved to another picking, being a backorder of the
|
||||
# original one. We are always interested in the original
|
||||
# picking that was processed at first, so if the picking
|
||||
# is a backorder of another picking, we take that other one.
|
||||
picking = operation.picking_id.backorder_id or operation.picking_id
|
||||
location = operation.current_move_line_id.location_id
|
||||
product = operation.product_id
|
||||
return operation, picking, location, product
|
||||
|
||||
def button_confirm_empty(self):
|
||||
"""User confirms the tray location is empty
|
||||
|
||||
This is in accordance with what we expected, because we only
|
||||
call this action if we think the location is empty. We create
|
||||
an inventory adjustment that states that a zero-check was
|
||||
done for this location."""
|
||||
operation, picking, location, product = self._get_data_from_operation()
|
||||
inventory_name = _(f"Zero check in location: {location.complete_name}")
|
||||
inventory = (
|
||||
self.env["stock.inventory"]
|
||||
.sudo()
|
||||
.create(
|
||||
{
|
||||
"name": inventory_name,
|
||||
"product_ids": [(4, product.id)],
|
||||
"location_ids": [(4, location.id)],
|
||||
"line_ids": [
|
||||
(
|
||||
0,
|
||||
0,
|
||||
{
|
||||
"product_id": product.id,
|
||||
"product_qty": 0,
|
||||
"theoretical_qty": 0,
|
||||
"location_id": location.id,
|
||||
},
|
||||
),
|
||||
],
|
||||
}
|
||||
)
|
||||
)
|
||||
inventory.action_start()
|
||||
inventory.action_validate()
|
||||
|
||||
# Return to the execution of the release,
|
||||
# but without checking again if the tray is empty.
|
||||
return operation.with_context(skip_zero_quantity_check=True).button_release()
|
||||
|
||||
def button_confirm_not_empty(self):
|
||||
"""User confirms the tray location is not empty
|
||||
|
||||
This contradicts what we expected, because we only call this
|
||||
action if we think the location is empty. We create a draft
|
||||
inventory adjustment stating the mismatch.
|
||||
"""
|
||||
operation, picking, location, product = self._get_data_from_operation()
|
||||
inventory_name = _(
|
||||
f"{picking.name} zero check issue on location {location.complete_name}"
|
||||
)
|
||||
self.env["stock.inventory"].sudo().create(
|
||||
{
|
||||
"name": inventory_name,
|
||||
"product_ids": [(4, product.id)],
|
||||
"location_ids": [(4, location.id)],
|
||||
}
|
||||
)
|
||||
|
||||
# Return to the execution of the release,
|
||||
# but without checking again if the tray is empty.
|
||||
return operation.with_context(skip_zero_quantity_check=True).button_release()
|
||||
@@ -0,0 +1,4 @@
|
||||
General
|
||||
~~~~~~~
|
||||
|
||||
In Inventory Settings, you must have activated the option: *Check Empty Tray*
|
||||
@@ -0,0 +1 @@
|
||||
* Carlos Serra-Toro <carlos.serra@camptocamp.com>
|
||||
@@ -0,0 +1,5 @@
|
||||
When a tray is released, and the system thinks it is empty,
|
||||
it prompts the user to actually check that it is empty or not.
|
||||
In any case, an inventory adjustment is done stating the
|
||||
situation: posted to zero if the tray is actually empty, and
|
||||
set to draft is it is not empty.
|
||||
3
stock_vertical_lift_empty_tray_check/tests/__init__.py
Normal file
3
stock_vertical_lift_empty_tray_check/tests/__init__.py
Normal file
@@ -0,0 +1,3 @@
|
||||
# Copyright 2021 Camptocamp SA
|
||||
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html)
|
||||
from . import test_pick
|
||||
84
stock_vertical_lift_empty_tray_check/tests/test_pick.py
Normal file
84
stock_vertical_lift_empty_tray_check/tests/test_pick.py
Normal file
@@ -0,0 +1,84 @@
|
||||
# Copyright 2021 Camptocamp SA
|
||||
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html)
|
||||
|
||||
from odoo.addons.stock_vertical_lift.tests.common import VerticalLiftCase
|
||||
|
||||
|
||||
class TestPick(VerticalLiftCase):
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super().setUpClass()
|
||||
cls.env = cls.env(context=dict(cls.env.context, tracking_disable=True))
|
||||
cls.picking_out = cls.env.ref(
|
||||
"stock_vertical_lift.stock_picking_out_demo_vertical_lift_1"
|
||||
)
|
||||
cls.env["ir.config_parameter"].sudo().set_param(
|
||||
"vertical_lift_empty_tray_check", True
|
||||
)
|
||||
|
||||
def _test_location_empty_common(self, operation, tray_is_empty):
|
||||
"""Common part for tests checking the tray location is empty
|
||||
|
||||
Returns the new inventory adjustment created."""
|
||||
self.assertEqual(operation.state, "scan_destination")
|
||||
move_line = operation.current_move_line_id
|
||||
customers_location = self.env.ref("stock.stock_location_customers")
|
||||
customers_location.barcode = "CUSTOMERS"
|
||||
operation.on_barcode_scanned(customers_location.barcode)
|
||||
self.assertEqual(move_line.location_dest_id, customers_location)
|
||||
self.assertEqual(operation.state, "save")
|
||||
operation.button_save()
|
||||
self.assertEqual(operation.state, "release")
|
||||
self.assertEqual(operation.tray_qty, 0)
|
||||
|
||||
old_inventories = self.env["stock.inventory"].search([])
|
||||
|
||||
res_dict = operation.button_release()
|
||||
wizard = self.env[(res_dict.get("res_model"))].browse(res_dict.get("res_id"))
|
||||
wizard = wizard.with_context(
|
||||
active_id=operation.id, active_model=operation._name
|
||||
)
|
||||
if tray_is_empty:
|
||||
wizard.button_confirm_empty()
|
||||
else:
|
||||
wizard.button_confirm_not_empty()
|
||||
|
||||
new_inventory = self.env["stock.inventory"].search([]) - old_inventories
|
||||
return new_inventory
|
||||
|
||||
def test_location_empty_is_empty(self):
|
||||
""" Location is indicated as being empty, and it is"""
|
||||
operation = self._open_screen("pick")
|
||||
tray_location = operation.tray_location_id
|
||||
tray_product = operation.current_move_line_id.product_id
|
||||
inventory = self._test_location_empty_common(operation, tray_is_empty=True)
|
||||
|
||||
self.assertEqual(len(inventory), 1)
|
||||
self.assertEqual(inventory.state, "done")
|
||||
self.assertEqual(
|
||||
inventory.name,
|
||||
"Zero check in location: {}".format(tray_location.complete_name),
|
||||
)
|
||||
self.assertEqual(len(inventory.line_ids), 1)
|
||||
self.assertEqual(inventory.line_ids[0].product_id, tray_product)
|
||||
self.assertEqual(inventory.line_ids[0].location_id, tray_location)
|
||||
self.assertEqual(inventory.line_ids[0].product_qty, 0)
|
||||
self.assertEqual(inventory.line_ids[0].theoretical_qty, 0)
|
||||
|
||||
def test_location_empty_is_not_empty(self):
|
||||
""" Location is indicated as being empty, but it is not.
|
||||
"""
|
||||
operation = self._open_screen("pick")
|
||||
tray_location = operation.tray_location_id
|
||||
tray_product = operation.current_move_line_id.product_id
|
||||
inventory = self._test_location_empty_common(operation, tray_is_empty=False)
|
||||
self.assertEqual(len(inventory), 1)
|
||||
self.assertEqual(inventory.state, "draft")
|
||||
self.assertEqual(
|
||||
inventory.name,
|
||||
"{} zero check issue on location {}".format(
|
||||
self.picking_out.name, tray_location.complete_name,
|
||||
),
|
||||
)
|
||||
self.assertEqual(inventory.product_ids, tray_product)
|
||||
self.assertEqual(inventory.location_ids, tray_location)
|
||||
@@ -0,0 +1,31 @@
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<odoo>
|
||||
<data>
|
||||
<record id="res_config_settings_view_form" model="ir.ui.view">
|
||||
<field name="name">res.config.settings.view.form.inherit.stock</field>
|
||||
<field name="model">res.config.settings</field>
|
||||
<field name="priority" eval="30" />
|
||||
<field name="inherit_id" ref="stock.res_config_settings_view_form" />
|
||||
<field name="arch" type="xml">
|
||||
<xpath
|
||||
expr="//field[@name='group_stock_adv_location']/ancestor::div[hasclass('o_setting_box')]"
|
||||
position="after"
|
||||
>
|
||||
<div class="col-12 col-lg-6 o_setting_box">
|
||||
<div class="o_setting_left_pane">
|
||||
<field name="vertical_lift_empty_tray_check" />
|
||||
</div>
|
||||
<div class="o_setting_right_pane">
|
||||
<label for="vertical_lift_empty_tray_check" />
|
||||
<div class="text-muted">
|
||||
If checked and the system thinks the vertical tray is
|
||||
empty, the operator will be asked to explicitly check
|
||||
if this is the case or not
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
</data>
|
||||
</odoo>
|
||||
@@ -0,0 +1,34 @@
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<!-- Copyright 2021 Camptocamp SA
|
||||
License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl) -->
|
||||
<odoo>
|
||||
<record id="vertical_lift_operation_pick_zero_check_view_form" model="ir.ui.view">
|
||||
<field name="name">vertical.lift.operation.pick.zero.check.view.form</field>
|
||||
<field name="model">vertical.lift.operation.pick.zero.check</field>
|
||||
<field name="arch" type="xml">
|
||||
<form>
|
||||
<div class="row">
|
||||
<div class="col-6">
|
||||
<button
|
||||
name="button_confirm_empty"
|
||||
string="Tray Empty"
|
||||
type="object"
|
||||
class="btn-success"
|
||||
style="padding: 1em; font-size: 2em; text-transform: uppercase;"
|
||||
/>
|
||||
</div>
|
||||
<div class="col-6">
|
||||
<button
|
||||
name="button_confirm_not_empty"
|
||||
string="Tray Not Empty"
|
||||
type="object"
|
||||
class="btn-danger"
|
||||
style="padding: 1em; font-size: 2em; text-transform: uppercase;"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<footer />
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
</odoo>
|
||||
Reference in New Issue
Block a user