diff --git a/setup/stock_vertical_lift_storage_type/odoo/addons/stock_vertical_lift_storage_type b/setup/stock_vertical_lift_storage_type/odoo/addons/stock_vertical_lift_storage_type new file mode 120000 index 000000000..fc17491a1 --- /dev/null +++ b/setup/stock_vertical_lift_storage_type/odoo/addons/stock_vertical_lift_storage_type @@ -0,0 +1 @@ +../../../../stock_vertical_lift_storage_type \ No newline at end of file diff --git a/setup/stock_vertical_lift_storage_type/setup.py b/setup/stock_vertical_lift_storage_type/setup.py new file mode 100644 index 000000000..28c57bb64 --- /dev/null +++ b/setup/stock_vertical_lift_storage_type/setup.py @@ -0,0 +1,6 @@ +import setuptools + +setuptools.setup( + setup_requires=['setuptools-odoo'], + odoo_addon=True, +) diff --git a/stock_vertical_lift_storage_type/README.rst b/stock_vertical_lift_storage_type/README.rst new file mode 100644 index 000000000..7e11f81dd --- /dev/null +++ b/stock_vertical_lift_storage_type/README.rst @@ -0,0 +1,97 @@ +============================ +Vertical Lift - Storage Type +============================ + +.. !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! This file is generated by oca-gen-addon-readme !! + !! changes will be overwritten. !! + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + +.. |badge1| image:: https://img.shields.io/badge/maturity-Alpha-red.png + :target: https://odoo-community.org/page/development-status + :alt: Alpha +.. |badge2| image:: https://img.shields.io/badge/licence-AGPL--3-blue.png + :target: http://www.gnu.org/licenses/agpl-3.0-standalone.html + :alt: License: AGPL-3 +.. |badge3| image:: https://img.shields.io/badge/github-OCA%2Fstock--logistics--warehouse-lightgray.png?logo=github + :target: https://github.com/OCA/stock-logistics-warehouse/tree/14.0/stock_vertical_lift_storage_type + :alt: OCA/stock-logistics-warehouse +.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png + :target: https://translation.odoo-community.org/projects/stock-logistics-warehouse-14-0/stock-logistics-warehouse-14-0-stock_vertical_lift_storage_type + :alt: Translate me on Weblate +.. |badge5| image:: https://img.shields.io/badge/runbot-Try%20me-875A7B.png + :target: https://runbot.odoo-community.org/runbot/153/14.0 + :alt: Try me on Runbot + +|badge1| |badge2| |badge3| |badge4| |badge5| + +Compatibility layer between Stock Vertical Lift and Putaway Storage Types (OCA/wms). + +In the vertical lift's Putaway screen, when a good is scanned for a putaway, the +user has to scan the tray type of the corresponding size, so an empty place in a +matching tray is found. When we use storage types, we should know what tray is +compatible with the storage type. + +Changes with this module: + +* The storage types of trays cannot be selected in the locations form, they have + to be set in the Tray types. +* In the lift put-away screen, when a package has a storage type, the user isn't + asked to scan a tray type, instead, the putaway of the Package Storage Type is + applied. + +.. IMPORTANT:: + This is an alpha version, the data model and design can change at any time without warning. + Only for development or testing purpose, do not use in production. + `More details on development status `_ + +**Table of contents** + +.. contents:: + :local: + +Bug Tracker +=========== + +Bugs are tracked on `GitHub Issues `_. +In case of trouble, please check there if your issue has already been reported. +If you spotted it first, help us smashing it by providing a detailed and welcomed +`feedback `_. + +Do not contact contributors directly about support or help with technical issues. + +Credits +======= + +Authors +~~~~~~~ + +* Camptocamp + +Contributors +~~~~~~~~~~~~ + +* Guewen Baconnier +* `Trobz `_: + +Other credits +~~~~~~~~~~~~~ + +The migration of this module from 13.0 to 14.0 was financially supported by Camptocamp + +Maintainers +~~~~~~~~~~~ + +This module is maintained by the OCA. + +.. image:: https://odoo-community.org/logo.png + :alt: Odoo Community Association + :target: https://odoo-community.org + +OCA, or the Odoo Community Association, is a nonprofit organization whose +mission is to support the collaborative development of Odoo features and +promote its widespread use. + +This module is part of the `OCA/stock-logistics-warehouse `_ project on GitHub. + +You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute. diff --git a/stock_vertical_lift_storage_type/__init__.py b/stock_vertical_lift_storage_type/__init__.py new file mode 100644 index 000000000..0650744f6 --- /dev/null +++ b/stock_vertical_lift_storage_type/__init__.py @@ -0,0 +1 @@ +from . import models diff --git a/stock_vertical_lift_storage_type/__manifest__.py b/stock_vertical_lift_storage_type/__manifest__.py new file mode 100644 index 000000000..e91ffacd9 --- /dev/null +++ b/stock_vertical_lift_storage_type/__manifest__.py @@ -0,0 +1,21 @@ +# Copyright 2020 Camptocamp SA +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). +{ + "name": "Vertical Lift - Storage Type", + "summary": "Compatibility layer for storage types on vertical lifts", + "version": "14.0.1.0.0", + "category": "Stock", + "author": "Camptocamp, Odoo Community Association (OCA)", + "license": "AGPL-3", + "depends": [ + "stock_vertical_lift", # OCA/stock-logistics-warehouse + "stock_storage_type", # OCA/wms + ], + "website": "https://github.com/OCA/stock-logistics-warehouse", + "data": [ + "views/stock_location_tray_type_views.xml", + "views/stock_location_views.xml", + ], + "installable": True, + "development_status": "Alpha", +} diff --git a/stock_vertical_lift_storage_type/i18n/stock_vertical_lift_storage_type.pot b/stock_vertical_lift_storage_type/i18n/stock_vertical_lift_storage_type.pot new file mode 100644 index 000000000..d4546841e --- /dev/null +++ b/stock_vertical_lift_storage_type/i18n/stock_vertical_lift_storage_type.pot @@ -0,0 +1,68 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * stock_vertical_lift_storage_type +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 13.0\n" +"Report-Msgid-Bugs-To: \n" +"Last-Translator: \n" +"Language-Team: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: \n" + +#. module: stock_vertical_lift_storage_type +#: code:addons/stock_vertical_lift_storage_type/models/stock_location.py:0 +#, python-format +msgid "" +"Error creating '{}': Location storage type must be set on the tray type" +msgstr "" + +#. module: stock_vertical_lift_storage_type +#: code:addons/stock_vertical_lift_storage_type/models/stock_location.py:0 +#, python-format +msgid "Error updating {}: Location storage type must be set on the tray type" +msgstr "" + +#. module: stock_vertical_lift_storage_type +#: model:ir.model,name:stock_vertical_lift_storage_type.model_stock_location +msgid "Inventory Locations" +msgstr "" + +#. module: stock_vertical_lift_storage_type +#: model:ir.model.fields,field_description:stock_vertical_lift_storage_type.field_stock_location_tray_type__location_storage_type_ids +msgid "Location Storage Type" +msgstr "" + +#. module: stock_vertical_lift_storage_type +#: model:ir.model.fields,help:stock_vertical_lift_storage_type.field_stock_location_tray_type__location_storage_type_ids +msgid "Location storage types applied on the location using this tray type." +msgstr "" + +#. module: stock_vertical_lift_storage_type +#: code:addons/stock_vertical_lift_storage_type/models/vertical_lift_operation_put.py:0 +#, python-format +msgid "No free space found for storage type '{}' in shuttle '{}'" +msgstr "" + +#. module: stock_vertical_lift_storage_type +#: model_terms:ir.ui.view,arch_db:stock_vertical_lift_storage_type.view_stock_location_tray_type_form +msgid "Put-Away" +msgstr "" + +#. module: stock_vertical_lift_storage_type +#: model:ir.model,name:stock_vertical_lift_storage_type.model_stock_quant +msgid "Quants" +msgstr "" + +#. module: stock_vertical_lift_storage_type +#: model:ir.model,name:stock_vertical_lift_storage_type.model_stock_location_tray_type +msgid "Stock Location Tray Type" +msgstr "" + +#. module: stock_vertical_lift_storage_type +#: model:ir.model,name:stock_vertical_lift_storage_type.model_vertical_lift_operation_put +msgid "Vertical Lift Operation Put" +msgstr "" diff --git a/stock_vertical_lift_storage_type/models/__init__.py b/stock_vertical_lift_storage_type/models/__init__.py new file mode 100644 index 000000000..cb2169a09 --- /dev/null +++ b/stock_vertical_lift_storage_type/models/__init__.py @@ -0,0 +1,4 @@ +from . import stock_location +from . import stock_location_tray_type +from . import stock_quant +from . import vertical_lift_operation_put diff --git a/stock_vertical_lift_storage_type/models/stock_location.py b/stock_vertical_lift_storage_type/models/stock_location.py new file mode 100644 index 000000000..38b086abe --- /dev/null +++ b/stock_vertical_lift_storage_type/models/stock_location.py @@ -0,0 +1,50 @@ +# Copyright 2020 Camptocamp SA +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from odoo import _, api, exceptions, models + + +class StockLocation(models.Model): + _inherit = "stock.location" + + @api.model_create_multi + def create(self, vals_list): + if not self.env.context.get("_sync_tray_type"): + for vals in vals_list: + if vals.get("tray_type_id") and vals.get("location_storage_type_ids"): + raise exceptions.UserError( + _( + "Error creating '{}': Location storage" + " type must be set on the tray type" + ).format(vals.get("name")) + ) + + records = super().create(vals_list) + records._sync_tray_type_storage_types() + return records + + def write(self, values): + if not self.env.context.get("_sync_tray_type"): + if values.get("location_storage_type_ids"): + if values.get("tray_type_id"): + has_tray_type = self + else: + has_tray_type = self.filtered("tray_type_id") + if has_tray_type: + raise exceptions.UserError( + _( + "Error updating {}: Location storage" + " type must be set on the tray type" + ).format(", ".join(has_tray_type.mapped("name"))) + ) + res = super().write(values) + if values.get("tray_type_id"): + self._sync_tray_type_storage_types() + return res + + def _sync_tray_type_storage_types(self): + for location in self.with_context(_sync_tray_type=True): + if not location.tray_type_id: + continue + storage_types = location.tray_type_id.location_storage_type_ids + location.write({"location_storage_type_ids": [(6, 0, storage_types.ids)]}) diff --git a/stock_vertical_lift_storage_type/models/stock_location_tray_type.py b/stock_vertical_lift_storage_type/models/stock_location_tray_type.py new file mode 100644 index 000000000..15fbcff51 --- /dev/null +++ b/stock_vertical_lift_storage_type/models/stock_location_tray_type.py @@ -0,0 +1,29 @@ +# Copyright 2020 Camptocamp SA +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from odoo import fields, models + + +class StockLocationTrayType(models.Model): + _inherit = "stock.location.tray.type" + + location_storage_type_ids = fields.Many2many( + comodel_name="stock.location.storage.type", + help="Location storage types applied on the location using " "this tray type.", + ) + + def write(self, values): + res = super().write(values) + if values.get("location_storage_type_ids"): + self._sync_location_storage_type_ids() + return res + + def _sync_location_storage_type_ids(self): + for tray_type in self: + tray_type.location_ids.with_context(_sync_tray_type=True).write( + { + "location_storage_type_ids": [ + (6, 0, tray_type.location_storage_type_ids.ids) + ] + } + ) diff --git a/stock_vertical_lift_storage_type/models/stock_quant.py b/stock_vertical_lift_storage_type/models/stock_quant.py new file mode 100644 index 000000000..90994ee81 --- /dev/null +++ b/stock_vertical_lift_storage_type/models/stock_quant.py @@ -0,0 +1,18 @@ +# Copyright 2020 Camptocamp SA +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl) +from odoo import models + + +class StockQuant(models.Model): + + _inherit = "stock.quant" + + def _check_storage_type(self): + # disable the checks when placing goods in vertical lift cells: + # we still want to benefit of the storage type rules to select + # a destination, but we want to allow selecting a different tray + # type + self = self.filtered( + lambda quant: quant.location_id.vertical_lift_kind != "cell" + ) + super()._check_storage_type() diff --git a/stock_vertical_lift_storage_type/models/vertical_lift_operation_put.py b/stock_vertical_lift_storage_type/models/vertical_lift_operation_put.py new file mode 100644 index 000000000..054295ae5 --- /dev/null +++ b/stock_vertical_lift_storage_type/models/vertical_lift_operation_put.py @@ -0,0 +1,123 @@ +# Copyright 2020 Camptocamp SA +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from odoo import _, models + + +class VerticalLiftOperationPut(models.Model): + _inherit = "vertical.lift.operation.put" + + # In the base module, when a good is scanned for a put-away, the user must + # then scan a tray type, which will be used to find an available cell of + # this type. When we use storage types (OCA/wms::stock_storage_type), we + # shouldn't need to scan a tray type if we already have a storage type, + # that could be used to find an available cell location for this type + # (applying all the possible restrictions from storage type). + + def _transitions(self): + transitions = super()._transitions() + updated_transitions = [] + for transition in transitions: + states = (transition.current_state, transition.next_state) + if states == ("scan_tray_type", "save"): + # insert new transitions just before the normal transition + # scanning the tray type, that will bypass it when we have + # a storage type + updated_transitions.append( + self.Transition( + "scan_tray_type", + "save", + lambda self: self._has_storage_type() + and self._putaway_with_storage_type(), + # this is the trick that makes the transition applies + # its function and directly jumps to save + direct_eval=True, + ) + ) + updated_transitions.append( + self.Transition( + "scan_tray_type", + "scan_source", + # the transition above returned False because it could + # not find a free space, in that case, abort the + # put-away for this line in this shuttle + lambda self: self._has_storage_type() + and self._put_away_with_storage_type_failed() + and self.clear_current_move_line(), + # this is the trick that makes the transition applies + # its function and directly jumps to save + direct_eval=True, + ) + ) + # if none of the 2 transitions above is applied (because + # self._has_storage_type() is False), the state remains + # `scan_tray_type`, for the base transition doesn't have + # `direct_eval=True` + updated_transitions.append(transition) + + return tuple(updated_transitions) + + def _has_storage_type_domain(self): + move_line = self.current_move_line_id + package_storage_type = move_line.package_id.package_storage_type_id + # When a put-away is done based on the package's storage type and no + # destination is found, we can have 2 reasons: + # + # 1. No location is available according to the storage types rules, + # for instance because they are full + # 2. The storage type of the package doesn't have any location + # configured in the shuttle's locations + # + # We want to differentiate the 2 cases and handle them differently. + # For 2. we consider the storage type is not meant to be in any + # shuttle's tray but the user still tries: act the same way as if we + # had no storage type at all, the user has to scan the tray type. + # For 1. we try to find a destination location and if none is found, + # a notification indicates that the shuttle is full. + # In any case, the user should always be able to scan a different + # tray type. + return [ + ("id", "!=", self.location_id.id), + ("id", "child_of", self.location_id.id), + ( + "allowed_location_storage_type_ids", + "in", + package_storage_type.location_storage_type_ids.ids, + ), + ] + + def _has_storage_type(self): + domain = self._has_storage_type_domain() + # we don't care about order and count, only if we have at least one + # configured location for the storage type: sorting by id and limit by + # 1 will give the best explain plan + compatible_locations = self.env["stock.location"].search( + domain, limit=1, order="id" + ) + return bool(compatible_locations) + + def _putaway_with_storage_type(self): + move_line = self.current_move_line_id + # Trigger the put-away application to place it somewhere inside + # the current shuttle's location. + new_destination = move_line.location_dest_id._get_pack_putaway_strategy( + self.location_id, move_line.package_id.quant_ids, move_line.product_id + ) + if new_destination and new_destination.vertical_lift_kind == "cell": + move_line.location_dest_id = new_destination + move_line.package_level_id.location_dest_id = new_destination + self.fetch_tray() + return True + # no destination found: all the trays for this tray type are full, or rejected + # by the location storage type rules + return False + + def _put_away_with_storage_type_failed(self): + move_line = self.current_move_line_id + storage_type = move_line.package_id.package_storage_type_id + self.env.user.notify_warning( + _("No free space found for storage type '{}' in shuttle '{}'").format( + storage_type.name, self.name + ) + ) + return True diff --git a/stock_vertical_lift_storage_type/readme/CONTRIBUTORS.rst b/stock_vertical_lift_storage_type/readme/CONTRIBUTORS.rst new file mode 100644 index 000000000..dc8669b25 --- /dev/null +++ b/stock_vertical_lift_storage_type/readme/CONTRIBUTORS.rst @@ -0,0 +1,2 @@ +* Guewen Baconnier +* `Trobz `_: diff --git a/stock_vertical_lift_storage_type/readme/CREDITS.rst b/stock_vertical_lift_storage_type/readme/CREDITS.rst new file mode 100644 index 000000000..f37ebe757 --- /dev/null +++ b/stock_vertical_lift_storage_type/readme/CREDITS.rst @@ -0,0 +1 @@ +The migration of this module from 13.0 to 14.0 was financially supported by Camptocamp diff --git a/stock_vertical_lift_storage_type/readme/DESCRIPTION.rst b/stock_vertical_lift_storage_type/readme/DESCRIPTION.rst new file mode 100644 index 000000000..c8ee6394c --- /dev/null +++ b/stock_vertical_lift_storage_type/readme/DESCRIPTION.rst @@ -0,0 +1,14 @@ +Compatibility layer between Stock Vertical Lift and Putaway Storage Types (OCA/wms). + +In the vertical lift's Putaway screen, when a good is scanned for a putaway, the +user has to scan the tray type of the corresponding size, so an empty place in a +matching tray is found. When we use storage types, we should know what tray is +compatible with the storage type. + +Changes with this module: + +* The storage types of trays cannot be selected in the locations form, they have + to be set in the Tray types. +* In the lift put-away screen, when a package has a storage type, the user isn't + asked to scan a tray type, instead, the putaway of the Package Storage Type is + applied. diff --git a/stock_vertical_lift_storage_type/static/description/icon.png b/stock_vertical_lift_storage_type/static/description/icon.png new file mode 100644 index 000000000..3a0328b51 Binary files /dev/null and b/stock_vertical_lift_storage_type/static/description/icon.png differ diff --git a/stock_vertical_lift_storage_type/static/description/index.html b/stock_vertical_lift_storage_type/static/description/index.html new file mode 100644 index 000000000..ecbc37765 --- /dev/null +++ b/stock_vertical_lift_storage_type/static/description/index.html @@ -0,0 +1,443 @@ + + + + + + +Vertical Lift - Storage Type + + + +
+

