Merge PR #1106 into 13.0

Signed-off-by simahawk
This commit is contained in:
OCA-git-bot
2021-03-12 12:37:43 +00:00
16 changed files with 339 additions and 5 deletions

View File

@@ -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:

View File

@@ -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"""

View File

@@ -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

View 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

View 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",
}

View 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

View File

@@ -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",
)

View File

@@ -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,
}

View File

@@ -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()

View File

@@ -0,0 +1,4 @@
General
~~~~~~~
In Inventory Settings, you must have activated the option: *Check Empty Tray*

View File

@@ -0,0 +1 @@
* Carlos Serra-Toro <carlos.serra@camptocamp.com>

View File

@@ -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.

View 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

View 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)

View File

@@ -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>

View File

@@ -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>