Vertical Lift - Storage Type

+ + +

Alpha License: AGPL-3 OCA/stock-logistics-warehouse Translate me on Weblate Try me on Runbot

+

Compatibility layer between Stock Vertical Lift and Putaway Storage Types (OCA/wms).

+

In the vertical lift’s Putaway screen, when a good is scanned for a putaway, the +user has to scan the tray type of the corresponding size, so an empty place in a +matching tray is found. When we use storage types, we should know what tray is +compatible with the storage type.

+

Changes with this module:

+
    +
  • The storage types of trays cannot be selected in the locations form, they have +to be set in the Tray types.
  • +
  • In the lift put-away screen, when a package has a storage type, the user isn’t +asked to scan a tray type, instead, the putaway of the Package Storage Type is +applied.
  • +
+
+

Important

+

This is an alpha version, the data model and design can change at any time without warning. +Only for development or testing purpose, do not use in production. +More details on development status

+
+

Table of contents

+ +
+

Bug Tracker

+

Bugs are tracked on GitHub Issues. +In case of trouble, please check there if your issue has already been reported. +If you spotted it first, help us smashing it by providing a detailed and welcomed +feedback.

+

Do not contact contributors directly about support or help with technical issues.

+
+
+

Credits

+
+

Authors

+
    +
  • Camptocamp
  • +
+
+
+

Contributors

+ +
+
+

Other credits

+

The migration of this module from 13.0 to 14.0 was financially supported by Camptocamp

+
+
+

Maintainers

+

This module is maintained by the OCA.

+Odoo Community Association +

OCA, or the Odoo Community Association, is a nonprofit organization whose +mission is to support the collaborative development of Odoo features and +promote its widespread use.

+

This module is part of the OCA/stock-logistics-warehouse project on GitHub.

+

You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.

+
+
+
+ + diff --git a/stock_vertical_lift_storage_type/tests/__init__.py b/stock_vertical_lift_storage_type/tests/__init__.py new file mode 100644 index 000000000..91db0a261 --- /dev/null +++ b/stock_vertical_lift_storage_type/tests/__init__.py @@ -0,0 +1,3 @@ +from . import test_put +from . import test_stock_location +from . import test_tray_type diff --git a/stock_vertical_lift_storage_type/tests/common.py b/stock_vertical_lift_storage_type/tests/common.py new file mode 100644 index 000000000..8948f7c30 --- /dev/null +++ b/stock_vertical_lift_storage_type/tests/common.py @@ -0,0 +1,27 @@ +# Copyright 2020 Camptocamp SA +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from odoo.addons.stock_vertical_lift.tests.common import VerticalLiftCase + + +class TrayTypeCommonCase(VerticalLiftCase): + @classmethod + def setUpClass(cls): + super().setUpClass() + cls.TrayType = cls.env["stock.location.tray.type"] + cls.location_2b = cls.env.ref( + "stock_vertical_lift." "stock_location_vertical_lift_demo_tray_2b" + ) + cls.location_2d = cls.env.ref( + "stock_vertical_lift." "stock_location_vertical_lift_demo_tray_2d" + ) + LocStorageType = cls.env["stock.location.storage.type"] + cls.location_storage_type_buffer = LocStorageType.create( + {"name": "VLift Buffer"} + ) + cls.location_storage_type_small_8x = LocStorageType.create( + {"name": "Small 8x", "only_empty": True} + ) + cls.storage_types = ( + cls.location_storage_type_small_8x | cls.location_storage_type_buffer + ) diff --git a/stock_vertical_lift_storage_type/tests/test_put.py b/stock_vertical_lift_storage_type/tests/test_put.py new file mode 100644 index 000000000..0d98c52b6 --- /dev/null +++ b/stock_vertical_lift_storage_type/tests/test_put.py @@ -0,0 +1,165 @@ +# Copyright 2020 Camptocamp SA +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from odoo.addons.stock_vertical_lift.tests.common import VerticalLiftCase + + +class TestPut(VerticalLiftCase): + @classmethod + def setUpClass(cls): + super().setUpClass() + cls.wh = cls.env.ref("stock.warehouse0") + cls.wh.wh_input_stock_loc_id.active = True + cls.wh.int_type_id.active = True + + # used on the vertical lift top level + LocStorageType = cls.env["stock.location.storage.type"] + cls.location_storage_type_buffer = LocStorageType.create( + {"name": "VLift Buffer"} + ) + cls.location_storage_type_small_8x = LocStorageType.create( + {"name": "Small 8x", "only_empty": True} + ) + + PackageStorageType = cls.env["stock.package.storage.type"] + # package storage type used only to putaway a package temporarily in + # the vertical lift view location before being put in a shuttle, which + # will not be configured in any shuttle's locations, we'll use when + # the operator knows that a good has to go in a shuttle, but doesn't + # know yet in which tray type: when the first putaway is done, the good + # stays in the vertical lift view (above the shuttles), then, when the + # user scans the package in a shuttle, they have to scan a tray type. + cls.package_storage_type_buffer = PackageStorageType.create( + { + "name": "VLift Box", + "location_storage_type_ids": [ + (4, cls.location_storage_type_buffer.id), + ], + } + ) + # storage type used for Tray 1A, user won't have to scan a tray type + # when this storage type is already set on the package + cls.package_storage_type_small_8x = PackageStorageType.create( + { + "name": "Small 8x", + "location_storage_type_ids": [ + (4, cls.location_storage_type_small_8x.id), + (4, cls.location_storage_type_buffer.id), + ], + } + ) + + cls.location_shuttle1 = cls.shuttle.location_id + cls.vertical_lift_loc.location_storage_type_ids = ( + cls.location_storage_type_buffer + ) + cls.vertical_lift_loc.pack_putaway_strategy = "none" + cls.location_shuttle1.location_storage_type_ids = ( + cls.location_storage_type_small_8x + ) + cls.location_shuttle1.pack_putaway_strategy = "ordered_locations" + + cls.env["stock.storage.location.sequence"].create( + { + "package_storage_type_id": cls.package_storage_type_small_8x.id, + "sequence": 1, + "location_id": cls.vertical_lift_loc.id, + } + ) + cls.env["stock.storage.location.sequence"].create( + { + "package_storage_type_id": cls.package_storage_type_small_8x.id, + "sequence": 2, + "location_id": cls.location_shuttle1.id, + } + ) + + cls.package = cls.env["stock.quant.package"].create({}) + cls._update_qty_in_location( + cls.wh.wh_input_stock_loc_id, cls.product_socks, 10, package=cls.package + ) + + cls.int_picking = cls._create_simple_picking_int( + cls.product_socks, 10, cls.vertical_lift_loc + ) + cls.int_picking.action_confirm() + cls.int_picking.action_assign() + + @classmethod + def _create_simple_picking_int(cls, product, quantity, dest_location): + return cls.env["stock.picking"].create( + { + "picking_type_id": cls.wh.int_type_id.id, + "location_id": cls.wh.wh_input_stock_loc_id.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": cls.wh.int_type_id.id, + "location_id": cls.wh.wh_input_stock_loc_id.id, + "location_dest_id": dest_location.id, + }, + ) + ], + } + ) + + def test_storage_type_put_away(self): + self.package.package_storage_type_id = self.package_storage_type_small_8x + move_line = self.int_picking.move_line_ids + self.assertEqual(move_line.location_dest_id, self.vertical_lift_loc) + self.assertEqual( + move_line.package_level_id.location_dest_id, self.vertical_lift_loc + ) + + operation = self._open_screen("put") + # we begin with an empty screen, user has to scan a package, product, + # or lot + self.assertEqual(operation.state, "scan_source") + operation.on_barcode_scanned(self.package.name) + + self.assertEqual(operation.current_move_line_id, move_line) + # the dest location was Vertical Lift, it has been change to Vertical + # Lift/Shuttle 1, and the computation from there took the first cell + # available, we should be the pos x1 and y1 in the tray A. + self.assertEqual(move_line.location_dest_id, self.location_1a_x1y1) + self.assertEqual( + move_line.package_level_id.location_dest_id, self.location_1a_x1y1 + ) + + # the state goes straight to "save", as we don't need to scan the tray type + # when a putaway is available + self.assertEqual(operation.state, "save") + + def test_storage_type_not_configured(self): + # if we do have a storage type, but the storage type is not used in the + # shuttle (although it may be used on the lift view location, so we can have an + # initial putaway rule that puts the package in the lift view location first), + # we have to ask to scan a tray type + self.package.package_storage_type_id = self.package_storage_type_buffer + move_line = self.int_picking.move_line_ids + self.assertEqual(move_line.location_dest_id, self.vertical_lift_loc) + self.assertEqual( + move_line.package_level_id.location_dest_id, self.vertical_lift_loc + ) + + operation = self._open_screen("put") + # we begin with an empty screen, user has to scan a package, product, + # or lot + self.assertEqual(operation.state, "scan_source") + operation.on_barcode_scanned(self.package.name) + + self.assertEqual(operation.current_move_line_id, move_line) + # it stays here, as the put-away has no rule to put the "buffer box" in + # any location + self.assertEqual(move_line.location_dest_id, self.vertical_lift_loc) + + # and the user has to scan a tray type (so we are back to the normal + # flow, tested in stock_vertical_lift) + self.assertEqual(operation.state, "scan_tray_type") diff --git a/stock_vertical_lift_storage_type/tests/test_stock_location.py b/stock_vertical_lift_storage_type/tests/test_stock_location.py new file mode 100644 index 000000000..082ac1e49 --- /dev/null +++ b/stock_vertical_lift_storage_type/tests/test_stock_location.py @@ -0,0 +1,90 @@ +# Copyright 2020 Camptocamp SA +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from odoo import exceptions + +from .common import TrayTypeCommonCase + + +class TestTrayTypeLocation(TrayTypeCommonCase): + @classmethod + def setUpClass(cls): + super().setUpClass() + cls.tray_type = cls.env.ref( + "stock_location_tray.stock_location_tray_type_small_8x" + ) + cls.tray_type.write( + {"location_storage_type_ids": [(6, 0, cls.storage_types.ids)]} + ) + cls.locations = cls.location_2a | cls.location_2b + + def test_location_create_sync(self): + locations = self.env["stock.location"].create( + [ + { + "name": "tray test 1", + "location_id": self.shuttle.location_id.id, + "usage": "internal", + "tray_type_id": self.tray_type.id, + }, + { + "name": "tray test 2", + "location_id": self.shuttle.location_id.id, + "usage": "internal", + "tray_type_id": self.tray_type.id, + }, + ] + ) + self.assertEqual(locations[0].location_storage_type_ids, self.storage_types) + self.assertEqual(locations[1].location_storage_type_ids, self.storage_types) + + def test_location_write_sync(self): + self.locations.tray_type_id = self.tray_type + self.assertEqual(self.location_2a.location_storage_type_ids, self.storage_types) + self.assertEqual(self.location_2b.location_storage_type_ids, self.storage_types) + + def test_location_create_error(self): + with self.assertRaisesRegex(exceptions.UserError, "Error creating.*"): + self.env["stock.location"].create( + [ + { + "name": "tray test 1", + "location_id": self.shuttle.location_id.id, + "usage": "internal", + "tray_type_id": self.tray_type.id, + "location_storage_type_ids": [ + (6, 0, self.location_storage_type_buffer.ids) + ], + }, + { + "name": "tray test 2", + "location_id": self.shuttle.location_id.id, + "usage": "internal", + "tray_type_id": self.tray_type.id, + "location_storage_type_ids": [ + (6, 0, self.location_storage_type_buffer.ids) + ], + }, + ] + ) + + def test_location_write_both_fields_error(self): + with self.assertRaisesRegex(exceptions.UserError, "Error updating.*"): + self.locations.write( + { + "tray_type_id": self.tray_type.id, + "location_storage_type_ids": [ + (6, 0, self.location_storage_type_buffer.ids) + ], + } + ) + + def test_location_write_storage_type_error(self): + with self.assertRaisesRegex(exceptions.UserError, "Error updating.*"): + self.locations.write( + { + "location_storage_type_ids": [ + (6, 0, self.location_storage_type_buffer.ids) + ], + } + ) diff --git a/stock_vertical_lift_storage_type/tests/test_tray_type.py b/stock_vertical_lift_storage_type/tests/test_tray_type.py new file mode 100644 index 000000000..81559dce1 --- /dev/null +++ b/stock_vertical_lift_storage_type/tests/test_tray_type.py @@ -0,0 +1,38 @@ +# Copyright 2020 Camptocamp SA +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from .common import TrayTypeCommonCase + + +class TestTrayType(TrayTypeCommonCase): + def test_tray_type_write_sync(self): + # both tray 1A and tray 2D use stock_location_tray_type_small_8x + tray_type = self.env.ref( + "stock_location_tray.stock_location_tray_type_small_8x" + ) + + tray_type.write({"location_storage_type_ids": [(6, 0, self.storage_types.ids)]}) + + self.assertEqual(self.location_1a.location_storage_type_ids, self.storage_types) + self.assertEqual(self.location_2d.location_storage_type_ids, self.storage_types) + + def test_location_create_sync(self): + # both tray 1A and tray 2D use stock_location_tray_type_small_8x + tray_type = self.env.ref( + "stock_location_tray.stock_location_tray_type_small_8x" + ) + tray_type.write({"location_storage_type_ids": [(6, 0, self.storage_types.ids)]}) + + locations = self.location_2a | self.location_2b + + self.assertNotEqual( + self.location_2a.location_storage_type_ids, self.storage_types + ) + self.assertNotEqual( + self.location_2b.location_storage_type_ids, self.storage_types + ) + + locations.tray_type_id = tray_type + + self.assertEqual(self.location_2a.location_storage_type_ids, self.storage_types) + self.assertEqual(self.location_2b.location_storage_type_ids, self.storage_types) diff --git a/stock_vertical_lift_storage_type/views/stock_location_tray_type_views.xml b/stock_vertical_lift_storage_type/views/stock_location_tray_type_views.xml new file mode 100644 index 000000000..008a213da --- /dev/null +++ b/stock_vertical_lift_storage_type/views/stock_location_tray_type_views.xml @@ -0,0 +1,18 @@ + + + + stock.location.tray.type.form + stock.location.tray.type + + + + + + + + + + diff --git a/stock_vertical_lift_storage_type/views/stock_location_views.xml b/stock_vertical_lift_storage_type/views/stock_location_views.xml new file mode 100644 index 000000000..8b1c5232d --- /dev/null +++ b/stock_vertical_lift_storage_type/views/stock_location_views.xml @@ -0,0 +1,15 @@ + + + + stock.location.form.vertical.lift.storage.type + stock.location + + + + {"readonly": [("vertical_lift_kind", "in", ("tray", "cell"))]} + + + +