diff --git a/pandoc-3.6.2-x86_64-macOS.pkg b/pandoc-3.6.2-x86_64-macOS.pkg new file mode 100644 index 0000000000..fa37a2661a Binary files /dev/null and b/pandoc-3.6.2-x86_64-macOS.pkg differ diff --git a/stock_storage_type/README.rst b/stock_storage_type/README.rst new file mode 100644 index 0000000000..2772c78e41 --- /dev/null +++ b/stock_storage_type/README.rst @@ -0,0 +1,199 @@ +================== +Stock Storage Type +================== + +.. + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! This file is generated by oca-gen-addon-readme !! + !! changes will be overwritten. !! + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! source digest: sha256:0e700eefd6bcaa1449f71d59b90a1e4a57c0f749f2d282af71ca5cdd7c0e5c42 + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + +.. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png + :target: https://odoo-community.org/page/development-status + :alt: Beta +.. |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%2Fwms-lightgray.png?logo=github + :target: https://github.com/OCA/wms/tree/17.0/stock_storage_type + :alt: OCA/wms +.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png + :target: https://translation.odoo-community.org/projects/wms-17-0/wms-17-0-stock_storage_type + :alt: Translate me on Weblate +.. |badge5| image:: https://img.shields.io/badge/runboat-Try%20me-875A7B.png + :target: https://runboat.odoo-community.org/builds?repo=OCA/wms&target_branch=17.0 + :alt: Try me on Runboat + +|badge1| |badge2| |badge3| |badge4| |badge5| + +| This module introduces two new models in order to manage stock moves + with +| packages according to the packaging and stock location properties. + +- Stock package storage type (stock.package.storage.type) + + This model is linked to product.packaging and defines the type of + storage related to a specific packaging. + +- Stock location storage type (stock.location.storage.type) + + This models is linked to stock.location and defines the types of + storage that are allowed for a specific location. + +Therefore a Stock location storage type can include different Stock +package storage type in order to validate the destination of a move with +package into a stock location. Moreover Stock location storage type can +include product, size or lot restrictions for the stock locations it's +defined on, so that a move with package will only be allowed if it +doesn't violate the restrictions defined (cf +stock_location_storage_type_strategy). + +Moreover, this module implements "storage type put-away strategy" in +order to compute a put-away location using storage types. + +The standard put-away strategy is applied *before* the storage type +put-away strategy as the former relies on product or product category +and the latter relies on stock packages. + +In other words, when a move is assigned, Odoo standard put-away strategy +will be applied to compute a new destination on the stock move lines, +according to the product. After this first "put-away computation", the +"storage type" put-away strategy is applied, if the reserved quant is +linked to a package defining a package storage type. + +Storage locations linked to the package storage are processed +sequentially, if said storage location is a child of the move line's +destination location (i.e either the put-away location or the move's +destination location). For each location, their packs storage strategy +is applied as well as the restrictions defined on the stock location +storage types. If no suitable location is found, the next location in +the sequence will be searched and so on. + +For the packs putaway strategy "none", the location is considered as is. +For the "ordered children" strategy, children locations are sorted by +first by max height which is a physical constraint to respect, then pack +putaway sequence which allow to favor for example some level or +corridor, and finally by name. + +At the end, if found location is not the same as the original +destination location, the putaway strategies are applied (e.g.: A "none" +pack putaway strategy is set on computed location and a putaway rule +exists on that one). + +**Table of contents** + +.. contents:: + :local: + +Known issues / Roadmap +====================== + +Currently, the module supports only strategies applied on packages +(``stock.quant.package``). For implementations that do not use packages, +it would be possible to add compatibility with product packaging. + +The information needed from a package are: + +- the storage type, to know which strategy is applied +- the dimensions and weight, to apply constraints + +If we want to support product packaging, we would need to: + +- guess the product packaging of a move line based on the product and + quantities (multiple of a packaging quantity, for instance 8000 would + be a pallet if the pallet has 2000 units, 1900 would be Box if the Box + has 100 units) +- from the product packaging, we know the storage type and dimensions + +Everywhere the module is using ``package_id``, we would have to check +this: + +- use the package if a package is set +- else, use the computed packaging + +About Unit of Measures: + +In v13, there is an assumption of height to be expressed in mm and +weight in kg. In v14, packaging can be expressed in differents units. +Explicit fields are introduced like max_weight_in_kg in order make +simple and efficient computations. + +Limitation +---------- + +If the locations structure is using views intensively in order to +separate storage types kindly (not mixing them), Odoo standard method to +get putaway strategy is returning the first child if a move location +destination is a view. + +This is not convenient if we want to set specific strategies on that +view. So, we override standard process by returning the view itself (if +no putaway is set). + +This can lead to a change on standard behavior as people will need to +change manually the location destination for pickings with views as +default destination. + +Idea: maybe adding a field on view locations to say 'this is a view but +don't apply standard child location selection' could help filtering view +candidates. + +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 to smash it by providing a detailed and welcomed +`feedback `_. + +Do not contact contributors directly about support or help with technical issues. + +Credits +======= + +Authors +------- + +* Camptocamp +* BCIM + +Contributors +------------ + +- Akim Juillerat +- Guewen Baconnier +- Raphaël Reverdy +- Jacques-Etienne Baudoux +- Laurent Mignon +- Fernando La Chica - GreenICe +- Denis Roussel + +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. + +.. |maintainer-jbaudoux| image:: https://github.com/jbaudoux.png?size=40px + :target: https://github.com/jbaudoux + :alt: jbaudoux +.. |maintainer-rousseldenis| image:: https://github.com/rousseldenis.png?size=40px + :target: https://github.com/rousseldenis + :alt: rousseldenis + +Current `maintainers `__: + +|maintainer-jbaudoux| |maintainer-rousseldenis| + +This module is part of the `OCA/wms `_ project on GitHub. + +You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute. diff --git a/stock_storage_type/__init__.py b/stock_storage_type/__init__.py new file mode 100644 index 0000000000..0650744f6b --- /dev/null +++ b/stock_storage_type/__init__.py @@ -0,0 +1 @@ +from . import models diff --git a/stock_storage_type/__manifest__.py b/stock_storage_type/__manifest__.py new file mode 100644 index 0000000000..fadeaca9e1 --- /dev/null +++ b/stock_storage_type/__manifest__.py @@ -0,0 +1,42 @@ +# Copyright 2019-2021 Camptocamp SA +# Copyright 2019-2021 Jacques-Etienne Baudoux (BCIM) +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl) +{ + "name": "Stock Storage Type", + "summary": "Manage packages and locations storage types", + "version": "17.0.1.0.0", + "development_status": "Beta", + "category": "Warehouse Management", + "website": "https://github.com/OCA/wms", + "author": "Camptocamp, BCIM, Odoo Community Association (OCA)", + "maintainers": ["jbaudoux", "rousseldenis"], + "license": "AGPL-3", + "application": False, + "installable": True, + "depends": [ + "stock_move_line_reserved_quant", + "stock_putaway_hook", + "stock_quant_package_dimension", + "stock_storage_category_capacity_name", + ], + "data": [ + "security/ir.model.access.csv", + "views/product_template.xml", + "views/stock_location.xml", + "views/stock_storage_category.xml", + "views/stock_storage_category_capacity.xml", + "views/stock_package_level.xml", + "views/stock_package_type.xml", + "views/stock_storage_location_sequence.xml", + "views/stock_storage_location_sequence_cond.xml", + "views/storage_type_menus.xml", + ], + "demo": [ + "demo/stock_package_type.xml", + "demo/stock_storage_category.xml", + "demo/stock_storage_category_capacity.xml", + "demo/product_packaging.xml", + "demo/stock_location.xml", + "demo/stock_storage_location_sequence.xml", + ], +} diff --git a/stock_storage_type/demo/product_packaging.xml b/stock_storage_type/demo/product_packaging.xml new file mode 100644 index 0000000000..ccc93bb2de --- /dev/null +++ b/stock_storage_type/demo/product_packaging.xml @@ -0,0 +1,24 @@ + + + + Single bag + 1 + + + + 4 units cardbox + 4 + + + + + 48 units pallet + 48 + + + 1200 + 800 + 1500 + 60 + + diff --git a/stock_storage_type/demo/stock_location.xml b/stock_storage_type/demo/stock_location.xml new file mode 100644 index 0000000000..44c2efc948 --- /dev/null +++ b/stock_storage_type/demo/stock_location.xml @@ -0,0 +1,107 @@ + + + + Cardboxes storage area + + + ordered_locations + + + Bin 1 + + + + Bin 2 + + + + Bin 3 + + + + Bin 4 + + + + Pallets storage area + + + ordered_locations + + + Pallets Bin 1 + + + + Pallets Bin 2 + + + + Pallets Bin 3 + + + + Pallets Bin 4 + + + + Pallets reserve storage area + + + ordered_locations + + + Pallets Reserve Bin 1 + + + + Pallets Reserve Bin 2 + + + + Pallets Reserve Bin 3 + + + + Pallets Reserve Bin 4 + + + + Cardboxes reserve storage area + + + ordered_locations + + + Cardboxes Reserve Bin 1 + + + + Cardboxes Reserve Bin 2 + + + + Cardboxes Reserve Bin 3 + + + + Cardboxes Reserve Bin 4 + + + diff --git a/stock_storage_type/demo/stock_package_type.xml b/stock_storage_type/demo/stock_package_type.xml new file mode 100644 index 0000000000..2951f7766e --- /dev/null +++ b/stock_storage_type/demo/stock_package_type.xml @@ -0,0 +1,12 @@ + + + + Pallets + + + Pallets UK + + + Cardboxes + + diff --git a/stock_storage_type/demo/stock_storage_category.xml b/stock_storage_type/demo/stock_storage_category.xml new file mode 100644 index 0000000000..174b1166c1 --- /dev/null +++ b/stock_storage_type/demo/stock_storage_category.xml @@ -0,0 +1,11 @@ + + + + + Pallets + + + Cardboxes + + diff --git a/stock_storage_type/demo/stock_storage_category_capacity.xml b/stock_storage_type/demo/stock_storage_category_capacity.xml new file mode 100644 index 0000000000..5a3a536582 --- /dev/null +++ b/stock_storage_type/demo/stock_storage_category_capacity.xml @@ -0,0 +1,39 @@ + + + + + + empty + + + 1 + + + + + + 1 + + + + + + 1 + + diff --git a/stock_storage_type/demo/stock_storage_location_sequence.xml b/stock_storage_type/demo/stock_storage_location_sequence.xml new file mode 100644 index 0000000000..be6cee1cce --- /dev/null +++ b/stock_storage_type/demo/stock_storage_location_sequence.xml @@ -0,0 +1,47 @@ + + + + + 10 + + + + + 20 + + + + + 10 + + + + + 20 + + + diff --git a/stock_storage_type/i18n/de.po b/stock_storage_type/i18n/de.po new file mode 100644 index 0000000000..5f7dd2f193 --- /dev/null +++ b/stock_storage_type/i18n/de.po @@ -0,0 +1,662 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * stock_storage_type +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 17.0\n" +"Report-Msgid-Bugs-To: \n" +"Last-Translator: Automatically generated\n" +"Language-Team: none\n" +"Language: de\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: nplurals=2; plural=n != 1;\n" + +#. module: stock_storage_type +#. odoo-python +#: code:addons/stock_storage_type/models/stock_storage_location_sequence.py:0 +#, python-format +msgid "" +" * {location} (WARNING: restrictions are " +"active on location storage types matching this package storage type)" +msgstr "" + +#. module: stock_storage_type +#. odoo-python +#: code:addons/stock_storage_type/models/stock_storage_location_sequence.py:0 +#, python-format +msgid "" +" * {location} (WARNING: no suitable location " +"matching storage type)" +msgstr "" + +#. module: stock_storage_type +#. odoo-python +#: code:addons/stock_storage_type/models/stock_package_type.py:0 +#, python-format +msgid "" +"The \"Put-Away sequence\" must be defined in " +"order to put away packages using this package storage type " +"({storage})." +msgstr "" + +#. module: stock_storage_type +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_package_type__active +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_storage_location_sequence_cond__active +msgid "Active" +msgstr "" + +#. module: stock_storage_type +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_storage_category__allow_new_product +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_storage_category_capacity__allow_new_product +msgid "Allow New Product" +msgstr "" + +#. module: stock_storage_type +#. odoo-python +#: code:addons/stock_storage_type/models/stock_storage_category_capacity.py:0 +#, python-format +msgid "Allow New Product: " +msgstr "" + +#. module: stock_storage_type +#: model:ir.model.fields.selection,name:stock_storage_type.selection__stock_storage_category_capacity__allow_new_product__mixed +msgid "Allow mixed products" +msgstr "" + +#. module: stock_storage_type +#: model:ir.model.fields,help:stock_storage_type.field_stock_location__package_type_putaway_sequence +msgid "" +"Allow to sort the valid locations by sequence for the storage strategy based" +" on package type" +msgstr "" + +#. module: stock_storage_type +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_package_level__allowed_location_dest_ids +msgid "Allowed Destinations" +msgstr "" + +#. module: stock_storage_type +#: model_terms:ir.ui.view,arch_db:stock_storage_type.stock_storage_location_sequence_cond_form_view +msgid "Archived" +msgstr "" + +#. module: stock_storage_type +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_package_type__barcode +msgid "Barcode" +msgstr "" + +#. module: stock_storage_type +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_location__computed_storage_capacity_ids +msgid "Capacity" +msgstr "" + +#. module: stock_storage_type +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_storage_location_sequence_cond__code_snippet +msgid "Code Snippet" +msgstr "" + +#. module: stock_storage_type +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_storage_location_sequence_cond__code_snippet_docs +msgid "Code Snippet Docs" +msgstr "" + +#. module: stock_storage_type +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_storage_category__computed_location_ids +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_storage_category_capacity__computed_location_ids +msgid "Computed Location" +msgstr "" + +#. module: stock_storage_type +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_location__computed_storage_category_id +msgid "Computed Storage Category" +msgstr "" + +#. module: stock_storage_type +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_storage_location_sequence_cond__condition_type +msgid "Condition Type" +msgstr "" + +#. module: stock_storage_type +#. odoo-python +#: code:addons/stock_storage_type/models/stock_storage_location_sequence_cond.py:0 +#, python-format +msgid "Condition type is set to `Code`: you must provide a piece of code" +msgstr "" + +#. module: stock_storage_type +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_storage_location_sequence__location_sequence_cond_ids +msgid "Conditions" +msgstr "" + +#. module: stock_storage_type +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_storage_location_sequence__create_uid +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_storage_location_sequence_cond__create_uid +msgid "Created by" +msgstr "" + +#. module: stock_storage_type +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_storage_location_sequence__create_date +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_storage_location_sequence_cond__create_date +msgid "Created on" +msgstr "" + +#. module: stock_storage_type +#: model:ir.model.fields,help:stock_storage_type.field_product_product__package_type_id +#: model:ir.model.fields,help:stock_storage_type.field_product_template__package_type_id +msgid "" +"Defines a 'default' package type for this product to be applied on packages " +"without product packagings and on put-away computation based on package type" +" for product not in a package" +msgstr "" + +#. module: stock_storage_type +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_storage_category__length_uom_id +msgid "Dimensions Units of Measure" +msgstr "" + +#. module: stock_storage_type +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_storage_location_sequence__display_name +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_storage_location_sequence_cond__display_name +msgid "Display Name" +msgstr "" + +#. module: stock_storage_type +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_location__do_not_mix_lots +msgid "Do Not Mix Lots" +msgstr "" + +#. module: stock_storage_type +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_location__do_not_mix_products +msgid "Do Not Mix Products" +msgstr "" + +#. module: stock_storage_type +#: model:ir.model.fields.selection,name:stock_storage_type.selection__stock_storage_location_sequence_cond__condition_type__code +msgid "Execute code" +msgstr "" + +#. module: stock_storage_type +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_storage_category_capacity__has_restrictions +msgid "Has Restrictions" +msgstr "" + +#. module: stock_storage_type +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_quant_package__height_in_m +msgid "Height In M" +msgstr "" + +#. module: stock_storage_type +#: model:ir.model.fields,help:stock_storage_type.field_stock_package_type__height_required +msgid "Height is mandatory for packages configured with this storage type." +msgstr "" + +#. module: stock_storage_type +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_package_type__height_required +msgid "Height required for packages" +msgstr "" + +#. module: stock_storage_type +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_storage_location_sequence__id +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_storage_location_sequence_cond__id +msgid "ID" +msgstr "" + +#. module: stock_storage_type +#: model:ir.model.fields.selection,name:stock_storage_type.selection__stock_storage_category_capacity__allow_new_product__same_lot +msgid "If all lots are the same" +msgstr "" + +#. module: stock_storage_type +#: model:ir.model.fields.selection,name:stock_storage_type.selection__stock_storage_category_capacity__allow_new_product__same +msgid "If all products are same" +msgstr "" + +#. module: stock_storage_type +#: model:ir.model.fields.selection,name:stock_storage_type.selection__stock_storage_category__allow_new_product__same_lot +msgid "If lots are all the same" +msgstr "" + +#. module: stock_storage_type +#: model:ir.model.fields.selection,name:stock_storage_type.selection__stock_storage_category_capacity__allow_new_product__empty +msgid "If the location is empty" +msgstr "" + +#. module: stock_storage_type +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_location__in_move_ids +msgid "In Move" +msgstr "" + +#. module: stock_storage_type +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_location__in_move_line_ids +msgid "In Move Line" +msgstr "" + +#. module: stock_storage_type +#: model:ir.model,name:stock_storage_type.model_stock_location +msgid "Inventory Locations" +msgstr "" + +#. module: stock_storage_type +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_storage_location_sequence____last_update +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_storage_location_sequence_cond____last_update +msgid "Last Modified on" +msgstr "" + +#. module: stock_storage_type +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_storage_location_sequence__write_uid +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_storage_location_sequence_cond__write_uid +msgid "Last Updated by" +msgstr "" + +#. module: stock_storage_type +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_storage_location_sequence__write_date +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_storage_location_sequence_cond__write_date +msgid "Last Updated on" +msgstr "" + +#. module: stock_storage_type +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_location__leaf_child_location_ids +msgid "Leaf Child Location" +msgstr "" + +#. module: stock_storage_type +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_location__leaf_location_ids +msgid "Leaf Location" +msgstr "" + +#. module: stock_storage_type +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_storage_location_sequence__location_id +msgid "Location" +msgstr "" + +#. module: stock_storage_type +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_location__location_is_empty +msgid "Location Is Empty" +msgstr "" + +#. module: stock_storage_type +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_location__location_will_contain_lot_ids +msgid "Location Will Contain Lot" +msgstr "" + +#. module: stock_storage_type +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_location__location_will_contain_product_ids +msgid "Location Will Contain Product" +msgstr "" + +#. module: stock_storage_type +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_location__max_height_in_m +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_storage_category__max_height_in_m +msgid "Max Height In M" +msgstr "" + +#. module: stock_storage_type +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_storage_category__max_weight_in_kg +msgid "Max Weight In Kg" +msgstr "" + +#. module: stock_storage_type +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_location__max_height +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_storage_category__max_height +msgid "Max height (mm)" +msgstr "" + +#. module: stock_storage_type +#: model:ir.model.constraint,message:stock_storage_type.constraint_stock_storage_category_positive_max_height +msgid "Max height should be a positive number." +msgstr "" + +#. module: stock_storage_type +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_storage_location_sequence_cond__name +msgid "Name" +msgstr "" + +#. module: stock_storage_type +#: model:ir.model.fields.selection,name:stock_storage_type.selection__stock_location__pack_putaway_strategy__none +msgid "None" +msgstr "" + +#. module: stock_storage_type +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_location__only_empty +msgid "Only Empty" +msgstr "" + +#. module: stock_storage_type +#: model:ir.model.fields.selection,name:stock_storage_type.selection__stock_location__pack_putaway_strategy__ordered_locations +msgid "Ordered Children Locations" +msgstr "" + +#. module: stock_storage_type +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_location__out_move_line_ids +msgid "Out Move Line" +msgstr "" + +#. module: stock_storage_type +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_quant_package__pack_weight_in_kg +msgid "Pack Weight In Kg" +msgstr "" + +#. module: stock_storage_type +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_storage_location_sequence__package_type_id +msgid "Package Type" +msgstr "" + +#. module: stock_storage_type +#: model:ir.model.fields,field_description:stock_storage_type.field_product_product__package_type_id +#: model:ir.model.fields,field_description:stock_storage_type.field_product_template__package_type_id +msgid "Package type" +msgstr "" + +#. module: stock_storage_type +#. odoo-python +#: code:addons/stock_storage_type/models/stock_quant.py:0 +#, python-format +msgid "Package type {storage} is not allowed into Location {location}" +msgstr "" + +#. module: stock_storage_type +#. odoo-python +#: code:addons/stock_storage_type/models/stock_quant.py:0 +#, python-format +msgid "" +"Package {package} is not allowed into location {location}, because there isn't any storage capacity that allows package type {type} into it:\n" +"\n" +"{fails}" +msgstr "" + +#. module: stock_storage_type +#: model:ir.model,name:stock_storage_type.model_stock_quant_package +msgid "Packages" +msgstr "" + +#. module: stock_storage_type +#: model:ir.model,name:stock_storage_type.model_product_template +msgid "Product" +msgstr "" + +#. module: stock_storage_type +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_package_type__product_packaging_ids +msgid "Product Packaging" +msgstr "" + +#. module: stock_storage_type +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_location__pack_putaway_strategy +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_storage_location_sequence__location_putaway_strategy +msgid "Put-Away Strategy" +msgstr "" + +#. module: stock_storage_type +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_package_type__storage_location_sequence_ids +#: model_terms:ir.ui.view,arch_db:stock_storage_type.stock_package_type_form +msgid "Put-Away sequence" +msgstr "" + +#. module: stock_storage_type +#. odoo-python +#: code:addons/stock_storage_type/models/stock_package_type.py:0 +#, python-format +msgid "Put-away sequence" +msgstr "" + +#. module: stock_storage_type +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_location__package_type_putaway_sequence +msgid "Putaway Sequence" +msgstr "" + +#. module: stock_storage_type +#: model:ir.model,name:stock_storage_type.model_stock_quant +msgid "Quants" +msgstr "" + +#. module: stock_storage_type +#: model_terms:ir.ui.view,arch_db:stock_storage_type.package_level_tree_view_picking_inherit +msgid "Recompute Putaway" +msgstr "" + +#. module: stock_storage_type +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_storage_location_sequence__sequence +msgid "Sequence" +msgstr "" + +#. module: stock_storage_type +#: model:ir.model,name:stock_storage_type.model_stock_storage_location_sequence +msgid "Sequence of locations to put-away the package storage type" +msgstr "" + +#. module: stock_storage_type +#: model_terms:ir.ui.view,arch_db:stock_storage_type.package_storage_location_tree_view +msgid "Show locations" +msgstr "" + +#. module: stock_storage_type +#: model:ir.model,name:stock_storage_type.model_stock_package_level +msgid "Stock Package Level" +msgstr "" + +#. module: stock_storage_type +#: model:ir.model,name:stock_storage_type.model_stock_storage_location_sequence_cond +msgid "Stock Storage Location Sequence Condition" +msgstr "" + +#. module: stock_storage_type +#: model:ir.actions.act_window,name:stock_storage_type.stock_storage_location_sequence_cond_act_window +msgid "Stock Storage Location Sequence Conditions" +msgstr "" + +#. module: stock_storage_type +#: model:ir.model,name:stock_storage_type.model_stock_package_type +msgid "Stock package type" +msgstr "" + +#. module: stock_storage_type +#: model:ir.model.constraint,message:stock_storage_type.constraint_stock_storage_location_sequence_cond_name +msgid "Stock storage location sequence condition name must be unique" +msgstr "" + +#. module: stock_storage_type +#. odoo-python +#: code:addons/stock_storage_type/models/stock_quant.py:0 +#, python-format +msgid "" +"Storage Capacity {storage_capacity} is flagged 'do not mix lots' but there " +"are other lots in location." +msgstr "" + +#. module: stock_storage_type +#. odoo-python +#: code:addons/stock_storage_type/models/stock_quant.py:0 +#, python-format +msgid "" +"Storage Capacity {storage_capacity} is flagged 'do not mix products' but " +"there are other products in location." +msgstr "" + +#. module: stock_storage_type +#. odoo-python +#: code:addons/stock_storage_type/models/stock_quant.py:0 +#, python-format +msgid "" +"Storage Capacity {storage_capacity} is flagged 'only empty' with other " +"quants in location." +msgstr "" + +#. module: stock_storage_type +#: model:ir.model,name:stock_storage_type.model_stock_storage_category +msgid "Storage Category" +msgstr "" + +#. module: stock_storage_type +#: model:ir.model,name:stock_storage_type.model_stock_storage_category_capacity +msgid "Storage Category Capacity" +msgstr "" + +#. module: stock_storage_type +#. odoo-python +#: code:addons/stock_storage_type/models/stock_quant.py:0 +#, python-format +msgid "" +"Storage Category {storage_category} defines max height of {max_h} but the " +"package is bigger: {height}." +msgstr "" + +#. module: stock_storage_type +#. odoo-python +#: code:addons/stock_storage_type/models/stock_quant.py:0 +#, python-format +msgid "" +"Storage Category {storage_category} defines max weight of {max_w} but the " +"package is heavier: {weight_kg}." +msgstr "" + +#. module: stock_storage_type +#: model:ir.ui.menu,name:stock_storage_type.stock_storage_location_sequence_cond_menu +msgid "Storage Location Sequence Conditions" +msgstr "" + +#. module: stock_storage_type +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_package_type__storage_type_message +msgid "Storage Type Message" +msgstr "" + +#. module: stock_storage_type +#: model:ir.ui.menu,name:stock_storage_type.storage_type_menu +msgid "Storage Types" +msgstr "" + +#. module: stock_storage_type +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_location__storage_location_sequence_ids +msgid "Storage locations sequences" +msgstr "" + +#. module: stock_storage_type +#: model:ir.model.fields,help:stock_storage_type.field_stock_location__max_height_in_m +#: model:ir.model.fields,help:stock_storage_type.field_stock_quant_package__height_in_m +#: model:ir.model.fields,help:stock_storage_type.field_stock_quant_package__pack_weight_in_kg +#: model:ir.model.fields,help:stock_storage_type.field_stock_storage_category__max_height_in_m +#: model:ir.model.fields,help:stock_storage_type.field_stock_storage_category__max_weight_in_kg +msgid "Technical field, to speed up comparaisons" +msgstr "" + +#. module: stock_storage_type +#: model:ir.model.fields,help:stock_storage_type.field_stock_storage_category_capacity__has_restrictions +msgid "Technical: This is used to check if we need to display warning message" +msgstr "" + +#. module: stock_storage_type +#. odoo-python +#: code:addons/stock_storage_type/models/stock_quant_package.py:0 +#, python-format +msgid "The height is mandatory on package {}." +msgstr "" + +#. module: stock_storage_type +#: model:ir.model.fields,help:stock_storage_type.field_stock_location__max_height +#: model:ir.model.fields,help:stock_storage_type.field_stock_storage_category__max_height +msgid "The max height supported for this storage category." +msgstr "" + +#. module: stock_storage_type +#: model:ir.model.fields,help:stock_storage_type.field_stock_location__pack_putaway_strategy +#: model:ir.model.fields,help:stock_storage_type.field_stock_storage_location_sequence__location_putaway_strategy +msgid "" +"This defines the storage strategy based on package type to use when a product or package is put away in this location.\n" +"None: when moved to this location, it will not be put away any further.\n" +"Ordered Children Locations: when moved to this location, a suitable location will be searched in its children locations according to the restrictions defined on their respective location storage types." +msgstr "" + +#. module: stock_storage_type +#: model:ir.model.fields,help:stock_storage_type.field_stock_location__computed_storage_category_id +msgid "" +"This represents the Storage Category that will be used. It depends either on" +" the category set on the location or on one of its parent." +msgstr "" + +#. module: stock_storage_type +#: model:ir.model.fields,help:stock_storage_type.field_stock_storage_category__length_uom_id +msgid "UoM for height" +msgstr "" + +#. module: stock_storage_type +#: model:ir.model.fields,help:stock_storage_type.field_stock_storage_category__weight_uom_id +msgid "Weight Unit of Measure" +msgstr "" + +#. module: stock_storage_type +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_storage_category__weight_uom_id +msgid "Weight Units of Measure" +msgstr "" + +#. module: stock_storage_type +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_storage_category__weight_uom_name +msgid "Weight unit of measure label" +msgstr "" + +#. module: stock_storage_type +#. odoo-python +#: code:addons/stock_storage_type/models/stock_package_type.py:0 +#, python-format +msgid "" +"When a package with storage type {name} is put away, the strategy will look " +"for an allowed location in the following locations:

{message} " +"

Note: this happens as long as these locations are " +"children of the stock move destination location or as long as these " +"locations are children of the destination location after the (product or " +"category) put-away is applied." +msgstr "" + +#. module: stock_storage_type +#. odoo-python +#: code:addons/stock_storage_type/models/stock_storage_location_sequence_cond.py:0 +#, python-format +msgid "code_snippet should return boolean value into `result` variable." +msgstr "" + +#. module: stock_storage_type +#: model:ir.model.fields,help:stock_storage_type.field_stock_location__location_is_empty +msgid "" +"technical field: True if the location is empty and there is no pending " +"incoming products in the location. Computed only if the location needs to " +"check for emptiness (has an \"only empty\" location storage type)." +msgstr "" + +#. module: stock_storage_type +#: model:ir.model.fields,help:stock_storage_type.field_stock_location__leaf_location_ids +msgid "technical field: all the leaves locations" +msgstr "" + +#. module: stock_storage_type +#: model:ir.model.fields,help:stock_storage_type.field_stock_location__leaf_child_location_ids +msgid "technical field: all the leaves sub-locations" +msgstr "" + +#. module: stock_storage_type +#: model:ir.model.fields,help:stock_storage_type.field_stock_location__location_will_contain_product_ids +msgid "" +"technical field: list of products in the location, either now or in pending " +"operations" +msgstr "" + +#. module: stock_storage_type +#: model:ir.model.fields,help:stock_storage_type.field_stock_location__location_will_contain_lot_ids +msgid "" +"technical field: list of stock.lots in the location, either now or in " +"pending operations" +msgstr "" + +#. module: stock_storage_type +#: model:ir.model.fields,help:stock_storage_type.field_stock_location__in_move_line_ids +msgid "technical field: the pending incoming stock.move.lines in the location" +msgstr "" + +#. module: stock_storage_type +#: model:ir.model.fields,help:stock_storage_type.field_stock_location__in_move_ids +msgid "technical field: the pending incoming stock.moves in the location" +msgstr "" + +#. module: stock_storage_type +#: model:ir.model.fields,help:stock_storage_type.field_stock_location__out_move_line_ids +msgid "technical field: the pending outgoing stock.move.lines in the location" +msgstr "" diff --git a/stock_storage_type/i18n/es.po b/stock_storage_type/i18n/es.po new file mode 100644 index 0000000000..0e38b9430e --- /dev/null +++ b/stock_storage_type/i18n/es.po @@ -0,0 +1,727 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * stock_storage_type +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 17.0\n" +"Report-Msgid-Bugs-To: \n" +"PO-Revision-Date: 2023-09-07 18:37+0000\n" +"Last-Translator: Ivorra78 \n" +"Language-Team: none\n" +"Language: es\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: nplurals=2; plural=n != 1;\n" +"X-Generator: Weblate 4.17\n" + +#. module: stock_storage_type +#. odoo-python +#: code:addons/stock_storage_type/models/stock_storage_location_sequence.py:0 +#, python-format +msgid "" +" * {location} (WARNING: restrictions are " +"active on location storage types matching this package storage type)" +msgstr "" +" * {location} (ADVERTENCIA: hay restricciones " +"activas en los tipos de ubicación de almacenamiento que coinciden con este " +"tipo de almacenamiento de paquete)" + +#. module: stock_storage_type +#. odoo-python +#: code:addons/stock_storage_type/models/stock_storage_location_sequence.py:0 +#, python-format +msgid "" +" * {location} (WARNING: no suitable location " +"matching storage type)" +msgstr "" +" * {location} (ADVERTENCIA: no hay una ubicación " +"adecuada que coincida con el tipo de almacenamiento)" + +#. module: stock_storage_type +#. odoo-python +#: code:addons/stock_storage_type/models/stock_package_type.py:0 +#, python-format +msgid "" +"The \"Put-Away sequence\" must be defined in " +"order to put away packages using this package storage type ({storage})." +msgstr "" +"La \"secuencia de almacenamiento\" debe " +"definirse para poder almacenar paquetes utilizando este tipo de " +"almacenamiento de paquetes ({storage})." + +#. module: stock_storage_type +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_package_type__active +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_storage_location_sequence_cond__active +msgid "Active" +msgstr "Activo" + +#. module: stock_storage_type +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_storage_category__allow_new_product +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_storage_category_capacity__allow_new_product +msgid "Allow New Product" +msgstr "Nuevo Producto Permitido" + +#. module: stock_storage_type +#. odoo-python +#: code:addons/stock_storage_type/models/stock_storage_category_capacity.py:0 +#, python-format +msgid "Allow New Product: " +msgstr "Nuevo Producto Permitido " + +#. module: stock_storage_type +#: model:ir.model.fields.selection,name:stock_storage_type.selection__stock_storage_category_capacity__allow_new_product__mixed +msgid "Allow mixed products" +msgstr "Permitir productos mixtos" + +#. module: stock_storage_type +#: model:ir.model.fields,help:stock_storage_type.field_stock_location__package_type_putaway_sequence +msgid "" +"Allow to sort the valid locations by sequence for the storage strategy based " +"on package type" +msgstr "" +"Permitir ordenar las ubicaciones válidas por secuencia para la estrategia de " +"almacenamiento basada en el tipo de paquete" + +#. module: stock_storage_type +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_package_level__allowed_location_dest_ids +msgid "Allowed Destinations" +msgstr "Destinos permitidos" + +#. module: stock_storage_type +#: model_terms:ir.ui.view,arch_db:stock_storage_type.stock_storage_location_sequence_cond_form_view +msgid "Archived" +msgstr "Archivado" + +#. module: stock_storage_type +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_package_type__barcode +msgid "Barcode" +msgstr "Código de Barras" + +#. module: stock_storage_type +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_location__computed_storage_capacity_ids +msgid "Capacity" +msgstr "Capacidad" + +#. module: stock_storage_type +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_storage_location_sequence_cond__code_snippet +msgid "Code Snippet" +msgstr "Fragmento de código" + +#. module: stock_storage_type +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_storage_location_sequence_cond__code_snippet_docs +msgid "Code Snippet Docs" +msgstr "Documentos de fragmentos de código" + +#. module: stock_storage_type +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_storage_category__computed_location_ids +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_storage_category_capacity__computed_location_ids +msgid "Computed Location" +msgstr "Computar Localización" + +#. module: stock_storage_type +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_location__computed_storage_category_id +msgid "Computed Storage Category" +msgstr "Categoría de almacenamiento computado" + +#. module: stock_storage_type +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_storage_location_sequence_cond__condition_type +msgid "Condition Type" +msgstr "Tipo de condición" + +#. module: stock_storage_type +#. odoo-python +#: code:addons/stock_storage_type/models/stock_storage_location_sequence_cond.py:0 +#, python-format +msgid "Condition type is set to `Code`: you must provide a piece of code" +msgstr "" +"El tipo de condición es `Código`: debe proporcionar un fragmento de código" + +#. module: stock_storage_type +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_storage_location_sequence__location_sequence_cond_ids +msgid "Conditions" +msgstr "Condiciones" + +#. module: stock_storage_type +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_storage_location_sequence__create_uid +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_storage_location_sequence_cond__create_uid +msgid "Created by" +msgstr "Creado por" + +#. module: stock_storage_type +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_storage_location_sequence__create_date +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_storage_location_sequence_cond__create_date +msgid "Created on" +msgstr "Creado el" + +#. module: stock_storage_type +#: model:ir.model.fields,help:stock_storage_type.field_product_product__package_type_id +#: model:ir.model.fields,help:stock_storage_type.field_product_template__package_type_id +msgid "" +"Defines a 'default' package type for this product to be applied on packages " +"without product packagings and on put-away computation based on package type " +"for product not in a package" +msgstr "" +"Define un tipo de envase \"por defecto\" para este producto que se aplicará " +"a los envases sin embalaje de producto y al cálculo de la salida de almacén " +"basado en el tipo de envase para el producto que no esté en un envase" + +#. module: stock_storage_type +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_storage_category__length_uom_id +msgid "Dimensions Units of Measure" +msgstr "Dimensiones Unidades de medida" + +#. module: stock_storage_type +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_storage_location_sequence__display_name +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_storage_location_sequence_cond__display_name +msgid "Display Name" +msgstr "Mostrar Nombre" + +#. module: stock_storage_type +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_location__do_not_mix_lots +msgid "Do Not Mix Lots" +msgstr "No Mezclar Lotes" + +#. module: stock_storage_type +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_location__do_not_mix_products +msgid "Do Not Mix Products" +msgstr "No Mezclar Productos" + +#. module: stock_storage_type +#: model:ir.model.fields.selection,name:stock_storage_type.selection__stock_storage_location_sequence_cond__condition_type__code +msgid "Execute code" +msgstr "Ejecutar código" + +#. module: stock_storage_type +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_storage_category_capacity__has_restrictions +msgid "Has Restrictions" +msgstr "Tiene restricciones" + +#. module: stock_storage_type +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_quant_package__height_in_m +msgid "Height In M" +msgstr "Altura En M" + +#. module: stock_storage_type +#: model:ir.model.fields,help:stock_storage_type.field_stock_package_type__height_required +msgid "Height is mandatory for packages configured with this storage type." +msgstr "" +"La altura es obligatoria para los paquetes configurados con este tipo de " +"almacenamiento." + +#. module: stock_storage_type +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_package_type__height_required +msgid "Height required for packages" +msgstr "Altura necesaria para los paquetes" + +#. module: stock_storage_type +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_storage_location_sequence__id +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_storage_location_sequence_cond__id +msgid "ID" +msgstr "ID (identificación)" + +#. module: stock_storage_type +#: model:ir.model.fields.selection,name:stock_storage_type.selection__stock_storage_category_capacity__allow_new_product__same_lot +msgid "If all lots are the same" +msgstr "Si todos los lotes son iguales" + +#. module: stock_storage_type +#: model:ir.model.fields.selection,name:stock_storage_type.selection__stock_storage_category_capacity__allow_new_product__same +msgid "If all products are same" +msgstr "Si todos los productos son iguales" + +#. module: stock_storage_type +#: model:ir.model.fields.selection,name:stock_storage_type.selection__stock_storage_category__allow_new_product__same_lot +msgid "If lots are all the same" +msgstr "Si los lotes son todos iguales" + +#. module: stock_storage_type +#: model:ir.model.fields.selection,name:stock_storage_type.selection__stock_storage_category_capacity__allow_new_product__empty +msgid "If the location is empty" +msgstr "Si la localización está vacía" + +#. module: stock_storage_type +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_location__in_move_ids +msgid "In Move" +msgstr "En movimiento" + +#. module: stock_storage_type +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_location__in_move_line_ids +msgid "In Move Line" +msgstr "En línea de movimiento" + +#. module: stock_storage_type +#: model:ir.model,name:stock_storage_type.model_stock_location +msgid "Inventory Locations" +msgstr "Localizaciones de Inventario" + +#. module: stock_storage_type +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_storage_location_sequence____last_update +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_storage_location_sequence_cond____last_update +msgid "Last Modified on" +msgstr "Última Modificación el" + +#. module: stock_storage_type +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_storage_location_sequence__write_uid +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_storage_location_sequence_cond__write_uid +msgid "Last Updated by" +msgstr "Última Actualización por" + +#. module: stock_storage_type +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_storage_location_sequence__write_date +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_storage_location_sequence_cond__write_date +msgid "Last Updated on" +msgstr "Última Actualización el" + +#. module: stock_storage_type +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_location__leaf_child_location_ids +msgid "Leaf Child Location" +msgstr "Ubicación de la hoja infantil" + +#. module: stock_storage_type +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_location__leaf_location_ids +msgid "Leaf Location" +msgstr "Ubicación de la hoja" + +#. module: stock_storage_type +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_storage_location_sequence__location_id +msgid "Location" +msgstr "Localización" + +#. module: stock_storage_type +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_location__location_is_empty +msgid "Location Is Empty" +msgstr "La ubicación está vacía" + +#. module: stock_storage_type +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_location__location_will_contain_lot_ids +msgid "Location Will Contain Lot" +msgstr "La ubicación contendrá el lote" + +#. module: stock_storage_type +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_location__location_will_contain_product_ids +msgid "Location Will Contain Product" +msgstr "La ubicación contendrá el producto" + +#. module: stock_storage_type +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_location__max_height_in_m +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_storage_category__max_height_in_m +msgid "Max Height In M" +msgstr "Altura máxima en M" + +#. module: stock_storage_type +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_storage_category__max_weight_in_kg +msgid "Max Weight In Kg" +msgstr "Peso máximo en kg" + +#. module: stock_storage_type +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_location__max_height +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_storage_category__max_height +msgid "Max height (mm)" +msgstr "Altura máxima (mm)" + +#. module: stock_storage_type +#: model:ir.model.constraint,message:stock_storage_type.constraint_stock_storage_category_positive_max_height +msgid "Max height should be a positive number." +msgstr "La altura máxima debe ser un número positivo." + +#. module: stock_storage_type +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_storage_location_sequence_cond__name +msgid "Name" +msgstr "Nombre" + +#. module: stock_storage_type +#: model:ir.model.fields.selection,name:stock_storage_type.selection__stock_location__pack_putaway_strategy__none +msgid "None" +msgstr "Ninguno/a" + +#. module: stock_storage_type +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_location__only_empty +msgid "Only Empty" +msgstr "Sólo vacío" + +#. module: stock_storage_type +#: model:ir.model.fields.selection,name:stock_storage_type.selection__stock_location__pack_putaway_strategy__ordered_locations +msgid "Ordered Children Locations" +msgstr "Ubicaciones de los niños solicitados" + +#. module: stock_storage_type +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_location__out_move_line_ids +msgid "Out Move Line" +msgstr "Fuera de la línea de movimiento" + +#. module: stock_storage_type +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_quant_package__pack_weight_in_kg +msgid "Pack Weight In Kg" +msgstr "Peso del paquete en Kg" + +#. module: stock_storage_type +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_storage_location_sequence__package_type_id +msgid "Package Type" +msgstr "Tipo de embalaje" + +#. module: stock_storage_type +#: model:ir.model.fields,field_description:stock_storage_type.field_product_product__package_type_id +#: model:ir.model.fields,field_description:stock_storage_type.field_product_template__package_type_id +msgid "Package type" +msgstr "Tipo de embalaje" + +#. module: stock_storage_type +#. odoo-python +#: code:addons/stock_storage_type/models/stock_quant.py:0 +#, python-format +msgid "Package type {storage} is not allowed into Location {location}" +msgstr "" +"El tipo de paquete {storage} no está permitido en la ubicación {location}" + +#. module: stock_storage_type +#. odoo-python +#: code:addons/stock_storage_type/models/stock_quant.py:0 +#, python-format +msgid "" +"Package {package} is not allowed into location {location}, because there " +"isn't any storage capacity that allows package type {type} into it:\n" +"\n" +"{fails}" +msgstr "" +"El paquete {package} no está permitido en la localización {location}, porque " +"no hay ninguna capacidad de almacenamiento que permita el tipo de paquete " +"{type} en ella:\n" +"\n" +"{fails}" + +#. module: stock_storage_type +#: model:ir.model,name:stock_storage_type.model_stock_quant_package +msgid "Packages" +msgstr "Paquetes" + +#. module: stock_storage_type +#: model:ir.model,name:stock_storage_type.model_product_template +msgid "Product" +msgstr "Producto" + +#. module: stock_storage_type +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_package_type__product_packaging_ids +msgid "Product Packaging" +msgstr "Empaquetado de producto" + +#. module: stock_storage_type +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_location__pack_putaway_strategy +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_storage_location_sequence__location_putaway_strategy +msgid "Put-Away Strategy" +msgstr "Estrategia de salida" + +#. module: stock_storage_type +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_package_type__storage_location_sequence_ids +#: model_terms:ir.ui.view,arch_db:stock_storage_type.stock_package_type_form +msgid "Put-Away sequence" +msgstr "Secuencia de salida" + +#. module: stock_storage_type +#. odoo-python +#: code:addons/stock_storage_type/models/stock_package_type.py:0 +#, python-format +msgid "Put-away sequence" +msgstr "Secuencia de salida" + +#. module: stock_storage_type +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_location__package_type_putaway_sequence +msgid "Putaway Sequence" +msgstr "Secuencia de salida" + +#. module: stock_storage_type +#: model:ir.model,name:stock_storage_type.model_stock_quant +msgid "Quants" +msgstr "Cants" + +#. module: stock_storage_type +#: model_terms:ir.ui.view,arch_db:stock_storage_type.package_level_tree_view_picking_inherit +msgid "Recompute Putaway" +msgstr "Recálculo de salida" + +#. module: stock_storage_type +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_storage_location_sequence__sequence +msgid "Sequence" +msgstr "Secuencia" + +#. module: stock_storage_type +#: model:ir.model,name:stock_storage_type.model_stock_storage_location_sequence +msgid "Sequence of locations to put-away the package storage type" +msgstr "Secuencia de ubicaciones para guardar el tipo de almacén de paquetes" + +#. module: stock_storage_type +#: model_terms:ir.ui.view,arch_db:stock_storage_type.package_storage_location_tree_view +msgid "Show locations" +msgstr "Mostrar localizaciones" + +#. module: stock_storage_type +#: model:ir.model,name:stock_storage_type.model_stock_package_level +msgid "Stock Package Level" +msgstr "Nivel del paquete de existencias" + +#. module: stock_storage_type +#: model:ir.model,name:stock_storage_type.model_stock_storage_location_sequence_cond +msgid "Stock Storage Location Sequence Condition" +msgstr "Condición de secuencia de ubicación de almacenamiento de existencias" + +#. module: stock_storage_type +#: model:ir.actions.act_window,name:stock_storage_type.stock_storage_location_sequence_cond_act_window +msgid "Stock Storage Location Sequence Conditions" +msgstr "Condiciones de secuencia de ubicación de almacenamiento de existencias" + +#. module: stock_storage_type +#: model:ir.model,name:stock_storage_type.model_stock_package_type +msgid "Stock package type" +msgstr "Tipo de empaquetado de existencias" + +#. module: stock_storage_type +#: model:ir.model.constraint,message:stock_storage_type.constraint_stock_storage_location_sequence_cond_name +msgid "Stock storage location sequence condition name must be unique" +msgstr "El nombre de la condición de secuencia del almacén debe ser único" + +#. module: stock_storage_type +#. odoo-python +#: code:addons/stock_storage_type/models/stock_quant.py:0 +#, python-format +msgid "" +"Storage Capacity {storage_capacity} is flagged 'do not mix lots' but there " +"are other lots in location." +msgstr "" +"Capacidad de almacenamiento {storage_capacity} está marcada como 'no mezclar " +"lotes' pero hay otros lotes en la ubicación." + +#. module: stock_storage_type +#. odoo-python +#: code:addons/stock_storage_type/models/stock_quant.py:0 +#, python-format +msgid "" +"Storage Capacity {storage_capacity} is flagged 'do not mix products' but " +"there are other products in location." +msgstr "" +"Capacidad de almacenamiento {storage_capacity} está marcada como 'no mezclar " +"productos' pero hay otros productos en la ubicación." + +#. module: stock_storage_type +#. odoo-python +#: code:addons/stock_storage_type/models/stock_quant.py:0 +#, python-format +msgid "" +"Storage Capacity {storage_capacity} is flagged 'only empty' with other " +"quants in location." +msgstr "" +"La capacidad de almacenamiento {storage_capacity} está marcada como \"sólo " +"vacía\" con otros cuantos en la ubicación." + +#. module: stock_storage_type +#: model:ir.model,name:stock_storage_type.model_stock_storage_category +msgid "Storage Category" +msgstr "Categoría Almacenamiento" + +#. module: stock_storage_type +#: model:ir.model,name:stock_storage_type.model_stock_storage_category_capacity +msgid "Storage Category Capacity" +msgstr "Capacidad de la categoría de almacenamiento" + +#. module: stock_storage_type +#. odoo-python +#: code:addons/stock_storage_type/models/stock_quant.py:0 +#, python-format +msgid "" +"Storage Category {storage_category} defines max height of {max_h} but the " +"package is bigger: {height}." +msgstr "" +"La categoría de almacenamiento {storage_category} define una altura máxima " +"de {max_h} pero el paquete es mayor: {height}." + +#. module: stock_storage_type +#. odoo-python +#: code:addons/stock_storage_type/models/stock_quant.py:0 +#, python-format +msgid "" +"Storage Category {storage_category} defines max weight of {max_w} but the " +"package is heavier: {weight_kg}." +msgstr "" +"La categoría de almacenamiento {storage_category} define un peso máximo de " +"{max_w} pero el paquete es más pesado: {weight_kg}." + +#. module: stock_storage_type +#: model:ir.ui.menu,name:stock_storage_type.stock_storage_location_sequence_cond_menu +msgid "Storage Location Sequence Conditions" +msgstr "Condiciones de secuencia del almacén" + +#. module: stock_storage_type +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_package_type__storage_type_message +msgid "Storage Type Message" +msgstr "Mensaje del tipo de almacenamiento" + +#. module: stock_storage_type +#: model:ir.ui.menu,name:stock_storage_type.storage_type_menu +msgid "Storage Types" +msgstr "Tipos de almacenamiento" + +#. module: stock_storage_type +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_location__storage_location_sequence_ids +msgid "Storage locations sequences" +msgstr "Secuencias de lugares de almacenamiento" + +#. module: stock_storage_type +#: model:ir.model.fields,help:stock_storage_type.field_stock_location__max_height_in_m +#: model:ir.model.fields,help:stock_storage_type.field_stock_quant_package__height_in_m +#: model:ir.model.fields,help:stock_storage_type.field_stock_quant_package__pack_weight_in_kg +#: model:ir.model.fields,help:stock_storage_type.field_stock_storage_category__max_height_in_m +#: model:ir.model.fields,help:stock_storage_type.field_stock_storage_category__max_weight_in_kg +msgid "Technical field, to speed up comparaisons" +msgstr "Ámbito técnico, para agilizar las comparaciones" + +#. module: stock_storage_type +#: model:ir.model.fields,help:stock_storage_type.field_stock_storage_category_capacity__has_restrictions +msgid "Technical: This is used to check if we need to display warning message" +msgstr "" +"Técnico: Se utiliza para comprobar si es necesario mostrar un mensaje de " +"advertencia" + +#. module: stock_storage_type +#. odoo-python +#: code:addons/stock_storage_type/models/stock_quant_package.py:0 +#, python-format +msgid "The height is mandatory on package {}." +msgstr "La altura es obligatoria en el paquete {}." + +#. module: stock_storage_type +#: model:ir.model.fields,help:stock_storage_type.field_stock_location__max_height +#: model:ir.model.fields,help:stock_storage_type.field_stock_storage_category__max_height +msgid "The max height supported for this storage category." +msgstr "La altura máxima admitida para esta categoría de almacenamiento." + +#. module: stock_storage_type +#: model:ir.model.fields,help:stock_storage_type.field_stock_location__pack_putaway_strategy +#: model:ir.model.fields,help:stock_storage_type.field_stock_storage_location_sequence__location_putaway_strategy +msgid "" +"This defines the storage strategy based on package type to use when a " +"product or package is put away in this location.\n" +"None: when moved to this location, it will not be put away any further.\n" +"Ordered Children Locations: when moved to this location, a suitable location " +"will be searched in its children locations according to the restrictions " +"defined on their respective location storage types." +msgstr "" +"Define la estrategia de almacenamiento basada en el tipo de paquete que se " +"utilizará cuando se guarde un producto o paquete en esta ubicación.\n" +"Ninguno: cuando se mueve a esta ubicación, no se guardará más.\n" +"Ubicaciones hijas ordenadas: cuando se mueve a esta ubicación, se buscará " +"una ubicación adecuada en sus ubicaciones hijas según las restricciones " +"definidas en sus respectivos tipos de almacenamiento de ubicación." + +#. module: stock_storage_type +#: model:ir.model.fields,help:stock_storage_type.field_stock_location__computed_storage_category_id +msgid "" +"This represents the Storage Category that will be used. It depends either on " +"the category set on the location or on one of its parent." +msgstr "" +"Representa la categoría de almacenamiento que se utilizará. Depende de la " +"categoría establecida en la ubicación o en uno de sus padres." + +#. module: stock_storage_type +#: model:ir.model.fields,help:stock_storage_type.field_stock_storage_category__length_uom_id +msgid "UoM for height" +msgstr "UdM para la altura" + +#. module: stock_storage_type +#: model:ir.model.fields,help:stock_storage_type.field_stock_storage_category__weight_uom_id +msgid "Weight Unit of Measure" +msgstr "Unidad de medida del peso" + +#. module: stock_storage_type +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_storage_category__weight_uom_id +msgid "Weight Units of Measure" +msgstr "Unidades de medida del peso" + +#. module: stock_storage_type +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_storage_category__weight_uom_name +msgid "Weight unit of measure label" +msgstr "Etiqueta de unidad de medida de peso" + +#. module: stock_storage_type +#. odoo-python +#: code:addons/stock_storage_type/models/stock_package_type.py:0 +#, python-format +msgid "" +"When a package with storage type {name} is put away, the strategy will look " +"for an allowed location in the following locations:

{message}

Note: this happens as long as these locations are children " +"of the stock move destination location or as long as these locations are " +"children of the destination location after the (product or category) put-" +"away is applied." +msgstr "" +"Cuando se guarda un paquete con tipo de almacenamiento {name}, la estrategia " +"buscará una ubicación permitida en las siguientes ubicaciones:

{message}

Nota: esto sucede siempre que estas ubicaciones " +"sean secundarias de la ubicación de destino del movimiento de " +"existencias o siempre que estas ubicaciones sean secundarias de la " +"ubicación de destino después de que se aplique la ubicación (de producto o " +"categoría) ." + +#. module: stock_storage_type +#. odoo-python +#: code:addons/stock_storage_type/models/stock_storage_location_sequence_cond.py:0 +#, python-format +msgid "code_snippet should return boolean value into `result` variable." +msgstr "code_snippet debe devolver un valor booleano en la variable `result`." + +#. module: stock_storage_type +#: model:ir.model.fields,help:stock_storage_type.field_stock_location__location_is_empty +msgid "" +"technical field: True if the location is empty and there is no pending " +"incoming products in the location. Computed only if the location needs to " +"check for emptiness (has an \"only empty\" location storage type)." +msgstr "" +"campo técnico: Verdadero si la ubicación está vacía y no hay productos " +"entrantes pendientes en la ubicación. Sólo se calcula si la ubicación " +"necesita comprobar si está vacía (tiene un tipo de almacenamiento de " +"ubicación \"sólo vacía\")." + +#. module: stock_storage_type +#: model:ir.model.fields,help:stock_storage_type.field_stock_location__leaf_location_ids +msgid "technical field: all the leaves locations" +msgstr "campo técnico: todas las localizaciones de hojas" + +#. module: stock_storage_type +#: model:ir.model.fields,help:stock_storage_type.field_stock_location__leaf_child_location_ids +msgid "technical field: all the leaves sub-locations" +msgstr "campo técnico: todas las sublocalizaciones de hojas" + +#. module: stock_storage_type +#: model:ir.model.fields,help:stock_storage_type.field_stock_location__location_will_contain_product_ids +msgid "" +"technical field: list of products in the location, either now or in pending " +"operations" +msgstr "" +"campo técnico: lista de productos en la ubicación, ya sea ahora o en " +"operaciones pendientes" + +#. module: stock_storage_type +#: model:ir.model.fields,help:stock_storage_type.field_stock_location__location_will_contain_lot_ids +msgid "" +"technical field: list of stock.lots in the location, either now or in " +"pending operations" +msgstr "" +"campo técnico: lista de stock.lotes en la ubicación, ya sea ahora o en " +"operaciones pendientes" + +#. module: stock_storage_type +#: model:ir.model.fields,help:stock_storage_type.field_stock_location__in_move_line_ids +msgid "technical field: the pending incoming stock.move.lines in the location" +msgstr "campo técnico: las líneas de entrada pendientes en la ubicación" + +#. module: stock_storage_type +#: model:ir.model.fields,help:stock_storage_type.field_stock_location__in_move_ids +msgid "technical field: the pending incoming stock.moves in the location" +msgstr "" +"campo técnico: las existencias entrantes pendientes.movimientos en la " +"ubicación" + +#. module: stock_storage_type +#: model:ir.model.fields,help:stock_storage_type.field_stock_location__out_move_line_ids +msgid "technical field: the pending outgoing stock.move.lines in the location" +msgstr "campo técnico: las líneas de salida pendientes en la ubicación" diff --git a/stock_storage_type/i18n/es_AR.po b/stock_storage_type/i18n/es_AR.po new file mode 100644 index 0000000000..86e7a3453f --- /dev/null +++ b/stock_storage_type/i18n/es_AR.po @@ -0,0 +1,1007 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * stock_storage_type +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 17.0\n" +"Report-Msgid-Bugs-To: \n" +"PO-Revision-Date: 2022-06-29 20:05+0000\n" +"Last-Translator: Ignacio Buioli \n" +"Language-Team: none\n" +"Language: es_AR\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: nplurals=2; plural=n != 1;\n" +"X-Generator: Weblate 4.3.2\n" + +#. module: stock_storage_type +#. odoo-python +#: code:addons/stock_storage_type/models/stock_storage_location_sequence.py:0 +#, python-format +msgid "" +" * {location} (WARNING: restrictions are " +"active on location storage types matching this package storage type)" +msgstr "" + +#. module: stock_storage_type +#. odoo-python +#: code:addons/stock_storage_type/models/stock_storage_location_sequence.py:0 +#, python-format +msgid "" +" * {location} (WARNING: no suitable location " +"matching storage type)" +msgstr "" + +#. module: stock_storage_type +#. odoo-python +#: code:addons/stock_storage_type/models/stock_package_type.py:0 +#, python-format +msgid "" +"The \"Put-Away sequence\" must be defined in " +"order to put away packages using this package storage type ({storage})." +msgstr "" + +#. module: stock_storage_type +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_package_type__active +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_storage_location_sequence_cond__active +msgid "Active" +msgstr "Activo" + +#. module: stock_storage_type +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_storage_category__allow_new_product +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_storage_category_capacity__allow_new_product +msgid "Allow New Product" +msgstr "" + +#. module: stock_storage_type +#. odoo-python +#: code:addons/stock_storage_type/models/stock_storage_category_capacity.py:0 +#, python-format +msgid "Allow New Product: " +msgstr "" + +#. module: stock_storage_type +#: model:ir.model.fields.selection,name:stock_storage_type.selection__stock_storage_category_capacity__allow_new_product__mixed +msgid "Allow mixed products" +msgstr "" + +#. module: stock_storage_type +#: model:ir.model.fields,help:stock_storage_type.field_stock_location__package_type_putaway_sequence +msgid "" +"Allow to sort the valid locations by sequence for the storage strategy based " +"on package type" +msgstr "" + +#. module: stock_storage_type +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_package_level__allowed_location_dest_ids +msgid "Allowed Destinations" +msgstr "" + +#. module: stock_storage_type +#: model_terms:ir.ui.view,arch_db:stock_storage_type.stock_storage_location_sequence_cond_form_view +msgid "Archived" +msgstr "Archivado" + +#. module: stock_storage_type +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_package_type__barcode +msgid "Barcode" +msgstr "Código de Barras" + +#. module: stock_storage_type +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_location__computed_storage_capacity_ids +msgid "Capacity" +msgstr "" + +#. module: stock_storage_type +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_storage_location_sequence_cond__code_snippet +msgid "Code Snippet" +msgstr "Snippet de Código" + +#. module: stock_storage_type +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_storage_location_sequence_cond__code_snippet_docs +msgid "Code Snippet Docs" +msgstr "Documentación de Snippet de Código" + +#. module: stock_storage_type +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_storage_category__computed_location_ids +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_storage_category_capacity__computed_location_ids +msgid "Computed Location" +msgstr "" + +#. module: stock_storage_type +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_location__computed_storage_category_id +msgid "Computed Storage Category" +msgstr "" + +#. module: stock_storage_type +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_storage_location_sequence_cond__condition_type +msgid "Condition Type" +msgstr "Tipo de Condición" + +#. module: stock_storage_type +#. odoo-python +#: code:addons/stock_storage_type/models/stock_storage_location_sequence_cond.py:0 +#, python-format +msgid "Condition type is set to `Code`: you must provide a piece of code" +msgstr "" +"Tipo de Condición configurado como `Código`: debe proveer una porción de " +"código" + +#. module: stock_storage_type +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_storage_location_sequence__location_sequence_cond_ids +msgid "Conditions" +msgstr "Condiciones" + +#. module: stock_storage_type +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_storage_location_sequence__create_uid +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_storage_location_sequence_cond__create_uid +msgid "Created by" +msgstr "Creado por" + +#. module: stock_storage_type +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_storage_location_sequence__create_date +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_storage_location_sequence_cond__create_date +msgid "Created on" +msgstr "Creado el" + +#. module: stock_storage_type +#: model:ir.model.fields,help:stock_storage_type.field_product_product__package_type_id +#: model:ir.model.fields,help:stock_storage_type.field_product_template__package_type_id +msgid "" +"Defines a 'default' package type for this product to be applied on packages " +"without product packagings and on put-away computation based on package type " +"for product not in a package" +msgstr "" + +#. module: stock_storage_type +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_storage_category__length_uom_id +msgid "Dimensions Units of Measure" +msgstr "Dimensiones Unidades de Medida" + +#. module: stock_storage_type +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_storage_location_sequence__display_name +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_storage_location_sequence_cond__display_name +msgid "Display Name" +msgstr "Mostrar Nombre" + +#. module: stock_storage_type +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_location__do_not_mix_lots +msgid "Do Not Mix Lots" +msgstr "No Mezcla Lotes" + +#. module: stock_storage_type +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_location__do_not_mix_products +msgid "Do Not Mix Products" +msgstr "No Mezcla Productos" + +#. module: stock_storage_type +#: model:ir.model.fields.selection,name:stock_storage_type.selection__stock_storage_location_sequence_cond__condition_type__code +msgid "Execute code" +msgstr "Ejecutar código" + +#. module: stock_storage_type +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_storage_category_capacity__has_restrictions +msgid "Has Restrictions" +msgstr "Tiene Restricciones" + +#. module: stock_storage_type +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_quant_package__height_in_m +msgid "Height In M" +msgstr "" + +#. module: stock_storage_type +#: model:ir.model.fields,help:stock_storage_type.field_stock_package_type__height_required +msgid "Height is mandatory for packages configured with this storage type." +msgstr "" +"La Altura es obligatoria para paquetes configurados con este tipo de " +"almacenamiento." + +#. module: stock_storage_type +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_package_type__height_required +msgid "Height required for packages" +msgstr "Altura requerida para paquetes" + +#. module: stock_storage_type +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_storage_location_sequence__id +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_storage_location_sequence_cond__id +msgid "ID" +msgstr "ID" + +#. module: stock_storage_type +#: model:ir.model.fields.selection,name:stock_storage_type.selection__stock_storage_category_capacity__allow_new_product__same_lot +msgid "If all lots are the same" +msgstr "" + +#. module: stock_storage_type +#: model:ir.model.fields.selection,name:stock_storage_type.selection__stock_storage_category_capacity__allow_new_product__same +msgid "If all products are same" +msgstr "" + +#. module: stock_storage_type +#: model:ir.model.fields.selection,name:stock_storage_type.selection__stock_storage_category__allow_new_product__same_lot +msgid "If lots are all the same" +msgstr "" + +#. module: stock_storage_type +#: model:ir.model.fields.selection,name:stock_storage_type.selection__stock_storage_category_capacity__allow_new_product__empty +msgid "If the location is empty" +msgstr "" + +#. module: stock_storage_type +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_location__in_move_ids +msgid "In Move" +msgstr "En Movimiento" + +#. module: stock_storage_type +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_location__in_move_line_ids +msgid "In Move Line" +msgstr "En Línea de Movimiento" + +#. module: stock_storage_type +#: model:ir.model,name:stock_storage_type.model_stock_location +msgid "Inventory Locations" +msgstr "Ubicaciones de Inventario" + +#. module: stock_storage_type +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_storage_location_sequence____last_update +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_storage_location_sequence_cond____last_update +msgid "Last Modified on" +msgstr "Última Modificación el" + +#. module: stock_storage_type +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_storage_location_sequence__write_uid +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_storage_location_sequence_cond__write_uid +msgid "Last Updated by" +msgstr "Última Actualización por" + +#. module: stock_storage_type +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_storage_location_sequence__write_date +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_storage_location_sequence_cond__write_date +msgid "Last Updated on" +msgstr "Última Actualización el" + +#. module: stock_storage_type +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_location__leaf_child_location_ids +msgid "Leaf Child Location" +msgstr "Ubicación de Hoja Hija" + +#. module: stock_storage_type +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_location__leaf_location_ids +msgid "Leaf Location" +msgstr "Ubicación Hoja" + +#. module: stock_storage_type +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_storage_location_sequence__location_id +msgid "Location" +msgstr "Ubicación" + +#. module: stock_storage_type +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_location__location_is_empty +msgid "Location Is Empty" +msgstr "La Ubicación está Vacía" + +#. module: stock_storage_type +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_location__location_will_contain_lot_ids +msgid "Location Will Contain Lot" +msgstr "La Ubicación Contendrá Lote" + +#. module: stock_storage_type +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_location__location_will_contain_product_ids +msgid "Location Will Contain Product" +msgstr "La Ubicación Contendrá Producto" + +#. module: stock_storage_type +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_location__max_height_in_m +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_storage_category__max_height_in_m +msgid "Max Height In M" +msgstr "" + +#. module: stock_storage_type +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_storage_category__max_weight_in_kg +msgid "Max Weight In Kg" +msgstr "" + +#. module: stock_storage_type +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_location__max_height +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_storage_category__max_height +msgid "Max height (mm)" +msgstr "Altura máxima (mm)" + +#. module: stock_storage_type +#: model:ir.model.constraint,message:stock_storage_type.constraint_stock_storage_category_positive_max_height +msgid "Max height should be a positive number." +msgstr "" + +#. module: stock_storage_type +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_storage_location_sequence_cond__name +msgid "Name" +msgstr "Nombre" + +#. module: stock_storage_type +#: model:ir.model.fields.selection,name:stock_storage_type.selection__stock_location__pack_putaway_strategy__none +msgid "None" +msgstr "Ninguno" + +#. module: stock_storage_type +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_location__only_empty +msgid "Only Empty" +msgstr "Solo Vacío" + +#. module: stock_storage_type +#: model:ir.model.fields.selection,name:stock_storage_type.selection__stock_location__pack_putaway_strategy__ordered_locations +msgid "Ordered Children Locations" +msgstr "Ubicaciones Hijas Ordenadas" + +#. module: stock_storage_type +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_location__out_move_line_ids +msgid "Out Move Line" +msgstr "Línea de Movimiento de Salida" + +#. module: stock_storage_type +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_quant_package__pack_weight_in_kg +msgid "Pack Weight In Kg" +msgstr "" + +#. module: stock_storage_type +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_storage_location_sequence__package_type_id +msgid "Package Type" +msgstr "" + +#. module: stock_storage_type +#: model:ir.model.fields,field_description:stock_storage_type.field_product_product__package_type_id +#: model:ir.model.fields,field_description:stock_storage_type.field_product_template__package_type_id +msgid "Package type" +msgstr "" + +#. module: stock_storage_type +#. odoo-python +#: code:addons/stock_storage_type/models/stock_quant.py:0 +#, python-format +msgid "Package type {storage} is not allowed into Location {location}" +msgstr "" + +#. module: stock_storage_type +#. odoo-python +#: code:addons/stock_storage_type/models/stock_quant.py:0 +#, python-format +msgid "" +"Package {package} is not allowed into location {location}, because there " +"isn't any storage capacity that allows package type {type} into it:\n" +"\n" +"{fails}" +msgstr "" + +#. module: stock_storage_type +#: model:ir.model,name:stock_storage_type.model_stock_quant_package +msgid "Packages" +msgstr "Paquetes" + +#. module: stock_storage_type +#: model:ir.model,name:stock_storage_type.model_product_template +msgid "Product" +msgstr "" + +#. module: stock_storage_type +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_package_type__product_packaging_ids +msgid "Product Packaging" +msgstr "Empaquetado del Producto" + +#. module: stock_storage_type +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_location__pack_putaway_strategy +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_storage_location_sequence__location_putaway_strategy +msgid "Put-Away Strategy" +msgstr "" + +#. module: stock_storage_type +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_package_type__storage_location_sequence_ids +#: model_terms:ir.ui.view,arch_db:stock_storage_type.stock_package_type_form +msgid "Put-Away sequence" +msgstr "Secuencia de Colocación" + +#. module: stock_storage_type +#. odoo-python +#: code:addons/stock_storage_type/models/stock_package_type.py:0 +#, python-format +msgid "Put-away sequence" +msgstr "Secuencia de colocación" + +#. module: stock_storage_type +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_location__package_type_putaway_sequence +msgid "Putaway Sequence" +msgstr "" + +#. module: stock_storage_type +#: model:ir.model,name:stock_storage_type.model_stock_quant +msgid "Quants" +msgstr "Cantidades" + +#. module: stock_storage_type +#: model_terms:ir.ui.view,arch_db:stock_storage_type.package_level_tree_view_picking_inherit +msgid "Recompute Putaway" +msgstr "Recomputar Colocación" + +#. module: stock_storage_type +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_storage_location_sequence__sequence +msgid "Sequence" +msgstr "Secuencia" + +#. module: stock_storage_type +#: model:ir.model,name:stock_storage_type.model_stock_storage_location_sequence +msgid "Sequence of locations to put-away the package storage type" +msgstr "" +"Secuencia de ubicaciones para guardar el tipo del paquete de almacenamiento" + +#. module: stock_storage_type +#: model_terms:ir.ui.view,arch_db:stock_storage_type.package_storage_location_tree_view +msgid "Show locations" +msgstr "Mostrar ubicaciones" + +#. module: stock_storage_type +#: model:ir.model,name:stock_storage_type.model_stock_package_level +msgid "Stock Package Level" +msgstr "Nivel de Paquete de Existencias" + +#. module: stock_storage_type +#: model:ir.model,name:stock_storage_type.model_stock_storage_location_sequence_cond +msgid "Stock Storage Location Sequence Condition" +msgstr "Condición de Secuencia de Ubicación de Almacenamiento de Stock" + +#. module: stock_storage_type +#: model:ir.actions.act_window,name:stock_storage_type.stock_storage_location_sequence_cond_act_window +msgid "Stock Storage Location Sequence Conditions" +msgstr "Condiciones de Secuencia de Ubicación de Almacenamiento de Stock" + +#. module: stock_storage_type +#: model:ir.model,name:stock_storage_type.model_stock_package_type +msgid "Stock package type" +msgstr "" + +#. module: stock_storage_type +#: model:ir.model.constraint,message:stock_storage_type.constraint_stock_storage_location_sequence_cond_name +msgid "Stock storage location sequence condition name must be unique" +msgstr "" +"El nombre de la condición de secuencia de ubicación de almacenamiento de " +"stock debe ser único" + +#. module: stock_storage_type +#. odoo-python +#: code:addons/stock_storage_type/models/stock_quant.py:0 +#, python-format +msgid "" +"Storage Capacity {storage_capacity} is flagged 'do not mix lots' but there " +"are other lots in location." +msgstr "" + +#. module: stock_storage_type +#. odoo-python +#: code:addons/stock_storage_type/models/stock_quant.py:0 +#, python-format +msgid "" +"Storage Capacity {storage_capacity} is flagged 'do not mix products' but " +"there are other products in location." +msgstr "" + +#. module: stock_storage_type +#. odoo-python +#: code:addons/stock_storage_type/models/stock_quant.py:0 +#, python-format +msgid "" +"Storage Capacity {storage_capacity} is flagged 'only empty' with other " +"quants in location." +msgstr "" + +#. module: stock_storage_type +#: model:ir.model,name:stock_storage_type.model_stock_storage_category +msgid "Storage Category" +msgstr "" + +#. module: stock_storage_type +#: model:ir.model,name:stock_storage_type.model_stock_storage_category_capacity +msgid "Storage Category Capacity" +msgstr "" + +#. module: stock_storage_type +#. odoo-python +#: code:addons/stock_storage_type/models/stock_quant.py:0 +#, python-format +msgid "" +"Storage Category {storage_category} defines max height of {max_h} but the " +"package is bigger: {height}." +msgstr "" + +#. module: stock_storage_type +#. odoo-python +#: code:addons/stock_storage_type/models/stock_quant.py:0 +#, python-format +msgid "" +"Storage Category {storage_category} defines max weight of {max_w} but the " +"package is heavier: {weight_kg}." +msgstr "" + +#. module: stock_storage_type +#: model:ir.ui.menu,name:stock_storage_type.stock_storage_location_sequence_cond_menu +msgid "Storage Location Sequence Conditions" +msgstr "Condiciones de Secuencia de Ubicación de Almacenamiento" + +#. module: stock_storage_type +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_package_type__storage_type_message +msgid "Storage Type Message" +msgstr "Mensaje de Tipo de Almacenamiento" + +#. module: stock_storage_type +#: model:ir.ui.menu,name:stock_storage_type.storage_type_menu +msgid "Storage Types" +msgstr "Tipos de Almacenamiento" + +#. module: stock_storage_type +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_location__storage_location_sequence_ids +msgid "Storage locations sequences" +msgstr "Secuencias de ubicaciones de almacenamiento" + +#. module: stock_storage_type +#: model:ir.model.fields,help:stock_storage_type.field_stock_location__max_height_in_m +#: model:ir.model.fields,help:stock_storage_type.field_stock_quant_package__height_in_m +#: model:ir.model.fields,help:stock_storage_type.field_stock_quant_package__pack_weight_in_kg +#: model:ir.model.fields,help:stock_storage_type.field_stock_storage_category__max_height_in_m +#: model:ir.model.fields,help:stock_storage_type.field_stock_storage_category__max_weight_in_kg +msgid "Technical field, to speed up comparaisons" +msgstr "Campo técnico, para acelerar comparaciones" + +#. module: stock_storage_type +#: model:ir.model.fields,help:stock_storage_type.field_stock_storage_category_capacity__has_restrictions +msgid "Technical: This is used to check if we need to display warning message" +msgstr "" + +#. module: stock_storage_type +#. odoo-python +#: code:addons/stock_storage_type/models/stock_quant_package.py:0 +#, python-format +msgid "The height is mandatory on package {}." +msgstr "La altura es obligatoria en el paquete {}." + +#. module: stock_storage_type +#: model:ir.model.fields,help:stock_storage_type.field_stock_location__max_height +#: model:ir.model.fields,help:stock_storage_type.field_stock_storage_category__max_height +msgid "The max height supported for this storage category." +msgstr "" + +#. module: stock_storage_type +#: model:ir.model.fields,help:stock_storage_type.field_stock_location__pack_putaway_strategy +#: model:ir.model.fields,help:stock_storage_type.field_stock_storage_location_sequence__location_putaway_strategy +msgid "" +"This defines the storage strategy based on package type to use when a " +"product or package is put away in this location.\n" +"None: when moved to this location, it will not be put away any further.\n" +"Ordered Children Locations: when moved to this location, a suitable location " +"will be searched in its children locations according to the restrictions " +"defined on their respective location storage types." +msgstr "" + +#. module: stock_storage_type +#: model:ir.model.fields,help:stock_storage_type.field_stock_location__computed_storage_category_id +msgid "" +"This represents the Storage Category that will be used. It depends either on " +"the category set on the location or on one of its parent." +msgstr "" + +#. module: stock_storage_type +#: model:ir.model.fields,help:stock_storage_type.field_stock_storage_category__length_uom_id +msgid "UoM for height" +msgstr "UdM para la altura" + +#. module: stock_storage_type +#: model:ir.model.fields,help:stock_storage_type.field_stock_storage_category__weight_uom_id +msgid "Weight Unit of Measure" +msgstr "Unidad de Medida del Peso" + +#. module: stock_storage_type +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_storage_category__weight_uom_id +msgid "Weight Units of Measure" +msgstr "Unidades de Medida del Peso" + +#. module: stock_storage_type +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_storage_category__weight_uom_name +msgid "Weight unit of measure label" +msgstr "Etiqueta de la unidad de medida del peso" + +#. module: stock_storage_type +#. odoo-python +#: code:addons/stock_storage_type/models/stock_package_type.py:0 +#, python-format +msgid "" +"When a package with storage type {name} is put away, the strategy will look " +"for an allowed location in the following locations:

{message}

Note: this happens as long as these locations are children " +"of the stock move destination location or as long as these locations are " +"children of the destination location after the (product or category) put-" +"away is applied." +msgstr "" + +#. module: stock_storage_type +#. odoo-python +#: code:addons/stock_storage_type/models/stock_storage_location_sequence_cond.py:0 +#, python-format +msgid "code_snippet should return boolean value into `result` variable." +msgstr "code_snippet debe devolver un valor booleano en la variable `result`." + +#. module: stock_storage_type +#: model:ir.model.fields,help:stock_storage_type.field_stock_location__location_is_empty +msgid "" +"technical field: True if the location is empty and there is no pending " +"incoming products in the location. Computed only if the location needs to " +"check for emptiness (has an \"only empty\" location storage type)." +msgstr "" +"campo técnico: Es Verdadero si la ubicación está vacía y no hay productos " +"entrantes pendientes en la ubicación. Se calcula solo si la ubicación " +"necesita verificar si está vacía (tiene un tipo de ubicación de " +"alamacenamiento como \"solo vacío\")." + +#. module: stock_storage_type +#: model:ir.model.fields,help:stock_storage_type.field_stock_location__leaf_location_ids +msgid "technical field: all the leaves locations" +msgstr "campo técnico: todas las ubicaciones hojas" + +#. module: stock_storage_type +#: model:ir.model.fields,help:stock_storage_type.field_stock_location__leaf_child_location_ids +msgid "technical field: all the leaves sub-locations" +msgstr "campo técnico: todas las sub-ubicaciones hojas" + +#. module: stock_storage_type +#: model:ir.model.fields,help:stock_storage_type.field_stock_location__location_will_contain_product_ids +msgid "" +"technical field: list of products in the location, either now or in pending " +"operations" +msgstr "" +"campo técnico: lista de productos en la ubicación, ya sea ahora o en " +"operaciones pendientes" + +#. module: stock_storage_type +#: model:ir.model.fields,help:stock_storage_type.field_stock_location__location_will_contain_lot_ids +msgid "" +"technical field: list of stock.lots in the location, either now or in " +"pending operations" +msgstr "" + +#. module: stock_storage_type +#: model:ir.model.fields,help:stock_storage_type.field_stock_location__in_move_line_ids +msgid "technical field: the pending incoming stock.move.lines in the location" +msgstr "campo técnico: stock.move.lines de entrada pendientes en la ubicación" + +#. module: stock_storage_type +#: model:ir.model.fields,help:stock_storage_type.field_stock_location__in_move_ids +msgid "technical field: the pending incoming stock.moves in the location" +msgstr "campo técnico: stock.moves de entrada pendientes en la ubicación" + +#. module: stock_storage_type +#: model:ir.model.fields,help:stock_storage_type.field_stock_location__out_move_line_ids +msgid "technical field: the pending outgoing stock.move.lines in the location" +msgstr "campo técnico: stock.move.lines de salida pendientes en la ubicación" + +#, python-format +#~ msgid "" +#~ " * %s (WARNING: restrictions are active on " +#~ "location storage types matching this package storage type)" +#~ msgstr "" +#~ " * %s (ADVERTENCIA: las restricciones " +#~ "están activas en los tipos de almacenamiento de ubicación que coinciden " +#~ "con este tipo de almacenamiento de paquetes)" + +#, python-format +#~ msgid "" +#~ " * %s (WARNING: no suitable location matching " +#~ "storage type)" +#~ msgstr "" +#~ " * %s (ADVERTENCIA: no hay una ubicación " +#~ "adecuada que coincida con el tipo de almacenamiento)" + +#, python-format +#~ msgid "" +#~ "The \"Put-Away sequence\" must be defined in " +#~ "order to put away packages using this package storage type (%s)." +#~ msgstr "" +#~ "La \"Secuencia de almacenamiento\" debe " +#~ "definirse para guardar paquetes utilizando este tipo de almacenamiento de " +#~ "paquetes (%s)." + +#~ msgid "Allowed Destinations Domain" +#~ msgstr "Dominios de Destinos Permitidos" + +#~ msgid "Allowed Location Storage Type" +#~ msgstr "Tipo de Almacenamiento de Ubicación Permitida" + +#~ msgid "Allowed locations storage types" +#~ msgstr "Tipos de almacenamiento de ubicaciones permitidas" + +#~ msgid "Allowed packages storage types" +#~ msgstr "Tipos de almacenamiento de paquetes permitidos" + +#~ msgid "Content restrictions" +#~ msgstr "Restricciones de contenido" + +#~ msgid "" +#~ "Defines a 'default' package storage type for this product to be applied " +#~ "on packages without product packagings for put-away computations." +#~ msgstr "" +#~ "Define un tipo de almacenamiento de paquete 'predeterminado' para que " +#~ "este producto se aplique en paquetes sin embalajes de producto para " +#~ "cálculos de ubicación." + +#~ msgid "Height in m" +#~ msgstr "Altura en m" + +#~ msgid "" +#~ "If checked, moves to the destination location will only be allowed if the " +#~ "location contains product of the same lot." +#~ msgstr "" +#~ "Si está marcado, los traslados a la ubicación de destino solo se " +#~ "permitirán si la ubicación contiene productos del mismo lote." + +#~ msgid "" +#~ "If checked, moves to the destination location will only be allowed if the " +#~ "location contains the same product." +#~ msgstr "" +#~ "Si está marcado, los movimientos a la ubicación de destino solo se " +#~ "permitirán si la ubicación contiene el mismo producto." + +#~ msgid "" +#~ "If checked, moves to the destination location will only be allowed if " +#~ "there are not any existing quant nor planned move on this location" +#~ msgstr "" +#~ "Si está marcado, los movimientos a la ubicación de destino solo se " +#~ "permitirán si no hay ninguna cantidad existente ni un movimiento " +#~ "planificado en esta ubicación" + +#~ msgid "" +#~ "If defined, moves to the destination location will only be allowed if the " +#~ "packaging height is lower than this maximum." +#~ msgstr "" +#~ "Si se define, los movimientos a la ubicación de destino solo se " +#~ "permitirán si la altura del embalaje es menor que este máximo." + +#~ msgid "" +#~ "If defined, moves to the destination location will only be allowed if the " +#~ "packaging wight is lower than this maximum." +#~ msgstr "" +#~ "Si se define, los traslados a la ubicación de destino solo se permitirán " +#~ "si el peso del embalaje es inferior a este máximo." + +#~ msgid "Length unit of measure label" +#~ msgstr "Etiqueta de Unidad de Medida de Longitud" + +#~ msgid "Location Package storage type relation" +#~ msgstr "Ubicación Relación del tipo de almacenamiento del paquete" + +#~ msgid "Location Storage Type" +#~ msgstr "Tipo de Ubicación de Almacenamiento" + +#~ msgid "Location Storage Types" +#~ msgstr "Tipos de Ubicación de Almacenamiento" + +#~ msgid "Location storage type" +#~ msgstr "Tipo de ubicación de almacenamiento" + +#, python-format +#~ msgid "" +#~ "Location storage type %s defines max height of %s but the package is " +#~ "bigger: %s." +#~ msgstr "" +#~ "El tipo de ubicación de almacenamiento %s define una altura máxima de %s " +#~ "pero el paquete es más grande: %s." + +#, python-format +#~ msgid "" +#~ "Location storage type %s defines max weight of %s but the package is " +#~ "heavier: %s." +#~ msgstr "" +#~ "El tipo de ubicación de almacenamiento %s define un peso máximo de %s " +#~ "pero el paquete es más pesado: %s." + +#, python-format +#~ msgid "" +#~ "Location storage type %s is flagged 'do not mix lots' but there are other " +#~ "lots in location." +#~ msgstr "" +#~ "El tipo de ubicación de almacenamiento %s está marcado como 'no mezcla " +#~ "lotes' pero hay otros lotes en la ubicación." + +#, python-format +#~ msgid "" +#~ "Location storage type %s is flagged 'do not mix products' but there are " +#~ "other products in location." +#~ msgstr "" +#~ "El tipo de ubicación de almacenamiento %s está marcado como 'no mezcla " +#~ "productos' pero hay otros productos en la ubicación." + +#, python-format +#~ msgid "" +#~ "Location storage type %s is flagged 'only empty' with other quants in " +#~ "location." +#~ msgstr "" +#~ "El tipo de ubicación de almacenamiento %s está marcado como 'solo vacío' " +#~ "con otras cantidades en la ubicación." + +#~ msgid "" +#~ "Location storage types defined here will be applied on all the children " +#~ "locations that do not define their own location storage types." +#~ msgstr "" +#~ "Los tipos de ubicación de almacenamiento definidos aquí se aplicarán a " +#~ "todas las ubicaciones secundarias que no definen sus propios tipos de " +#~ "ubicación de almacenamiento." + +#~ msgid "Locations" +#~ msgstr "Ubicaciones" + +#~ msgid "Locations storage types that can accept such a package storage type." +#~ msgstr "" +#~ "Los tipos de ubicación de almacenamiento que puede aceptar dicho tipo de " +#~ "almacenamiento de paquetes." + +#~ msgid "" +#~ "Locations storage types that this location can accept. (If no location " +#~ "storage types are defined on this specific location, the location storage " +#~ "types of the parent location are applied)." +#~ msgstr "" +#~ "Tipos de ubicaciones de almacenamiento que esta ubicación puede aceptar. " +#~ "(Si no se definen tipos de ubicación de almacenamiento en esta ubicación " +#~ "específica, se aplican los tipos de ubicación de almacenamiento de la " +#~ "ubicación principal)." + +#~ msgid "Max height (m)" +#~ msgstr "Altura máxima (m)" + +#~ msgid "Max height in m" +#~ msgstr "Altura máxima en metros" + +#~ msgid "Max weight (kg)" +#~ msgstr "Peso máximo (kg)" + +#~ msgid "Max weight in kg" +#~ msgstr "Peso máximo en kg" + +#~ msgid "Pack Putaway Sequence" +#~ msgstr "Secuencia de Paquete de Almacenamiento" + +#~ msgid "Pack weight in kg" +#~ msgstr "Peso del paquete en kg" + +#, python-format +#~ msgid "" +#~ "Package %s is not allowed into location %s, because there isn't any " +#~ "location storage type that allows package storage type %s into it:\n" +#~ "\n" +#~ "%s" +#~ msgstr "" +#~ "El paquete %s no está permitido en la ubicación %s, porque no hay ningún " +#~ "tipo de ubicación de almacenamiento que permita el tipo de almacenamiento " +#~ "de paquetes %s en él:\n" +#~ "\n" +#~ "%s" + +#~ msgid "Package Storage Type" +#~ msgstr "Tipo de Paquete de Almacenamiento" + +#~ msgid "Package Storage Types" +#~ msgstr "Tipos de Paquetes de Almacenamiento" + +#~ msgid "Package storage type" +#~ msgstr "Tipo de paquete de almacenamiento" + +#, python-format +#~ msgid "Package storage type %s is not allowed into Location %s" +#~ msgstr "" +#~ "El tipo de paquete de almacenamiento %s no está permitido en la Ubicación " +#~ "%s" + +#~ msgid "" +#~ "Package storage type for put-away computation. Get value automatically " +#~ "from the packaging if set, or from the product ifthe package contains " +#~ "only a single product." +#~ msgstr "" +#~ "Tipo de paquetes de almacenamiento para el cálculo de ubicación. Obtenga " +#~ "valor automáticamente del paquete si está configurado, o del producto si " +#~ "el paquete contiene solo un producto." + +#~ msgid "" +#~ "Package storage types that are allowed on locations where this location " +#~ "storage type is defined." +#~ msgstr "" +#~ "Tipos de paquetes de almacenamiento que están permitidos en ubicaciones " +#~ "donde se define este tipo de ubicación de almacenamiento." + +#~ msgid "Packs Put-Away Strategy" +#~ msgstr "Estrategia de Colocación de Paquetes" + +#~ msgid "Product Template" +#~ msgstr "Plantilla del Producto" + +#~ msgid "Size restrictions" +#~ msgstr "Restricciones de tamaño" + +#~ msgid "Stock Move" +#~ msgstr "Movimiento de Inventario" + +#~ msgid "Storage Types Mapping" +#~ msgstr "Asignación de Tipos de Almacenamiento" + +#~ msgid "Storage locations" +#~ msgstr "Ubicaciones de almacenamiento" + +#~ msgid "The max height supported among allowed location storage types." +#~ msgstr "" +#~ "La altura máxima soportada entre los tipos de ubicación de almacenamiento " +#~ "permitidos." + +#~ msgid "" +#~ "The package storage type will be set on stock packages using this product " +#~ "packaging, in order to compute its putaway." +#~ msgstr "" +#~ "El tipo del paquete de almacenamiento se establecerá en los paquetes de " +#~ "existencias que utilicen el empaquetado de este producto para calcular su " +#~ "entrada en inventario." + +#~ msgid "" +#~ "This defines the storage strategy to use when packs are put away in this " +#~ "location.\n" +#~ "None: when a pack is moved to this location, it will not be put away any " +#~ "further.\n" +#~ "Ordered Children Locations: when a pack is moved to this location, a " +#~ "suitable location will be searched in its children locations according to " +#~ "the restrictions defined on their respective location storage types." +#~ msgstr "" +#~ "Esto define la estrategia de almacenamiento que se utilizará cuando los " +#~ "paquetes se guarden en esta ubicación.\n" +#~ "Ninguno: cuando un paquete se mueve a esta ubicación, no se guardará " +#~ "más.\n" +#~ "Ubicaciones Hijas Ordenadas: cuando un paquete se mueve a esta ubicación, " +#~ "se buscará una ubicación adecuada en sus ubicaciones hijas de acuerdo con " +#~ "las restricciones definidas en sus respectivos tipos de almacenamiento de " +#~ "ubicación." + +#, python-format +#~ msgid "" +#~ "When a package with storage type %s is put away, the strategy will look " +#~ "for an allowed location in the following locations:

%s

Note: this happens as long as these locations are children of " +#~ "the stock move destination location or as long as these locations are " +#~ "children of the destination location after the (product or category) put-" +#~ "away is applied." +#~ msgstr "" +#~ "Cuando se guarda un paquete con el tipo de almacenamiento %s, la " +#~ "estrategia buscará una ubicación permitida en las siguientes ubicaciones: " +#~ "

%s

Nota: esto sucede siempre que estas " +#~ "ubicaciones sean hijas de la ubicación de destino del movimiento de " +#~ "inventario o siempre que estas ubicaciones sean hijas de la ubicación " +#~ "de destino después de que se aplique la ubicación de (producto o " +#~ "categoría)." + +#, python-format +#~ msgid "" +#~ "You cannot set 'Do not mix lots' or 'Do not mix products' with 'Only " +#~ "empty' constraint." +#~ msgstr "" +#~ "No se puede configurar 'No mezcla lotes' o 'No mezcla prodictos' con la " +#~ "restricción 'Solo vacío'." + +#, python-format +#~ msgid "" +#~ "You cannot set 'Do not mix lots' without setting 'Do not mix products' " +#~ "constraint." +#~ msgstr "" +#~ "No se puede configurar 'No Mezcla Lotes' sin configurar la restricción " +#~ "'No Mezcla Productos'." + +#~ msgid "kg" +#~ msgstr "kg" + +#~ msgid "m³" +#~ msgstr "m³" + +#~ msgid "" +#~ "technical field: list of stock.production.lots in the location, either " +#~ "now or in pending operations" +#~ msgstr "" +#~ "campo técnico: lista de stock.production.lots en la ubicación, ya sea " +#~ "ahora o en operaciones pendientes" diff --git a/stock_storage_type/i18n/fr.po b/stock_storage_type/i18n/fr.po new file mode 100644 index 0000000000..137d6f2041 --- /dev/null +++ b/stock_storage_type/i18n/fr.po @@ -0,0 +1,662 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * stock_storage_type +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 17.0\n" +"Report-Msgid-Bugs-To: \n" +"Last-Translator: Automatically generated\n" +"Language-Team: none\n" +"Language: fr\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: nplurals=2; plural=n > 1;\n" + +#. module: stock_storage_type +#. odoo-python +#: code:addons/stock_storage_type/models/stock_storage_location_sequence.py:0 +#, python-format +msgid "" +" * {location} (WARNING: restrictions are " +"active on location storage types matching this package storage type)" +msgstr "" + +#. module: stock_storage_type +#. odoo-python +#: code:addons/stock_storage_type/models/stock_storage_location_sequence.py:0 +#, python-format +msgid "" +" * {location} (WARNING: no suitable location " +"matching storage type)" +msgstr "" + +#. module: stock_storage_type +#. odoo-python +#: code:addons/stock_storage_type/models/stock_package_type.py:0 +#, python-format +msgid "" +"The \"Put-Away sequence\" must be defined in " +"order to put away packages using this package storage type " +"({storage})." +msgstr "" + +#. module: stock_storage_type +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_package_type__active +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_storage_location_sequence_cond__active +msgid "Active" +msgstr "" + +#. module: stock_storage_type +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_storage_category__allow_new_product +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_storage_category_capacity__allow_new_product +msgid "Allow New Product" +msgstr "" + +#. module: stock_storage_type +#. odoo-python +#: code:addons/stock_storage_type/models/stock_storage_category_capacity.py:0 +#, python-format +msgid "Allow New Product: " +msgstr "" + +#. module: stock_storage_type +#: model:ir.model.fields.selection,name:stock_storage_type.selection__stock_storage_category_capacity__allow_new_product__mixed +msgid "Allow mixed products" +msgstr "" + +#. module: stock_storage_type +#: model:ir.model.fields,help:stock_storage_type.field_stock_location__package_type_putaway_sequence +msgid "" +"Allow to sort the valid locations by sequence for the storage strategy based" +" on package type" +msgstr "" + +#. module: stock_storage_type +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_package_level__allowed_location_dest_ids +msgid "Allowed Destinations" +msgstr "" + +#. module: stock_storage_type +#: model_terms:ir.ui.view,arch_db:stock_storage_type.stock_storage_location_sequence_cond_form_view +msgid "Archived" +msgstr "" + +#. module: stock_storage_type +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_package_type__barcode +msgid "Barcode" +msgstr "" + +#. module: stock_storage_type +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_location__computed_storage_capacity_ids +msgid "Capacity" +msgstr "" + +#. module: stock_storage_type +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_storage_location_sequence_cond__code_snippet +msgid "Code Snippet" +msgstr "" + +#. module: stock_storage_type +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_storage_location_sequence_cond__code_snippet_docs +msgid "Code Snippet Docs" +msgstr "" + +#. module: stock_storage_type +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_storage_category__computed_location_ids +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_storage_category_capacity__computed_location_ids +msgid "Computed Location" +msgstr "" + +#. module: stock_storage_type +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_location__computed_storage_category_id +msgid "Computed Storage Category" +msgstr "" + +#. module: stock_storage_type +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_storage_location_sequence_cond__condition_type +msgid "Condition Type" +msgstr "" + +#. module: stock_storage_type +#. odoo-python +#: code:addons/stock_storage_type/models/stock_storage_location_sequence_cond.py:0 +#, python-format +msgid "Condition type is set to `Code`: you must provide a piece of code" +msgstr "" + +#. module: stock_storage_type +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_storage_location_sequence__location_sequence_cond_ids +msgid "Conditions" +msgstr "" + +#. module: stock_storage_type +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_storage_location_sequence__create_uid +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_storage_location_sequence_cond__create_uid +msgid "Created by" +msgstr "" + +#. module: stock_storage_type +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_storage_location_sequence__create_date +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_storage_location_sequence_cond__create_date +msgid "Created on" +msgstr "" + +#. module: stock_storage_type +#: model:ir.model.fields,help:stock_storage_type.field_product_product__package_type_id +#: model:ir.model.fields,help:stock_storage_type.field_product_template__package_type_id +msgid "" +"Defines a 'default' package type for this product to be applied on packages " +"without product packagings and on put-away computation based on package type" +" for product not in a package" +msgstr "" + +#. module: stock_storage_type +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_storage_category__length_uom_id +msgid "Dimensions Units of Measure" +msgstr "" + +#. module: stock_storage_type +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_storage_location_sequence__display_name +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_storage_location_sequence_cond__display_name +msgid "Display Name" +msgstr "" + +#. module: stock_storage_type +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_location__do_not_mix_lots +msgid "Do Not Mix Lots" +msgstr "" + +#. module: stock_storage_type +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_location__do_not_mix_products +msgid "Do Not Mix Products" +msgstr "" + +#. module: stock_storage_type +#: model:ir.model.fields.selection,name:stock_storage_type.selection__stock_storage_location_sequence_cond__condition_type__code +msgid "Execute code" +msgstr "" + +#. module: stock_storage_type +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_storage_category_capacity__has_restrictions +msgid "Has Restrictions" +msgstr "" + +#. module: stock_storage_type +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_quant_package__height_in_m +msgid "Height In M" +msgstr "" + +#. module: stock_storage_type +#: model:ir.model.fields,help:stock_storage_type.field_stock_package_type__height_required +msgid "Height is mandatory for packages configured with this storage type." +msgstr "" + +#. module: stock_storage_type +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_package_type__height_required +msgid "Height required for packages" +msgstr "" + +#. module: stock_storage_type +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_storage_location_sequence__id +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_storage_location_sequence_cond__id +msgid "ID" +msgstr "" + +#. module: stock_storage_type +#: model:ir.model.fields.selection,name:stock_storage_type.selection__stock_storage_category_capacity__allow_new_product__same_lot +msgid "If all lots are the same" +msgstr "" + +#. module: stock_storage_type +#: model:ir.model.fields.selection,name:stock_storage_type.selection__stock_storage_category_capacity__allow_new_product__same +msgid "If all products are same" +msgstr "" + +#. module: stock_storage_type +#: model:ir.model.fields.selection,name:stock_storage_type.selection__stock_storage_category__allow_new_product__same_lot +msgid "If lots are all the same" +msgstr "" + +#. module: stock_storage_type +#: model:ir.model.fields.selection,name:stock_storage_type.selection__stock_storage_category_capacity__allow_new_product__empty +msgid "If the location is empty" +msgstr "" + +#. module: stock_storage_type +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_location__in_move_ids +msgid "In Move" +msgstr "" + +#. module: stock_storage_type +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_location__in_move_line_ids +msgid "In Move Line" +msgstr "" + +#. module: stock_storage_type +#: model:ir.model,name:stock_storage_type.model_stock_location +msgid "Inventory Locations" +msgstr "" + +#. module: stock_storage_type +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_storage_location_sequence____last_update +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_storage_location_sequence_cond____last_update +msgid "Last Modified on" +msgstr "" + +#. module: stock_storage_type +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_storage_location_sequence__write_uid +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_storage_location_sequence_cond__write_uid +msgid "Last Updated by" +msgstr "" + +#. module: stock_storage_type +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_storage_location_sequence__write_date +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_storage_location_sequence_cond__write_date +msgid "Last Updated on" +msgstr "" + +#. module: stock_storage_type +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_location__leaf_child_location_ids +msgid "Leaf Child Location" +msgstr "" + +#. module: stock_storage_type +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_location__leaf_location_ids +msgid "Leaf Location" +msgstr "" + +#. module: stock_storage_type +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_storage_location_sequence__location_id +msgid "Location" +msgstr "" + +#. module: stock_storage_type +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_location__location_is_empty +msgid "Location Is Empty" +msgstr "" + +#. module: stock_storage_type +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_location__location_will_contain_lot_ids +msgid "Location Will Contain Lot" +msgstr "" + +#. module: stock_storage_type +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_location__location_will_contain_product_ids +msgid "Location Will Contain Product" +msgstr "" + +#. module: stock_storage_type +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_location__max_height_in_m +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_storage_category__max_height_in_m +msgid "Max Height In M" +msgstr "" + +#. module: stock_storage_type +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_storage_category__max_weight_in_kg +msgid "Max Weight In Kg" +msgstr "" + +#. module: stock_storage_type +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_location__max_height +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_storage_category__max_height +msgid "Max height (mm)" +msgstr "" + +#. module: stock_storage_type +#: model:ir.model.constraint,message:stock_storage_type.constraint_stock_storage_category_positive_max_height +msgid "Max height should be a positive number." +msgstr "" + +#. module: stock_storage_type +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_storage_location_sequence_cond__name +msgid "Name" +msgstr "" + +#. module: stock_storage_type +#: model:ir.model.fields.selection,name:stock_storage_type.selection__stock_location__pack_putaway_strategy__none +msgid "None" +msgstr "" + +#. module: stock_storage_type +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_location__only_empty +msgid "Only Empty" +msgstr "" + +#. module: stock_storage_type +#: model:ir.model.fields.selection,name:stock_storage_type.selection__stock_location__pack_putaway_strategy__ordered_locations +msgid "Ordered Children Locations" +msgstr "" + +#. module: stock_storage_type +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_location__out_move_line_ids +msgid "Out Move Line" +msgstr "" + +#. module: stock_storage_type +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_quant_package__pack_weight_in_kg +msgid "Pack Weight In Kg" +msgstr "" + +#. module: stock_storage_type +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_storage_location_sequence__package_type_id +msgid "Package Type" +msgstr "" + +#. module: stock_storage_type +#: model:ir.model.fields,field_description:stock_storage_type.field_product_product__package_type_id +#: model:ir.model.fields,field_description:stock_storage_type.field_product_template__package_type_id +msgid "Package type" +msgstr "" + +#. module: stock_storage_type +#. odoo-python +#: code:addons/stock_storage_type/models/stock_quant.py:0 +#, python-format +msgid "Package type {storage} is not allowed into Location {location}" +msgstr "" + +#. module: stock_storage_type +#. odoo-python +#: code:addons/stock_storage_type/models/stock_quant.py:0 +#, python-format +msgid "" +"Package {package} is not allowed into location {location}, because there isn't any storage capacity that allows package type {type} into it:\n" +"\n" +"{fails}" +msgstr "" + +#. module: stock_storage_type +#: model:ir.model,name:stock_storage_type.model_stock_quant_package +msgid "Packages" +msgstr "" + +#. module: stock_storage_type +#: model:ir.model,name:stock_storage_type.model_product_template +msgid "Product" +msgstr "" + +#. module: stock_storage_type +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_package_type__product_packaging_ids +msgid "Product Packaging" +msgstr "" + +#. module: stock_storage_type +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_location__pack_putaway_strategy +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_storage_location_sequence__location_putaway_strategy +msgid "Put-Away Strategy" +msgstr "" + +#. module: stock_storage_type +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_package_type__storage_location_sequence_ids +#: model_terms:ir.ui.view,arch_db:stock_storage_type.stock_package_type_form +msgid "Put-Away sequence" +msgstr "" + +#. module: stock_storage_type +#. odoo-python +#: code:addons/stock_storage_type/models/stock_package_type.py:0 +#, python-format +msgid "Put-away sequence" +msgstr "" + +#. module: stock_storage_type +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_location__package_type_putaway_sequence +msgid "Putaway Sequence" +msgstr "" + +#. module: stock_storage_type +#: model:ir.model,name:stock_storage_type.model_stock_quant +msgid "Quants" +msgstr "" + +#. module: stock_storage_type +#: model_terms:ir.ui.view,arch_db:stock_storage_type.package_level_tree_view_picking_inherit +msgid "Recompute Putaway" +msgstr "" + +#. module: stock_storage_type +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_storage_location_sequence__sequence +msgid "Sequence" +msgstr "" + +#. module: stock_storage_type +#: model:ir.model,name:stock_storage_type.model_stock_storage_location_sequence +msgid "Sequence of locations to put-away the package storage type" +msgstr "" + +#. module: stock_storage_type +#: model_terms:ir.ui.view,arch_db:stock_storage_type.package_storage_location_tree_view +msgid "Show locations" +msgstr "" + +#. module: stock_storage_type +#: model:ir.model,name:stock_storage_type.model_stock_package_level +msgid "Stock Package Level" +msgstr "" + +#. module: stock_storage_type +#: model:ir.model,name:stock_storage_type.model_stock_storage_location_sequence_cond +msgid "Stock Storage Location Sequence Condition" +msgstr "" + +#. module: stock_storage_type +#: model:ir.actions.act_window,name:stock_storage_type.stock_storage_location_sequence_cond_act_window +msgid "Stock Storage Location Sequence Conditions" +msgstr "" + +#. module: stock_storage_type +#: model:ir.model,name:stock_storage_type.model_stock_package_type +msgid "Stock package type" +msgstr "" + +#. module: stock_storage_type +#: model:ir.model.constraint,message:stock_storage_type.constraint_stock_storage_location_sequence_cond_name +msgid "Stock storage location sequence condition name must be unique" +msgstr "" + +#. module: stock_storage_type +#. odoo-python +#: code:addons/stock_storage_type/models/stock_quant.py:0 +#, python-format +msgid "" +"Storage Capacity {storage_capacity} is flagged 'do not mix lots' but there " +"are other lots in location." +msgstr "" + +#. module: stock_storage_type +#. odoo-python +#: code:addons/stock_storage_type/models/stock_quant.py:0 +#, python-format +msgid "" +"Storage Capacity {storage_capacity} is flagged 'do not mix products' but " +"there are other products in location." +msgstr "" + +#. module: stock_storage_type +#. odoo-python +#: code:addons/stock_storage_type/models/stock_quant.py:0 +#, python-format +msgid "" +"Storage Capacity {storage_capacity} is flagged 'only empty' with other " +"quants in location." +msgstr "" + +#. module: stock_storage_type +#: model:ir.model,name:stock_storage_type.model_stock_storage_category +msgid "Storage Category" +msgstr "" + +#. module: stock_storage_type +#: model:ir.model,name:stock_storage_type.model_stock_storage_category_capacity +msgid "Storage Category Capacity" +msgstr "" + +#. module: stock_storage_type +#. odoo-python +#: code:addons/stock_storage_type/models/stock_quant.py:0 +#, python-format +msgid "" +"Storage Category {storage_category} defines max height of {max_h} but the " +"package is bigger: {height}." +msgstr "" + +#. module: stock_storage_type +#. odoo-python +#: code:addons/stock_storage_type/models/stock_quant.py:0 +#, python-format +msgid "" +"Storage Category {storage_category} defines max weight of {max_w} but the " +"package is heavier: {weight_kg}." +msgstr "" + +#. module: stock_storage_type +#: model:ir.ui.menu,name:stock_storage_type.stock_storage_location_sequence_cond_menu +msgid "Storage Location Sequence Conditions" +msgstr "" + +#. module: stock_storage_type +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_package_type__storage_type_message +msgid "Storage Type Message" +msgstr "" + +#. module: stock_storage_type +#: model:ir.ui.menu,name:stock_storage_type.storage_type_menu +msgid "Storage Types" +msgstr "" + +#. module: stock_storage_type +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_location__storage_location_sequence_ids +msgid "Storage locations sequences" +msgstr "" + +#. module: stock_storage_type +#: model:ir.model.fields,help:stock_storage_type.field_stock_location__max_height_in_m +#: model:ir.model.fields,help:stock_storage_type.field_stock_quant_package__height_in_m +#: model:ir.model.fields,help:stock_storage_type.field_stock_quant_package__pack_weight_in_kg +#: model:ir.model.fields,help:stock_storage_type.field_stock_storage_category__max_height_in_m +#: model:ir.model.fields,help:stock_storage_type.field_stock_storage_category__max_weight_in_kg +msgid "Technical field, to speed up comparaisons" +msgstr "" + +#. module: stock_storage_type +#: model:ir.model.fields,help:stock_storage_type.field_stock_storage_category_capacity__has_restrictions +msgid "Technical: This is used to check if we need to display warning message" +msgstr "" + +#. module: stock_storage_type +#. odoo-python +#: code:addons/stock_storage_type/models/stock_quant_package.py:0 +#, python-format +msgid "The height is mandatory on package {}." +msgstr "" + +#. module: stock_storage_type +#: model:ir.model.fields,help:stock_storage_type.field_stock_location__max_height +#: model:ir.model.fields,help:stock_storage_type.field_stock_storage_category__max_height +msgid "The max height supported for this storage category." +msgstr "" + +#. module: stock_storage_type +#: model:ir.model.fields,help:stock_storage_type.field_stock_location__pack_putaway_strategy +#: model:ir.model.fields,help:stock_storage_type.field_stock_storage_location_sequence__location_putaway_strategy +msgid "" +"This defines the storage strategy based on package type to use when a product or package is put away in this location.\n" +"None: when moved to this location, it will not be put away any further.\n" +"Ordered Children Locations: when moved to this location, a suitable location will be searched in its children locations according to the restrictions defined on their respective location storage types." +msgstr "" + +#. module: stock_storage_type +#: model:ir.model.fields,help:stock_storage_type.field_stock_location__computed_storage_category_id +msgid "" +"This represents the Storage Category that will be used. It depends either on" +" the category set on the location or on one of its parent." +msgstr "" + +#. module: stock_storage_type +#: model:ir.model.fields,help:stock_storage_type.field_stock_storage_category__length_uom_id +msgid "UoM for height" +msgstr "" + +#. module: stock_storage_type +#: model:ir.model.fields,help:stock_storage_type.field_stock_storage_category__weight_uom_id +msgid "Weight Unit of Measure" +msgstr "" + +#. module: stock_storage_type +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_storage_category__weight_uom_id +msgid "Weight Units of Measure" +msgstr "" + +#. module: stock_storage_type +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_storage_category__weight_uom_name +msgid "Weight unit of measure label" +msgstr "" + +#. module: stock_storage_type +#. odoo-python +#: code:addons/stock_storage_type/models/stock_package_type.py:0 +#, python-format +msgid "" +"When a package with storage type {name} is put away, the strategy will look " +"for an allowed location in the following locations:

{message} " +"

Note: this happens as long as these locations are " +"children of the stock move destination location or as long as these " +"locations are children of the destination location after the (product or " +"category) put-away is applied." +msgstr "" + +#. module: stock_storage_type +#. odoo-python +#: code:addons/stock_storage_type/models/stock_storage_location_sequence_cond.py:0 +#, python-format +msgid "code_snippet should return boolean value into `result` variable." +msgstr "" + +#. module: stock_storage_type +#: model:ir.model.fields,help:stock_storage_type.field_stock_location__location_is_empty +msgid "" +"technical field: True if the location is empty and there is no pending " +"incoming products in the location. Computed only if the location needs to " +"check for emptiness (has an \"only empty\" location storage type)." +msgstr "" + +#. module: stock_storage_type +#: model:ir.model.fields,help:stock_storage_type.field_stock_location__leaf_location_ids +msgid "technical field: all the leaves locations" +msgstr "" + +#. module: stock_storage_type +#: model:ir.model.fields,help:stock_storage_type.field_stock_location__leaf_child_location_ids +msgid "technical field: all the leaves sub-locations" +msgstr "" + +#. module: stock_storage_type +#: model:ir.model.fields,help:stock_storage_type.field_stock_location__location_will_contain_product_ids +msgid "" +"technical field: list of products in the location, either now or in pending " +"operations" +msgstr "" + +#. module: stock_storage_type +#: model:ir.model.fields,help:stock_storage_type.field_stock_location__location_will_contain_lot_ids +msgid "" +"technical field: list of stock.lots in the location, either now or in " +"pending operations" +msgstr "" + +#. module: stock_storage_type +#: model:ir.model.fields,help:stock_storage_type.field_stock_location__in_move_line_ids +msgid "technical field: the pending incoming stock.move.lines in the location" +msgstr "" + +#. module: stock_storage_type +#: model:ir.model.fields,help:stock_storage_type.field_stock_location__in_move_ids +msgid "technical field: the pending incoming stock.moves in the location" +msgstr "" + +#. module: stock_storage_type +#: model:ir.model.fields,help:stock_storage_type.field_stock_location__out_move_line_ids +msgid "technical field: the pending outgoing stock.move.lines in the location" +msgstr "" diff --git a/stock_storage_type/i18n/it.po b/stock_storage_type/i18n/it.po new file mode 100644 index 0000000000..cca6dc46ee --- /dev/null +++ b/stock_storage_type/i18n/it.po @@ -0,0 +1,724 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * stock_storage_type +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 17.0\n" +"Report-Msgid-Bugs-To: \n" +"PO-Revision-Date: 2024-06-07 12:35+0000\n" +"Last-Translator: mymage \n" +"Language-Team: none\n" +"Language: it\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: nplurals=2; plural=n != 1;\n" +"X-Generator: Weblate 4.17\n" + +#. module: stock_storage_type +#. odoo-python +#: code:addons/stock_storage_type/models/stock_storage_location_sequence.py:0 +#, python-format +msgid "" +" * {location} (WARNING: restrictions are " +"active on location storage types matching this package storage type)" +msgstr "" +" * {location} (ATTENZIONE: ci sono " +"restrizioni attive nei tipi stoccaggio ubicazione corrispondenti a questo " +"tipo stoccaggio collo)" + +#. module: stock_storage_type +#. odoo-python +#: code:addons/stock_storage_type/models/stock_storage_location_sequence.py:0 +#, python-format +msgid "" +" * {location} (WARNING: no suitable location " +"matching storage type)" +msgstr "" +" * {location} (ATTENZIONE: nessuna ubicazione " +"disponibile corrispondente al tipo stoccaggio)" + +#. module: stock_storage_type +#. odoo-python +#: code:addons/stock_storage_type/models/stock_package_type.py:0 +#, python-format +msgid "" +"The \"Put-Away sequence\" must be defined in " +"order to put away packages using this package storage type ({storage})." +msgstr "" +"La \"Sequenza deposito\" deve essere definita " +"per depositare i colli utilizzando questo tipo stoccaggio collo " +"({storage})." + +#. module: stock_storage_type +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_package_type__active +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_storage_location_sequence_cond__active +msgid "Active" +msgstr "Attiva" + +#. module: stock_storage_type +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_storage_category__allow_new_product +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_storage_category_capacity__allow_new_product +msgid "Allow New Product" +msgstr "Consente nuovo prodotto" + +#. module: stock_storage_type +#. odoo-python +#: code:addons/stock_storage_type/models/stock_storage_category_capacity.py:0 +#, python-format +msgid "Allow New Product: " +msgstr "Consente nuovo prodotto: " + +#. module: stock_storage_type +#: model:ir.model.fields.selection,name:stock_storage_type.selection__stock_storage_category_capacity__allow_new_product__mixed +msgid "Allow mixed products" +msgstr "Consente prodotti misti" + +#. module: stock_storage_type +#: model:ir.model.fields,help:stock_storage_type.field_stock_location__package_type_putaway_sequence +msgid "" +"Allow to sort the valid locations by sequence for the storage strategy based " +"on package type" +msgstr "" +"Consente di ordinare le ubicazioni valide per sequenza per la strategia di " +"stoccaggio basata sul tipo collo" + +#. module: stock_storage_type +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_package_level__allowed_location_dest_ids +msgid "Allowed Destinations" +msgstr "Destinazioni consentite" + +#. module: stock_storage_type +#: model_terms:ir.ui.view,arch_db:stock_storage_type.stock_storage_location_sequence_cond_form_view +msgid "Archived" +msgstr "In archivio" + +#. module: stock_storage_type +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_package_type__barcode +msgid "Barcode" +msgstr "Codice a barre" + +#. module: stock_storage_type +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_location__computed_storage_capacity_ids +msgid "Capacity" +msgstr "Capacità" + +#. module: stock_storage_type +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_storage_location_sequence_cond__code_snippet +msgid "Code Snippet" +msgstr "Esempio codice" + +#. module: stock_storage_type +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_storage_location_sequence_cond__code_snippet_docs +msgid "Code Snippet Docs" +msgstr "Documenti esempio codice" + +#. module: stock_storage_type +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_storage_category__computed_location_ids +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_storage_category_capacity__computed_location_ids +msgid "Computed Location" +msgstr "Calcola ubicazione" + +#. module: stock_storage_type +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_location__computed_storage_category_id +msgid "Computed Storage Category" +msgstr "Calcola categoria stoccaggio" + +#. module: stock_storage_type +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_storage_location_sequence_cond__condition_type +msgid "Condition Type" +msgstr "Tipo condizione" + +#. module: stock_storage_type +#. odoo-python +#: code:addons/stock_storage_type/models/stock_storage_location_sequence_cond.py:0 +#, python-format +msgid "Condition type is set to `Code`: you must provide a piece of code" +msgstr "Il tipo condizione è impostato a `Codice`: fornire parte del codice" + +#. module: stock_storage_type +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_storage_location_sequence__location_sequence_cond_ids +msgid "Conditions" +msgstr "Condizioni" + +#. module: stock_storage_type +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_storage_location_sequence__create_uid +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_storage_location_sequence_cond__create_uid +msgid "Created by" +msgstr "Creato da" + +#. module: stock_storage_type +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_storage_location_sequence__create_date +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_storage_location_sequence_cond__create_date +msgid "Created on" +msgstr "Creato il" + +#. module: stock_storage_type +#: model:ir.model.fields,help:stock_storage_type.field_product_product__package_type_id +#: model:ir.model.fields,help:stock_storage_type.field_product_template__package_type_id +msgid "" +"Defines a 'default' package type for this product to be applied on packages " +"without product packagings and on put-away computation based on package type " +"for product not in a package" +msgstr "" +"Definisce un tipo collo 'predefinito' per questo prodotto da utilizzare nei " +"colli senza imballaggio prdotto e nel calcolo del deposito in base al tipo " +"collo per prodotto non in un collo" + +#. module: stock_storage_type +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_storage_category__length_uom_id +msgid "Dimensions Units of Measure" +msgstr "Unità di misura per le dimensioni" + +#. module: stock_storage_type +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_storage_location_sequence__display_name +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_storage_location_sequence_cond__display_name +msgid "Display Name" +msgstr "Nome visualizzato" + +#. module: stock_storage_type +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_location__do_not_mix_lots +msgid "Do Not Mix Lots" +msgstr "Non mescolare lotti" + +#. module: stock_storage_type +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_location__do_not_mix_products +msgid "Do Not Mix Products" +msgstr "Non mescolare prodotti" + +#. module: stock_storage_type +#: model:ir.model.fields.selection,name:stock_storage_type.selection__stock_storage_location_sequence_cond__condition_type__code +msgid "Execute code" +msgstr "Esegui codice" + +#. module: stock_storage_type +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_storage_category_capacity__has_restrictions +msgid "Has Restrictions" +msgstr "Ha restrizioni" + +#. module: stock_storage_type +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_quant_package__height_in_m +msgid "Height In M" +msgstr "Altezza in metri" + +#. module: stock_storage_type +#: model:ir.model.fields,help:stock_storage_type.field_stock_package_type__height_required +msgid "Height is mandatory for packages configured with this storage type." +msgstr "" +"L'altezza è obbligatoria per colli configurati con questo tipo stoccaggio." + +#. module: stock_storage_type +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_package_type__height_required +msgid "Height required for packages" +msgstr "Altezza richiesta per i colli" + +#. module: stock_storage_type +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_storage_location_sequence__id +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_storage_location_sequence_cond__id +msgid "ID" +msgstr "ID" + +#. module: stock_storage_type +#: model:ir.model.fields.selection,name:stock_storage_type.selection__stock_storage_category_capacity__allow_new_product__same_lot +msgid "If all lots are the same" +msgstr "Se tutti i lotti sono lo stesso" + +#. module: stock_storage_type +#: model:ir.model.fields.selection,name:stock_storage_type.selection__stock_storage_category_capacity__allow_new_product__same +msgid "If all products are same" +msgstr "Se tutti i prodotti sono lo stesso" + +#. module: stock_storage_type +#: model:ir.model.fields.selection,name:stock_storage_type.selection__stock_storage_category__allow_new_product__same_lot +msgid "If lots are all the same" +msgstr "Se i lotti sono uguali" + +#. module: stock_storage_type +#: model:ir.model.fields.selection,name:stock_storage_type.selection__stock_storage_category_capacity__allow_new_product__empty +msgid "If the location is empty" +msgstr "Se l'ubicazione è vuota" + +#. module: stock_storage_type +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_location__in_move_ids +msgid "In Move" +msgstr "Nel movimento" + +#. module: stock_storage_type +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_location__in_move_line_ids +msgid "In Move Line" +msgstr "Nella riga movimento" + +#. module: stock_storage_type +#: model:ir.model,name:stock_storage_type.model_stock_location +msgid "Inventory Locations" +msgstr "Ubicazioni di inventario" + +#. module: stock_storage_type +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_storage_location_sequence____last_update +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_storage_location_sequence_cond____last_update +msgid "Last Modified on" +msgstr "Ultima modifica il" + +#. module: stock_storage_type +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_storage_location_sequence__write_uid +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_storage_location_sequence_cond__write_uid +msgid "Last Updated by" +msgstr "Ultimo aggiornamento di" + +#. module: stock_storage_type +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_storage_location_sequence__write_date +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_storage_location_sequence_cond__write_date +msgid "Last Updated on" +msgstr "Ultimo aggiornamento il" + +#. module: stock_storage_type +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_location__leaf_child_location_ids +msgid "Leaf Child Location" +msgstr "Ubicazione figlia terminale" + +#. module: stock_storage_type +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_location__leaf_location_ids +msgid "Leaf Location" +msgstr "Ubicazione terminale" + +#. module: stock_storage_type +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_storage_location_sequence__location_id +msgid "Location" +msgstr "Ubicazione" + +#. module: stock_storage_type +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_location__location_is_empty +msgid "Location Is Empty" +msgstr "Ubicazione vuota" + +#. module: stock_storage_type +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_location__location_will_contain_lot_ids +msgid "Location Will Contain Lot" +msgstr "L'ubicazione conterrà lotto" + +#. module: stock_storage_type +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_location__location_will_contain_product_ids +msgid "Location Will Contain Product" +msgstr "L'ubicazione conterrà prodotto" + +#. module: stock_storage_type +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_location__max_height_in_m +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_storage_category__max_height_in_m +msgid "Max Height In M" +msgstr "Altezza massima in metri" + +#. module: stock_storage_type +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_storage_category__max_weight_in_kg +msgid "Max Weight In Kg" +msgstr "Peso massimo in Kg" + +#. module: stock_storage_type +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_location__max_height +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_storage_category__max_height +msgid "Max height (mm)" +msgstr "Altezza massima (mm)" + +#. module: stock_storage_type +#: model:ir.model.constraint,message:stock_storage_type.constraint_stock_storage_category_positive_max_height +msgid "Max height should be a positive number." +msgstr "L'altezza massima deve essere un numero positivo." + +#. module: stock_storage_type +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_storage_location_sequence_cond__name +msgid "Name" +msgstr "Nome" + +#. module: stock_storage_type +#: model:ir.model.fields.selection,name:stock_storage_type.selection__stock_location__pack_putaway_strategy__none +msgid "None" +msgstr "Nessuna" + +#. module: stock_storage_type +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_location__only_empty +msgid "Only Empty" +msgstr "Solo vuote" + +#. module: stock_storage_type +#: model:ir.model.fields.selection,name:stock_storage_type.selection__stock_location__pack_putaway_strategy__ordered_locations +msgid "Ordered Children Locations" +msgstr "Ubicazioni figlie ordinate" + +#. module: stock_storage_type +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_location__out_move_line_ids +msgid "Out Move Line" +msgstr "Riga movimento uscita" + +#. module: stock_storage_type +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_quant_package__pack_weight_in_kg +msgid "Pack Weight In Kg" +msgstr "Peso pacco in Kg" + +#. module: stock_storage_type +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_storage_location_sequence__package_type_id +msgid "Package Type" +msgstr "Tipo collo" + +#. module: stock_storage_type +#: model:ir.model.fields,field_description:stock_storage_type.field_product_product__package_type_id +#: model:ir.model.fields,field_description:stock_storage_type.field_product_template__package_type_id +msgid "Package type" +msgstr "Tipo collo" + +#. module: stock_storage_type +#. odoo-python +#: code:addons/stock_storage_type/models/stock_quant.py:0 +#, python-format +msgid "Package type {storage} is not allowed into Location {location}" +msgstr "Il tipo collo {storage} non è consentito nell'ubicazione {location}" + +#. module: stock_storage_type +#. odoo-python +#: code:addons/stock_storage_type/models/stock_quant.py:0 +#, python-format +msgid "" +"Package {package} is not allowed into location {location}, because there " +"isn't any storage capacity that allows package type {type} into it:\n" +"\n" +"{fails}" +msgstr "" +"Il collo {package} non è accettatto dall'ubicazione {location}, perché non " +"c'è capactà di stoccaggio che accetti il tipo collo {type}:\n" +"\n" +"{fails}" + +#. module: stock_storage_type +#: model:ir.model,name:stock_storage_type.model_stock_quant_package +msgid "Packages" +msgstr "Colli" + +#. module: stock_storage_type +#: model:ir.model,name:stock_storage_type.model_product_template +msgid "Product" +msgstr "Prodotto" + +#. module: stock_storage_type +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_package_type__product_packaging_ids +msgid "Product Packaging" +msgstr "Imballaggio prodotto" + +#. module: stock_storage_type +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_location__pack_putaway_strategy +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_storage_location_sequence__location_putaway_strategy +msgid "Put-Away Strategy" +msgstr "Strategia di deposito" + +#. module: stock_storage_type +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_package_type__storage_location_sequence_ids +#: model_terms:ir.ui.view,arch_db:stock_storage_type.stock_package_type_form +msgid "Put-Away sequence" +msgstr "Sequenza deposito" + +#. module: stock_storage_type +#. odoo-python +#: code:addons/stock_storage_type/models/stock_package_type.py:0 +#, python-format +msgid "Put-away sequence" +msgstr "Sequenza deposito" + +#. module: stock_storage_type +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_location__package_type_putaway_sequence +msgid "Putaway Sequence" +msgstr "Sequenza deposito" + +#. module: stock_storage_type +#: model:ir.model,name:stock_storage_type.model_stock_quant +msgid "Quants" +msgstr "Quanti" + +#. module: stock_storage_type +#: model_terms:ir.ui.view,arch_db:stock_storage_type.package_level_tree_view_picking_inherit +msgid "Recompute Putaway" +msgstr "Ricalcola deposito" + +#. module: stock_storage_type +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_storage_location_sequence__sequence +msgid "Sequence" +msgstr "Sequenza" + +#. module: stock_storage_type +#: model:ir.model,name:stock_storage_type.model_stock_storage_location_sequence +msgid "Sequence of locations to put-away the package storage type" +msgstr "Sequenza ubicazioni per deposito del tipo stoccaggio collo" + +#. module: stock_storage_type +#: model_terms:ir.ui.view,arch_db:stock_storage_type.package_storage_location_tree_view +msgid "Show locations" +msgstr "Visualizza ubicazioni" + +#. module: stock_storage_type +#: model:ir.model,name:stock_storage_type.model_stock_package_level +msgid "Stock Package Level" +msgstr "Livello collo magazzino" + +#. module: stock_storage_type +#: model:ir.model,name:stock_storage_type.model_stock_storage_location_sequence_cond +msgid "Stock Storage Location Sequence Condition" +msgstr "Sequenza condizione ubicazione stoccaggio magazzino" + +#. module: stock_storage_type +#: model:ir.actions.act_window,name:stock_storage_type.stock_storage_location_sequence_cond_act_window +msgid "Stock Storage Location Sequence Conditions" +msgstr "Sequenza condizioni ubicazione stoccaggio magazzino" + +#. module: stock_storage_type +#: model:ir.model,name:stock_storage_type.model_stock_package_type +msgid "Stock package type" +msgstr "Tipo collo magazzino" + +#. module: stock_storage_type +#: model:ir.model.constraint,message:stock_storage_type.constraint_stock_storage_location_sequence_cond_name +msgid "Stock storage location sequence condition name must be unique" +msgstr "" +"Il nome della seqenza condizione ubicazione stoccaggio magazzino deve essere " +"univoco" + +#. module: stock_storage_type +#. odoo-python +#: code:addons/stock_storage_type/models/stock_quant.py:0 +#, python-format +msgid "" +"Storage Capacity {storage_capacity} is flagged 'do not mix lots' but there " +"are other lots in location." +msgstr "" +"La capacità stoccaggio {storage_capacity} è settata 'non mescolare lotti' ma " +"ci sono lotti diversi nell'ubicazione." + +#. module: stock_storage_type +#. odoo-python +#: code:addons/stock_storage_type/models/stock_quant.py:0 +#, python-format +msgid "" +"Storage Capacity {storage_capacity} is flagged 'do not mix products' but " +"there are other products in location." +msgstr "" +"La capacità stoccaggio {storage_capacity} è settata 'non mescolare prdotti' " +"ma ci sono prodotti diversi nell'ubicazione." + +#. module: stock_storage_type +#. odoo-python +#: code:addons/stock_storage_type/models/stock_quant.py:0 +#, python-format +msgid "" +"Storage Capacity {storage_capacity} is flagged 'only empty' with other " +"quants in location." +msgstr "" +"La capacità stoccaggio {storage_capacity} è settata 'solo vuoti' ma ci sono " +"altre quantità nell'ubicazione." + +#. module: stock_storage_type +#: model:ir.model,name:stock_storage_type.model_stock_storage_category +msgid "Storage Category" +msgstr "Categoria stoccaggio" + +#. module: stock_storage_type +#: model:ir.model,name:stock_storage_type.model_stock_storage_category_capacity +msgid "Storage Category Capacity" +msgstr "Capacità categoria stoccaggio" + +#. module: stock_storage_type +#. odoo-python +#: code:addons/stock_storage_type/models/stock_quant.py:0 +#, python-format +msgid "" +"Storage Category {storage_category} defines max height of {max_h} but the " +"package is bigger: {height}." +msgstr "" +"La categoria stoccaggio {storage_category} ha una altezza massima di {max_h} " +"ma il collo è più grande: {height}." + +#. module: stock_storage_type +#. odoo-python +#: code:addons/stock_storage_type/models/stock_quant.py:0 +#, python-format +msgid "" +"Storage Category {storage_category} defines max weight of {max_w} but the " +"package is heavier: {weight_kg}." +msgstr "" +"La categoria stoccaggio {storage_category} ha un peso massimo di {max_w} ma " +"il collo è più pesante: {weight_kg}." + +#. module: stock_storage_type +#: model:ir.ui.menu,name:stock_storage_type.stock_storage_location_sequence_cond_menu +msgid "Storage Location Sequence Conditions" +msgstr "Sequenza condizioni ubicazione stoccaggio" + +#. module: stock_storage_type +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_package_type__storage_type_message +msgid "Storage Type Message" +msgstr "Messaggio tipo stoccaggio" + +#. module: stock_storage_type +#: model:ir.ui.menu,name:stock_storage_type.storage_type_menu +msgid "Storage Types" +msgstr "Tipi stoccaggio" + +#. module: stock_storage_type +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_location__storage_location_sequence_ids +msgid "Storage locations sequences" +msgstr "Seqenze ubicazioni stoccaggio" + +#. module: stock_storage_type +#: model:ir.model.fields,help:stock_storage_type.field_stock_location__max_height_in_m +#: model:ir.model.fields,help:stock_storage_type.field_stock_quant_package__height_in_m +#: model:ir.model.fields,help:stock_storage_type.field_stock_quant_package__pack_weight_in_kg +#: model:ir.model.fields,help:stock_storage_type.field_stock_storage_category__max_height_in_m +#: model:ir.model.fields,help:stock_storage_type.field_stock_storage_category__max_weight_in_kg +msgid "Technical field, to speed up comparaisons" +msgstr "Campo tecnico, per velocizzare i confronti" + +#. module: stock_storage_type +#: model:ir.model.fields,help:stock_storage_type.field_stock_storage_category_capacity__has_restrictions +msgid "Technical: This is used to check if we need to display warning message" +msgstr "" +"Tecnico: viene utilizzato per controllare se serve visualizzare il messaggio " +"di attenzione" + +#. module: stock_storage_type +#. odoo-python +#: code:addons/stock_storage_type/models/stock_quant_package.py:0 +#, python-format +msgid "The height is mandatory on package {}." +msgstr "L'altezza è obbligatria per il collo {}." + +#. module: stock_storage_type +#: model:ir.model.fields,help:stock_storage_type.field_stock_location__max_height +#: model:ir.model.fields,help:stock_storage_type.field_stock_storage_category__max_height +msgid "The max height supported for this storage category." +msgstr "L'altezza massima accettata per questa categoria stoccaggio." + +#. module: stock_storage_type +#: model:ir.model.fields,help:stock_storage_type.field_stock_location__pack_putaway_strategy +#: model:ir.model.fields,help:stock_storage_type.field_stock_storage_location_sequence__location_putaway_strategy +msgid "" +"This defines the storage strategy based on package type to use when a " +"product or package is put away in this location.\n" +"None: when moved to this location, it will not be put away any further.\n" +"Ordered Children Locations: when moved to this location, a suitable location " +"will be searched in its children locations according to the restrictions " +"defined on their respective location storage types." +msgstr "" +"Questo definisce la strategia di stoccaggio in base al tipo di collo da " +"utilizzare quanto un prodotto o un collo è depositato in questa ubicazione.\n" +"Nessuna: quanto movimentato in questa ubicazione, non verrà movimentato " +"ulteriormente.\n" +"Ordinamento per ubicazioni figlie: quanto movimentato in questa ubicazione, " +"verrà cercata una ubicazione idonea nelle ubicazioni figlie in accordo con " +"le restrizioni definite nei rispettivi tipi stoccaggio ubicazione." + +#. module: stock_storage_type +#: model:ir.model.fields,help:stock_storage_type.field_stock_location__computed_storage_category_id +msgid "" +"This represents the Storage Category that will be used. It depends either on " +"the category set on the location or on one of its parent." +msgstr "" +"Questo rappresenta la categoria stoccaggio che verrà utilizzata. Dipende " +"dalla categoria mpostata nell'ubicazione o in uno dei suoi padri." + +#. module: stock_storage_type +#: model:ir.model.fields,help:stock_storage_type.field_stock_storage_category__length_uom_id +msgid "UoM for height" +msgstr "UdM per l'altezza" + +#. module: stock_storage_type +#: model:ir.model.fields,help:stock_storage_type.field_stock_storage_category__weight_uom_id +msgid "Weight Unit of Measure" +msgstr "Unità di misura del peso" + +#. module: stock_storage_type +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_storage_category__weight_uom_id +msgid "Weight Units of Measure" +msgstr "Unità di misura del peso" + +#. module: stock_storage_type +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_storage_category__weight_uom_name +msgid "Weight unit of measure label" +msgstr "Etichetta unità di misura del peso" + +#. module: stock_storage_type +#. odoo-python +#: code:addons/stock_storage_type/models/stock_package_type.py:0 +#, python-format +msgid "" +"When a package with storage type {name} is put away, the strategy will look " +"for an allowed location in the following locations:

{message}

Note: this happens as long as these locations are children " +"of the stock move destination location or as long as these locations are " +"children of the destination location after the (product or category) put-" +"away is applied." +msgstr "" +"Quando i colli con tipo stoccaggio {name} sono depositati, la strategia " +"cerca una ubicazione disponibile nelle segenti ubicazoni:

{message}" +"

Nota: questo accade finché queste ubicazioni sono " +"figlie della ubicazione destinazione del movimento di magazzino o finché " +"queste ubicazioni sono figlie dell'ubicazione di destinazione dopo che è " +"stato eseguito il deposito (prodotto o categoria)." + +#. module: stock_storage_type +#. odoo-python +#: code:addons/stock_storage_type/models/stock_storage_location_sequence_cond.py:0 +#, python-format +msgid "code_snippet should return boolean value into `result` variable." +msgstr "code_snippet deve restituire un buleano nella variabile `result`." + +#. module: stock_storage_type +#: model:ir.model.fields,help:stock_storage_type.field_stock_location__location_is_empty +msgid "" +"technical field: True if the location is empty and there is no pending " +"incoming products in the location. Computed only if the location needs to " +"check for emptiness (has an \"only empty\" location storage type)." +msgstr "" +"campo tecnico: vero se l'ubicazione è vuta e non ci sono in sospeso ingressi " +"di prodotti nell'ubicazIone. Calcolato solo se l'ubicazione deve essere " +"controllata per rienpimento (ha un tipo stoccaggio ubicazione \"solo vuota" +"\")." + +#. module: stock_storage_type +#: model:ir.model.fields,help:stock_storage_type.field_stock_location__leaf_location_ids +msgid "technical field: all the leaves locations" +msgstr "campo tecnico: tutte le ubicazioni terminali" + +#. module: stock_storage_type +#: model:ir.model.fields,help:stock_storage_type.field_stock_location__leaf_child_location_ids +msgid "technical field: all the leaves sub-locations" +msgstr "campo tecnico: tutte le sotto ubicazioni terminali" + +#. module: stock_storage_type +#: model:ir.model.fields,help:stock_storage_type.field_stock_location__location_will_contain_product_ids +msgid "" +"technical field: list of products in the location, either now or in pending " +"operations" +msgstr "" +"campo tecnico: elencodei prdotti nell'ubicazione, sia operazioni attuali che " +"in attesa" + +#. module: stock_storage_type +#: model:ir.model.fields,help:stock_storage_type.field_stock_location__location_will_contain_lot_ids +msgid "" +"technical field: list of stock.lots in the location, either now or in " +"pending operations" +msgstr "" +"campo tecnico: elenco lotti magazzino nll'ubicazione, sia operazioni attuali " +"che in attesa" + +#. module: stock_storage_type +#: model:ir.model.fields,help:stock_storage_type.field_stock_location__in_move_line_ids +msgid "technical field: the pending incoming stock.move.lines in the location" +msgstr "" +"campo tecnico: le stock.move.lines in attesa in ingresso nell'ubicazione" + +#. module: stock_storage_type +#: model:ir.model.fields,help:stock_storage_type.field_stock_location__in_move_ids +msgid "technical field: the pending incoming stock.moves in the location" +msgstr "campo tecnico: i stock.moves in attesa nell'ubicazione" + +#. module: stock_storage_type +#: model:ir.model.fields,help:stock_storage_type.field_stock_location__out_move_line_ids +msgid "technical field: the pending outgoing stock.move.lines in the location" +msgstr "campo tecnico: le stock.move.lines in uscita in atesa nell'ubicazione" diff --git a/stock_storage_type/i18n/stock_storage_type.pot b/stock_storage_type/i18n/stock_storage_type.pot new file mode 100644 index 0000000000..0ef912e690 --- /dev/null +++ b/stock_storage_type/i18n/stock_storage_type.pot @@ -0,0 +1,661 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * stock_storage_type +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 17.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_storage_type +#. odoo-python +#: code:addons/stock_storage_type/models/stock_storage_location_sequence.py:0 +#, python-format +msgid "" +" * {location} (WARNING: restrictions are " +"active on location storage types matching this package storage type)" +msgstr "" + +#. module: stock_storage_type +#. odoo-python +#: code:addons/stock_storage_type/models/stock_storage_location_sequence.py:0 +#, python-format +msgid "" +" * {location} (WARNING: no suitable location " +"matching storage type)" +msgstr "" + +#. module: stock_storage_type +#. odoo-python +#: code:addons/stock_storage_type/models/stock_package_type.py:0 +#, python-format +msgid "" +"The \"Put-Away sequence\" must be defined in " +"order to put away packages using this package storage type " +"({storage})." +msgstr "" + +#. module: stock_storage_type +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_package_type__active +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_storage_location_sequence_cond__active +msgid "Active" +msgstr "" + +#. module: stock_storage_type +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_storage_category__allow_new_product +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_storage_category_capacity__allow_new_product +msgid "Allow New Product" +msgstr "" + +#. module: stock_storage_type +#. odoo-python +#: code:addons/stock_storage_type/models/stock_storage_category_capacity.py:0 +#, python-format +msgid "Allow New Product: " +msgstr "" + +#. module: stock_storage_type +#: model:ir.model.fields.selection,name:stock_storage_type.selection__stock_storage_category_capacity__allow_new_product__mixed +msgid "Allow mixed products" +msgstr "" + +#. module: stock_storage_type +#: model:ir.model.fields,help:stock_storage_type.field_stock_location__package_type_putaway_sequence +msgid "" +"Allow to sort the valid locations by sequence for the storage strategy based" +" on package type" +msgstr "" + +#. module: stock_storage_type +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_package_level__allowed_location_dest_ids +msgid "Allowed Destinations" +msgstr "" + +#. module: stock_storage_type +#: model_terms:ir.ui.view,arch_db:stock_storage_type.stock_storage_location_sequence_cond_form_view +msgid "Archived" +msgstr "" + +#. module: stock_storage_type +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_package_type__barcode +msgid "Barcode" +msgstr "" + +#. module: stock_storage_type +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_location__computed_storage_capacity_ids +msgid "Capacity" +msgstr "" + +#. module: stock_storage_type +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_storage_location_sequence_cond__code_snippet +msgid "Code Snippet" +msgstr "" + +#. module: stock_storage_type +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_storage_location_sequence_cond__code_snippet_docs +msgid "Code Snippet Docs" +msgstr "" + +#. module: stock_storage_type +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_storage_category__computed_location_ids +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_storage_category_capacity__computed_location_ids +msgid "Computed Location" +msgstr "" + +#. module: stock_storage_type +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_location__computed_storage_category_id +msgid "Computed Storage Category" +msgstr "" + +#. module: stock_storage_type +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_storage_location_sequence_cond__condition_type +msgid "Condition Type" +msgstr "" + +#. module: stock_storage_type +#. odoo-python +#: code:addons/stock_storage_type/models/stock_storage_location_sequence_cond.py:0 +#, python-format +msgid "Condition type is set to `Code`: you must provide a piece of code" +msgstr "" + +#. module: stock_storage_type +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_storage_location_sequence__location_sequence_cond_ids +msgid "Conditions" +msgstr "" + +#. module: stock_storage_type +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_storage_location_sequence__create_uid +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_storage_location_sequence_cond__create_uid +msgid "Created by" +msgstr "" + +#. module: stock_storage_type +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_storage_location_sequence__create_date +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_storage_location_sequence_cond__create_date +msgid "Created on" +msgstr "" + +#. module: stock_storage_type +#: model:ir.model.fields,help:stock_storage_type.field_product_product__package_type_id +#: model:ir.model.fields,help:stock_storage_type.field_product_template__package_type_id +msgid "" +"Defines a 'default' package type for this product to be applied on packages " +"without product packagings and on put-away computation based on package type" +" for product not in a package" +msgstr "" + +#. module: stock_storage_type +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_storage_category__length_uom_id +msgid "Dimensions Units of Measure" +msgstr "" + +#. module: stock_storage_type +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_storage_location_sequence__display_name +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_storage_location_sequence_cond__display_name +msgid "Display Name" +msgstr "" + +#. module: stock_storage_type +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_location__do_not_mix_lots +msgid "Do Not Mix Lots" +msgstr "" + +#. module: stock_storage_type +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_location__do_not_mix_products +msgid "Do Not Mix Products" +msgstr "" + +#. module: stock_storage_type +#: model:ir.model.fields.selection,name:stock_storage_type.selection__stock_storage_location_sequence_cond__condition_type__code +msgid "Execute code" +msgstr "" + +#. module: stock_storage_type +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_storage_category_capacity__has_restrictions +msgid "Has Restrictions" +msgstr "" + +#. module: stock_storage_type +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_quant_package__height_in_m +msgid "Height In M" +msgstr "" + +#. module: stock_storage_type +#: model:ir.model.fields,help:stock_storage_type.field_stock_package_type__height_required +msgid "Height is mandatory for packages configured with this storage type." +msgstr "" + +#. module: stock_storage_type +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_package_type__height_required +msgid "Height required for packages" +msgstr "" + +#. module: stock_storage_type +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_storage_location_sequence__id +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_storage_location_sequence_cond__id +msgid "ID" +msgstr "" + +#. module: stock_storage_type +#: model:ir.model.fields.selection,name:stock_storage_type.selection__stock_storage_category_capacity__allow_new_product__same_lot +msgid "If all lots are the same" +msgstr "" + +#. module: stock_storage_type +#: model:ir.model.fields.selection,name:stock_storage_type.selection__stock_storage_category_capacity__allow_new_product__same +msgid "If all products are same" +msgstr "" + +#. module: stock_storage_type +#: model:ir.model.fields.selection,name:stock_storage_type.selection__stock_storage_category__allow_new_product__same_lot +msgid "If lots are all the same" +msgstr "" + +#. module: stock_storage_type +#: model:ir.model.fields.selection,name:stock_storage_type.selection__stock_storage_category_capacity__allow_new_product__empty +msgid "If the location is empty" +msgstr "" + +#. module: stock_storage_type +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_location__in_move_ids +msgid "In Move" +msgstr "" + +#. module: stock_storage_type +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_location__in_move_line_ids +msgid "In Move Line" +msgstr "" + +#. module: stock_storage_type +#: model:ir.model,name:stock_storage_type.model_stock_location +msgid "Inventory Locations" +msgstr "" + +#. module: stock_storage_type +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_storage_location_sequence____last_update +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_storage_location_sequence_cond____last_update +msgid "Last Modified on" +msgstr "" + +#. module: stock_storage_type +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_storage_location_sequence__write_uid +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_storage_location_sequence_cond__write_uid +msgid "Last Updated by" +msgstr "" + +#. module: stock_storage_type +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_storage_location_sequence__write_date +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_storage_location_sequence_cond__write_date +msgid "Last Updated on" +msgstr "" + +#. module: stock_storage_type +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_location__leaf_child_location_ids +msgid "Leaf Child Location" +msgstr "" + +#. module: stock_storage_type +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_location__leaf_location_ids +msgid "Leaf Location" +msgstr "" + +#. module: stock_storage_type +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_storage_location_sequence__location_id +msgid "Location" +msgstr "" + +#. module: stock_storage_type +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_location__location_is_empty +msgid "Location Is Empty" +msgstr "" + +#. module: stock_storage_type +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_location__location_will_contain_lot_ids +msgid "Location Will Contain Lot" +msgstr "" + +#. module: stock_storage_type +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_location__location_will_contain_product_ids +msgid "Location Will Contain Product" +msgstr "" + +#. module: stock_storage_type +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_location__max_height_in_m +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_storage_category__max_height_in_m +msgid "Max Height In M" +msgstr "" + +#. module: stock_storage_type +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_storage_category__max_weight_in_kg +msgid "Max Weight In Kg" +msgstr "" + +#. module: stock_storage_type +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_location__max_height +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_storage_category__max_height +msgid "Max height (mm)" +msgstr "" + +#. module: stock_storage_type +#: model:ir.model.constraint,message:stock_storage_type.constraint_stock_storage_category_positive_max_height +msgid "Max height should be a positive number." +msgstr "" + +#. module: stock_storage_type +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_storage_location_sequence_cond__name +msgid "Name" +msgstr "" + +#. module: stock_storage_type +#: model:ir.model.fields.selection,name:stock_storage_type.selection__stock_location__pack_putaway_strategy__none +msgid "None" +msgstr "" + +#. module: stock_storage_type +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_location__only_empty +msgid "Only Empty" +msgstr "" + +#. module: stock_storage_type +#: model:ir.model.fields.selection,name:stock_storage_type.selection__stock_location__pack_putaway_strategy__ordered_locations +msgid "Ordered Children Locations" +msgstr "" + +#. module: stock_storage_type +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_location__out_move_line_ids +msgid "Out Move Line" +msgstr "" + +#. module: stock_storage_type +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_quant_package__pack_weight_in_kg +msgid "Pack Weight In Kg" +msgstr "" + +#. module: stock_storage_type +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_storage_location_sequence__package_type_id +msgid "Package Type" +msgstr "" + +#. module: stock_storage_type +#: model:ir.model.fields,field_description:stock_storage_type.field_product_product__package_type_id +#: model:ir.model.fields,field_description:stock_storage_type.field_product_template__package_type_id +msgid "Package type" +msgstr "" + +#. module: stock_storage_type +#. odoo-python +#: code:addons/stock_storage_type/models/stock_quant.py:0 +#, python-format +msgid "Package type {storage} is not allowed into Location {location}" +msgstr "" + +#. module: stock_storage_type +#. odoo-python +#: code:addons/stock_storage_type/models/stock_quant.py:0 +#, python-format +msgid "" +"Package {package} is not allowed into location {location}, because there isn't any storage capacity that allows package type {type} into it:\n" +"\n" +"{fails}" +msgstr "" + +#. module: stock_storage_type +#: model:ir.model,name:stock_storage_type.model_stock_quant_package +msgid "Packages" +msgstr "" + +#. module: stock_storage_type +#: model:ir.model,name:stock_storage_type.model_product_template +msgid "Product" +msgstr "" + +#. module: stock_storage_type +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_package_type__product_packaging_ids +msgid "Product Packaging" +msgstr "" + +#. module: stock_storage_type +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_location__pack_putaway_strategy +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_storage_location_sequence__location_putaway_strategy +msgid "Put-Away Strategy" +msgstr "" + +#. module: stock_storage_type +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_package_type__storage_location_sequence_ids +#: model_terms:ir.ui.view,arch_db:stock_storage_type.stock_package_type_form +msgid "Put-Away sequence" +msgstr "" + +#. module: stock_storage_type +#. odoo-python +#: code:addons/stock_storage_type/models/stock_package_type.py:0 +#, python-format +msgid "Put-away sequence" +msgstr "" + +#. module: stock_storage_type +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_location__package_type_putaway_sequence +msgid "Putaway Sequence" +msgstr "" + +#. module: stock_storage_type +#: model:ir.model,name:stock_storage_type.model_stock_quant +msgid "Quants" +msgstr "" + +#. module: stock_storage_type +#: model_terms:ir.ui.view,arch_db:stock_storage_type.package_level_tree_view_picking_inherit +msgid "Recompute Putaway" +msgstr "" + +#. module: stock_storage_type +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_storage_location_sequence__sequence +msgid "Sequence" +msgstr "" + +#. module: stock_storage_type +#: model:ir.model,name:stock_storage_type.model_stock_storage_location_sequence +msgid "Sequence of locations to put-away the package storage type" +msgstr "" + +#. module: stock_storage_type +#: model_terms:ir.ui.view,arch_db:stock_storage_type.package_storage_location_tree_view +msgid "Show locations" +msgstr "" + +#. module: stock_storage_type +#: model:ir.model,name:stock_storage_type.model_stock_package_level +msgid "Stock Package Level" +msgstr "" + +#. module: stock_storage_type +#: model:ir.model,name:stock_storage_type.model_stock_storage_location_sequence_cond +msgid "Stock Storage Location Sequence Condition" +msgstr "" + +#. module: stock_storage_type +#: model:ir.actions.act_window,name:stock_storage_type.stock_storage_location_sequence_cond_act_window +msgid "Stock Storage Location Sequence Conditions" +msgstr "" + +#. module: stock_storage_type +#: model:ir.model,name:stock_storage_type.model_stock_package_type +msgid "Stock package type" +msgstr "" + +#. module: stock_storage_type +#: model:ir.model.constraint,message:stock_storage_type.constraint_stock_storage_location_sequence_cond_name +msgid "Stock storage location sequence condition name must be unique" +msgstr "" + +#. module: stock_storage_type +#. odoo-python +#: code:addons/stock_storage_type/models/stock_quant.py:0 +#, python-format +msgid "" +"Storage Capacity {storage_capacity} is flagged 'do not mix lots' but there " +"are other lots in location." +msgstr "" + +#. module: stock_storage_type +#. odoo-python +#: code:addons/stock_storage_type/models/stock_quant.py:0 +#, python-format +msgid "" +"Storage Capacity {storage_capacity} is flagged 'do not mix products' but " +"there are other products in location." +msgstr "" + +#. module: stock_storage_type +#. odoo-python +#: code:addons/stock_storage_type/models/stock_quant.py:0 +#, python-format +msgid "" +"Storage Capacity {storage_capacity} is flagged 'only empty' with other " +"quants in location." +msgstr "" + +#. module: stock_storage_type +#: model:ir.model,name:stock_storage_type.model_stock_storage_category +msgid "Storage Category" +msgstr "" + +#. module: stock_storage_type +#: model:ir.model,name:stock_storage_type.model_stock_storage_category_capacity +msgid "Storage Category Capacity" +msgstr "" + +#. module: stock_storage_type +#. odoo-python +#: code:addons/stock_storage_type/models/stock_quant.py:0 +#, python-format +msgid "" +"Storage Category {storage_category} defines max height of {max_h} but the " +"package is bigger: {height}." +msgstr "" + +#. module: stock_storage_type +#. odoo-python +#: code:addons/stock_storage_type/models/stock_quant.py:0 +#, python-format +msgid "" +"Storage Category {storage_category} defines max weight of {max_w} but the " +"package is heavier: {weight_kg}." +msgstr "" + +#. module: stock_storage_type +#: model:ir.ui.menu,name:stock_storage_type.stock_storage_location_sequence_cond_menu +msgid "Storage Location Sequence Conditions" +msgstr "" + +#. module: stock_storage_type +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_package_type__storage_type_message +msgid "Storage Type Message" +msgstr "" + +#. module: stock_storage_type +#: model:ir.ui.menu,name:stock_storage_type.storage_type_menu +msgid "Storage Types" +msgstr "" + +#. module: stock_storage_type +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_location__storage_location_sequence_ids +msgid "Storage locations sequences" +msgstr "" + +#. module: stock_storage_type +#: model:ir.model.fields,help:stock_storage_type.field_stock_location__max_height_in_m +#: model:ir.model.fields,help:stock_storage_type.field_stock_quant_package__height_in_m +#: model:ir.model.fields,help:stock_storage_type.field_stock_quant_package__pack_weight_in_kg +#: model:ir.model.fields,help:stock_storage_type.field_stock_storage_category__max_height_in_m +#: model:ir.model.fields,help:stock_storage_type.field_stock_storage_category__max_weight_in_kg +msgid "Technical field, to speed up comparaisons" +msgstr "" + +#. module: stock_storage_type +#: model:ir.model.fields,help:stock_storage_type.field_stock_storage_category_capacity__has_restrictions +msgid "Technical: This is used to check if we need to display warning message" +msgstr "" + +#. module: stock_storage_type +#. odoo-python +#: code:addons/stock_storage_type/models/stock_quant_package.py:0 +#, python-format +msgid "The height is mandatory on package {}." +msgstr "" + +#. module: stock_storage_type +#: model:ir.model.fields,help:stock_storage_type.field_stock_location__max_height +#: model:ir.model.fields,help:stock_storage_type.field_stock_storage_category__max_height +msgid "The max height supported for this storage category." +msgstr "" + +#. module: stock_storage_type +#: model:ir.model.fields,help:stock_storage_type.field_stock_location__pack_putaway_strategy +#: model:ir.model.fields,help:stock_storage_type.field_stock_storage_location_sequence__location_putaway_strategy +msgid "" +"This defines the storage strategy based on package type to use when a product or package is put away in this location.\n" +"None: when moved to this location, it will not be put away any further.\n" +"Ordered Children Locations: when moved to this location, a suitable location will be searched in its children locations according to the restrictions defined on their respective location storage types." +msgstr "" + +#. module: stock_storage_type +#: model:ir.model.fields,help:stock_storage_type.field_stock_location__computed_storage_category_id +msgid "" +"This represents the Storage Category that will be used. It depends either on" +" the category set on the location or on one of its parent." +msgstr "" + +#. module: stock_storage_type +#: model:ir.model.fields,help:stock_storage_type.field_stock_storage_category__length_uom_id +msgid "UoM for height" +msgstr "" + +#. module: stock_storage_type +#: model:ir.model.fields,help:stock_storage_type.field_stock_storage_category__weight_uom_id +msgid "Weight Unit of Measure" +msgstr "" + +#. module: stock_storage_type +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_storage_category__weight_uom_id +msgid "Weight Units of Measure" +msgstr "" + +#. module: stock_storage_type +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_storage_category__weight_uom_name +msgid "Weight unit of measure label" +msgstr "" + +#. module: stock_storage_type +#. odoo-python +#: code:addons/stock_storage_type/models/stock_package_type.py:0 +#, python-format +msgid "" +"When a package with storage type {name} is put away, the strategy will look " +"for an allowed location in the following locations:

{message} " +"

Note: this happens as long as these locations are " +"children of the stock move destination location or as long as these " +"locations are children of the destination location after the (product or " +"category) put-away is applied." +msgstr "" + +#. module: stock_storage_type +#. odoo-python +#: code:addons/stock_storage_type/models/stock_storage_location_sequence_cond.py:0 +#, python-format +msgid "code_snippet should return boolean value into `result` variable." +msgstr "" + +#. module: stock_storage_type +#: model:ir.model.fields,help:stock_storage_type.field_stock_location__location_is_empty +msgid "" +"technical field: True if the location is empty and there is no pending " +"incoming products in the location. Computed only if the location needs to " +"check for emptiness (has an \"only empty\" location storage type)." +msgstr "" + +#. module: stock_storage_type +#: model:ir.model.fields,help:stock_storage_type.field_stock_location__leaf_location_ids +msgid "technical field: all the leaves locations" +msgstr "" + +#. module: stock_storage_type +#: model:ir.model.fields,help:stock_storage_type.field_stock_location__leaf_child_location_ids +msgid "technical field: all the leaves sub-locations" +msgstr "" + +#. module: stock_storage_type +#: model:ir.model.fields,help:stock_storage_type.field_stock_location__location_will_contain_product_ids +msgid "" +"technical field: list of products in the location, either now or in pending " +"operations" +msgstr "" + +#. module: stock_storage_type +#: model:ir.model.fields,help:stock_storage_type.field_stock_location__location_will_contain_lot_ids +msgid "" +"technical field: list of stock.lots in the location, either now or in " +"pending operations" +msgstr "" + +#. module: stock_storage_type +#: model:ir.model.fields,help:stock_storage_type.field_stock_location__in_move_line_ids +msgid "technical field: the pending incoming stock.move.lines in the location" +msgstr "" + +#. module: stock_storage_type +#: model:ir.model.fields,help:stock_storage_type.field_stock_location__in_move_ids +msgid "technical field: the pending incoming stock.moves in the location" +msgstr "" + +#. module: stock_storage_type +#: model:ir.model.fields,help:stock_storage_type.field_stock_location__out_move_line_ids +msgid "technical field: the pending outgoing stock.move.lines in the location" +msgstr "" diff --git a/stock_storage_type/models/__init__.py b/stock_storage_type/models/__init__.py new file mode 100644 index 0000000000..ef4b4162a1 --- /dev/null +++ b/stock_storage_type/models/__init__.py @@ -0,0 +1,12 @@ +from . import ( + product_template, + stock_location, + stock_package_level, + stock_package_type, + stock_quant, + stock_quant_package, + stock_storage_category, + stock_storage_category_capacity, + stock_storage_location_sequence, + stock_storage_location_sequence_cond, +) diff --git a/stock_storage_type/models/product_template.py b/stock_storage_type/models/product_template.py new file mode 100644 index 0000000000..f77548375e --- /dev/null +++ b/stock_storage_type/models/product_template.py @@ -0,0 +1,15 @@ +# Copyright 2020 Camptocamp SA +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl) +from odoo import fields, models + + +class ProductTemplate(models.Model): + _inherit = "product.template" + + package_type_id = fields.Many2one( + "stock.package.type", + string="Package type", + help="Defines a 'default' package type for this product to be " + "applied on packages without product packagings and on put-away " + "computation based on package type for product not in a package", + ) diff --git a/stock_storage_type/models/stock_location.py b/stock_storage_type/models/stock_location.py new file mode 100644 index 0000000000..9ae9d4c463 --- /dev/null +++ b/stock_storage_type/models/stock_location.py @@ -0,0 +1,770 @@ +# Copyright 2019-2021 Camptocamp SA +# Copyright 2019-2021 Jacques-Etienne Baudoux (BCIM) +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl) +import logging + +from psycopg2 import sql + +from odoo import api, fields, models +from odoo.fields import Command +from odoo.tools import float_compare, index_exists + +_logger = logging.getLogger(__name__) +OUT_MOVE_LINE_DOMAIN = [ + ("state", "in", ("waiting", "confirmed", "partially_available", "assigned")) +] + + +class StockLocation(models.Model): + _inherit = "stock.location" + + computed_storage_category_id = fields.Many2one( + comodel_name="stock.storage.category", + string="Computed Storage Category", + compute="_compute_computed_storage_category_id", + store=True, + recursive=True, + help="This represents the Storage Category that will be used. It depends " + "either on the category set on the location or on one of its parent.", + ) + computed_storage_capacity_ids = fields.One2many( + related="computed_storage_category_id.capacity_ids", + ) + pack_putaway_strategy = fields.Selection( + selection=[ + ("none", "None"), + ("ordered_locations", "Ordered Children Locations"), + ], + required=True, + default="none", + string="Put-Away Strategy", + help="This defines the storage strategy based on package type to use when " + "a product or package is put away in this location.\n" + "None: when moved to this location, it will not be put" + " away any further.\n" + "Ordered Children Locations: when moved to this " + "location, a suitable location will be searched in its children " + "locations according to the restrictions defined on their " + "respective location storage types.", + ) + package_type_putaway_sequence = fields.Integer( + string="Putaway Sequence", + help="Allow to sort the valid locations by sequence for the storage " + "strategy based on package type", + ) + storage_location_sequence_ids = fields.One2many( + "stock.storage.location.sequence", + "location_id", + string="Storage locations sequences", + ) + location_is_empty = fields.Boolean( + compute="_compute_location_is_empty", + store=True, + help="technical field: True if the location is empty " + "and there is no pending incoming products in the location. " + " Computed only if the location needs to check for emptiness " + '(has an "only empty" location storage type).', + recursive=True, + ) + # TODO: Maybe renaming these fields as there are already such fields + # in core but without domains. Something like 'pending_in_move_ids' + in_move_ids = fields.One2many( + "stock.move", + "location_dest_id", + domain=[ + ("state", "in", ("waiting", "confirmed", "partially_available", "assigned")) + ], + help="technical field: the pending incoming stock.moves in the location", + ) + + in_move_line_ids = fields.One2many( + "stock.move.line", + "location_dest_id", + domain=[ + ("state", "in", ("waiting", "confirmed", "partially_available", "assigned")) + ], + help="technical field: the pending incoming " + "stock.move.lines in the location", + ) + out_move_line_ids = fields.One2many( + "stock.move.line", + "location_id", + domain=OUT_MOVE_LINE_DOMAIN, + help="technical field: the pending outgoing " + "stock.move.lines in the location", + ) + location_will_contain_lot_ids = fields.Many2many( + "stock.lot", + store=True, + compute="_compute_location_will_contain_lot_ids", + help="technical field: list of stock.lots in " + "the location, either now or in pending operations", + ) + location_will_contain_product_ids = fields.Many2many( + "product.product", + store=True, + compute="_compute_location_will_contain_product_ids", + help="technical field: list of products in " + "the location, either now or in pending operations", + ) + + leaf_location_ids = fields.Many2many( + "stock.location", + compute="_compute_leaf_location_ids", + recursive=True, + help="technical field: all the leaves locations", + ) + leaf_child_location_ids = fields.Many2many( + "stock.location", + compute="_compute_leaf_location_ids", + recursive=True, + help="technical field: all the leaves sub-locations", + ) + max_height = fields.Float( + related="computed_storage_category_id.max_height", + store=True, + recursive=True, + ) + max_height_in_m = fields.Float( + related="computed_storage_category_id.max_height_in_m", + store=True, + recursive=True, + ) + + do_not_mix_products = fields.Boolean( + compute="_compute_do_not_mix_products", store=True, recursive=True + ) + do_not_mix_lots = fields.Boolean( + compute="_compute_do_not_mix_lots", store=True, recursive=True + ) + only_empty = fields.Boolean( + compute="_compute_only_empty", store=True, recursive=True + ) + + def init(self): # pylint: disable=missing-return + super().init() + if not index_exists(self._cr, "stock_move_line_location_state_index"): + self._cr.execute( + """ + CREATE INDEX stock_move_line_location_state_index + ON + stock_move_line (location_id, state) + WHERE + (state IS NULL OR state NOT IN ('cancel', 'done')) + """ + ) + + @api.depends( + "usage", + "computed_storage_category_id.allow_new_product", + "computed_storage_category_id.capacity_ids.allow_new_product", + ) + def _compute_do_not_mix_lots(self): + """ + This computes the value that says if the location cannot have mixed lots from: + - its own Storage Category value + - one of its Storage Capacities value + """ + for rec in self: + rec.do_not_mix_lots = rec.usage == "internal" and ( + any( + storage_type.allow_new_product == "same_lot" + for storage_type in rec.computed_storage_category_id.capacity_ids + ) + or rec.computed_storage_category_id.allow_new_product == "same_lot" + ) + + @api.depends( + "usage", + "computed_storage_category_id.allow_new_product", + "computed_storage_category_id.capacity_ids.allow_new_product", + ) + def _compute_only_empty(self): + """ + This computes the value that says if the location cannot have mixed lots from: + - its own Storage Category value + - one of its Storage Capacities value + """ + for rec in self: + rec.only_empty = rec.usage == "internal" and ( + any( + storage_type.allow_new_product == "empty" + for storage_type in rec.computed_storage_category_id.capacity_ids + ) + or rec.computed_storage_category_id.allow_new_product == "empty" + ) + + @api.depends( + "usage", + "computed_storage_category_id.allow_new_product", + "computed_storage_category_id.capacity_ids.allow_new_product", + ) + def _compute_do_not_mix_products(self): + """ + This computes the value that says if the location cannot have mixed lots from: + - its own Storage Category value + - one of its Storage Capacities value + """ + for rec in self: + rec.do_not_mix_products = rec.usage == "internal" and ( + any( + storage_type.allow_new_product in ("same", "same_lot") + for storage_type in rec.computed_storage_category_id.capacity_ids + ) + or rec.computed_storage_category_id.allow_new_product + in ("same", "same_lot") + ) + + @api.depends( + "location_id", "storage_category_id", "location_id.computed_storage_category_id" + ) + def _compute_computed_storage_category_id(self): + """ + This computes the Storage Category depending on: + - its own Storage Category + - or one of its parent (along the parent path) Storage Category + """ + for location in self: + if location.storage_category_id: + location.computed_storage_category_id = location.storage_category_id + else: + parent = location.location_id + location.computed_storage_category_id = parent.storage_category_id + + @api.depends("child_ids.leaf_location_ids", "child_ids.active") + def _compute_leaf_location_ids(self): + """Compute all children leaf locations. + Current location is excluded (not a child)""" + query = """ + SELECT parent.id, ARRAY_AGG(sub.id) AS leaves + FROM stock_location parent + INNER JOIN stock_location sub + ON sub.parent_path LIKE parent.parent_path || '%%' + AND sub.id != parent.id + AND sub.active + LEFT JOIN stock_location subsub + ON subsub.location_id = sub.id + AND subsub.active + WHERE + -- exclude any location which has children so we keep only leaves + subsub.id IS NULL + AND parent.id in %s + GROUP BY parent.id; + """ + self.env.cr.execute(query, (tuple(self.ids),)) + rows = dict(self.env.cr.fetchall()) + for loc in self: + leave_ids = rows.get(loc.id) + if not leave_ids: + loc.leaf_location_ids = loc + loc.leaf_child_location_ids = False + continue + leaves = self.search([("id", "in", leave_ids)]) + loc.leaf_location_ids = leaves + loc.leaf_child_location_ids = leaves + + def _should_compute_will_contain_product_ids(self): + return self.do_not_mix_products + + def _should_compute_will_contain_lot_ids(self): + return self.do_not_mix_lots + + def _should_compute_location_is_empty(self): + return self.only_empty + + @api.depends( + "quant_ids.quantity", + "in_move_ids", + "in_move_line_ids", + "do_not_mix_products", + ) + def _compute_location_will_contain_product_ids(self): + for rec in self: + if not rec._should_compute_will_contain_product_ids(): + no_product = self.env["product.product"].browse() + rec.location_will_contain_product_ids = no_product + else: + products = ( + rec.mapped("quant_ids") + .filtered(lambda q: q.quantity > 0) + .product_id + | rec.mapped("in_move_ids.product_id") + | rec.mapped("in_move_line_ids.product_id") + ) + rec.location_will_contain_product_ids = products + + @api.depends( + "quant_ids.quantity", + "in_move_line_ids", + "do_not_mix_lots", + ) + def _compute_location_will_contain_lot_ids(self): + for rec in self: + if not rec._should_compute_will_contain_lot_ids(): + no_lot = self.env["stock.lot"].browse() + rec.location_will_contain_lot_ids = no_lot + else: + lots = rec.mapped("quant_ids").filtered( + lambda q: q.quantity > 0 + ).lot_id | rec.mapped("in_move_line_ids.lot_id") + rec.location_will_contain_lot_ids = lots + + @api.depends( + "quant_ids.quantity", + "out_move_line_ids.quantity", + "in_move_ids", + "in_move_line_ids", + "only_empty", + ) + def _compute_location_is_empty(self): + # No restriction should apply on customer/supplier/... + # locations and we don't need to compute is empty + # if there is no limit on the location + only_empty_locations = self.filtered( + lambda location: not location._should_compute_location_is_empty() + ) + only_empty_locations.update({"location_is_empty": True}) + records = self - only_empty_locations + if not records: + return + location_domain = [("location_id", "in", records.ids)] + out_qty_by_location = {} + qty_by_location = {} + for group in self.env["stock.move.line"].read_group( + OUT_MOVE_LINE_DOMAIN + location_domain, + fields=["quantity:sum"], + groupby=["location_id"], + ): + location_id = group["location_id"][0] + out_qty_by_location[location_id] = group["quantity"] + for group in self.env["stock.quant"].read_group( + location_domain, fields=["quantity:sum"], groupby=["location_id"] + ): + location_id = group["location_id"][0] + qty_by_location[location_id] = group["quantity"] + for rec in records: + # we do want to keep a write here even if the value is the same + # to enforce concurrent transaction safety: 2 moves taking + # quantities in a location have to be executed sequentially + # or the location could remain "not empty" + if ( + qty_by_location.get(rec.id, 0.0) - out_qty_by_location.get(rec.id, 0.0) + > 0 + or rec.in_move_ids + or rec.in_move_line_ids + ): + rec.location_is_empty = False + else: + rec.location_is_empty = True + + # method provided by "stock_putaway_hook" + def _putaway_strategy_finalizer( + self, + putaway_location, + product, + quantity=0, + package=None, + packaging=None, + additional_qty=None, + ): + putaway_location = super()._putaway_strategy_finalizer( + putaway_location, product, quantity, package, packaging, additional_qty + ) + if package: + # If package provided, the product is not set + # (in the get_putaway_strategy() method) + product = package.single_product_id or product + return self._get_package_type_putaway_strategy( + putaway_location, package, product, quantity + ) + + def _get_package_type(self, package, product): + # Returns the package type either from the package, either from the product + package_type = self.env["stock.package.type"].browse() + if package: + package_type = package.package_type_id + _logger.debug( + f"Computing putaway for package {package} " + f"of package type {package_type}" + ) + elif product.package_type_id: + # Get the default package type on product if defined + package_type = product.package_type_id + _logger.debug( + f"Computing putaway for product {product} " + f"of package type {package_type}" + ) + return package_type + + def _get_package_type_putaway_strategy( + self, putaway_location, package, product, quantity + ): + package_type = self._get_package_type(package, product) + # exclude_sml_ids are passed into the context during the get_putaway_strategy + # call. + if package: + quants = package.quant_ids + else: + stock_move_line_ids = self.env.context.get("exclude_sml_ids", []) + stock_move_lines = self.env["stock.move.line"].browse(stock_move_line_ids) + quants = stock_move_lines.mapped("reserved_quant_id").filtered( + lambda q: q.product_id == product + ) + if not package_type: + # Fallback on standard one + return putaway_location + # TODO: Remove this and use only putaway_location as always filled in + dest_location = putaway_location or self + _logger.debug("putaway location: %s", dest_location.name) + package_locations = self.env["stock.storage.location.sequence"].search( + [ + ("package_type_id", "=", package_type.id), + ("location_id", "child_of", dest_location.ids), + ] + ) + if not package_locations: + return dest_location + + for package_sequence in package_locations: + if not package_sequence.can_be_applied(putaway_location, quants, product): + continue + pref_loc = package_sequence.location_id + storage_locations = pref_loc.get_storage_locations(products=product) + _logger.debug("Storage locations selected: %s" % storage_locations) + allowed_location = storage_locations.select_first_allowed_location( + package_type, quants, product + ) + if allowed_location: + _logger.debug( + "Applied putaway strategy to location %s" + % allowed_location.complete_name + ) + # Reapply putaway strategy if particular rules have been put on + # product level and check if the allowed location is not self to + # avoid recursive computations + if allowed_location != self: + final_location = allowed_location._get_putaway_strategy( + product, quantity, package + ) + return final_location + return allowed_location + _logger.debug( + "Could not find a valid putaway location, fallback to %s" + % putaway_location.complete_name + ) + return putaway_location + + def get_storage_locations(self, products=None): + # TODO support multiple products? cf ABC + self.ensure_one() + locations = self.browse() + if self.pack_putaway_strategy == "none": + locations = self + return locations + else: + products = products or self.env["product.product"] + locations = self._get_sorted_leaf_child_locations(products) + return locations + + def _get_sorted_leaf_locations_orderby(self, products): + """Return SQL orderby clause and params for sorting locations + + First, locations are ordered by max height, knowing that a max height of 0 + means "no limit" and as such it should be among the last locations. + Then, they are ordered by a sequence and name. + """ + self.env["stock.location"].flush_model( + ["max_height", "package_type_putaway_sequence", "name"] + ) + orderby = [] + if self.pack_putaway_strategy == "ordered_locations": + orderby = [ + "CASE WHEN max_height > 0 THEN max_height ELSE 'Infinity' END", + "package_type_putaway_sequence", + "name", + "id", + ] + return ", ".join(orderby), [] + + def _get_sorted_leaf_child_locations(self, products): + """Return sorted leaf sub-locations + + The locations are candidate locations that will be evaluated one per + one in order to find the first available location. They must be leaf + locations where we can actually put goods. + """ + if not self.leaf_child_location_ids: + return self.leaf_child_location_ids + query = self._where_calc([("id", "in", self.leaf_child_location_ids.ids)]) + _, where_clause, where_params = query.get_sql() + orderby_clause, orderby_params = self._get_sorted_leaf_locations_orderby( + products + ) + query = sql.SQL( + "SELECT id FROM {table} WHERE {where} ORDER BY {orderby}" + ).format( + table=sql.Identifier(self._table), + where=sql.SQL(where_clause), + orderby=sql.SQL(orderby_clause), + ) + self._cr.execute(query, where_params + orderby_params) + location_ids = [x[0] for x in self.env.cr.fetchall()] + return self.env["stock.location"].browse(location_ids) + + def select_first_allowed_location(self, package_type, quants, products): + allowed = self.select_allowed_locations(package_type, quants, products, limit=1) + return allowed + + def _domain_location_storage_type_constraints(self, package_type, quants, products): + """Compute the domain for the location storage type which match the package + storage type + + This method also checks the "capacity" constraints (height and weight) + """ + # There can be multiple location storage types for a given + # location, so we need to filter on the ones relative to the package + # we consider. + Capacity = self.env["stock.storage.category.capacity"] + compatible_location_storage_types = Capacity.search( + [("computed_location_ids", "in", self.ids)] + ) + + pertinent_loc_storagetype_domain = [ + ("id", "in", compatible_location_storage_types.ids), + ("package_type_id", "=", package_type.id), + ] + if quants.package_id.height: + pertinent_loc_storagetype_domain += [ + "|", + ("storage_category_id.max_height_in_m", "=", 0), + ( + "storage_category_id.max_height_in_m", + ">=", + quants.package_id.height_in_m, + ), + ] + package_weight_kg = ( + quants.package_id.pack_weight_in_kg + or quants.package_id.estimated_pack_weight_kg + ) + if package_weight_kg: + pertinent_loc_storagetype_domain += [ + "|", + ("storage_category_id.max_weight_in_kg", "=", 0), + ("storage_category_id.max_weight_in_kg", ">=", package_weight_kg), + ] + _logger.debug( + "pertinent storage type domain: %s", pertinent_loc_storagetype_domain + ) + return pertinent_loc_storagetype_domain + + def _allowed_locations_for_location_storage_types( + self, location_storage_types, quants, products + ): + valid_location_ids = set() + for loc_storage_type in location_storage_types: + location_domain = loc_storage_type._domain_location_storage_type( + self, quants, products + ) + _logger.debug("pertinent location domain: %s", location_domain) + locations = self.search(location_domain) + valid_location_ids |= set(locations.ids) + return self.browse(valid_location_ids) + + def _select_final_valid_putaway_locations(self, limit=None): + """Return the valid locations using the provided limit + + ``self`` contains locations already ordered and contains + only valid locations. + This method can be used as a hook to add or remove valid + locations based on other properties. Pay attention to + keep the order. + """ + return self[:limit] + + def select_allowed_locations(self, package_type, quants, products, limit=None): + """Filter allowed locations for a storage type + + ``self`` contains locations already ordered according to the + putaway strategy, so beware of the return that must keep the + same order + """ + # We have package who may be placed in a stock.location + # + # 1. On the stock.location there are location_storage_type and on the + # packages there are package_storage_type. Between both, there's a m2m + # who says which package ST can be placed in which location ST + # + # 2. On a location_ST there are some additional restrictions: a - + # capacity (volume / height / weight) and b - properties (boolean + # flags: only empty, don't mix lots, don't mix products) + Capacity = self.env["stock.storage.category.capacity"] + _logger.debug( + "select allowed location for package storage type %s (q=%s, p=%s)", + package_type.name, + quants, + products.mapped("name"), + ) + # 1: filter locations on compatible storage type + compatible_locations = self.search( + [ + ("id", "in", self.ids), + ( + "computed_storage_category_id.capacity_ids", + "in", + package_type.storage_category_capacity_ids.ids, + ), + ] + ) + pertinent_loc_s_t_domain = ( + compatible_locations._domain_location_storage_type_constraints( + package_type, quants, products + ) + ) + + pertinent_loc_storage_types = Capacity.search(pertinent_loc_s_t_domain) + + # now loop over the pertinent location storage types (there should be + # few of them) and check for properties to find suitable locations + valid_locations = ( + compatible_locations._allowed_locations_for_location_storage_types( + pertinent_loc_storage_types, quants, products + ) + ) + + valid_locations = self._order_allowed_locations(valid_locations) + valid_locations = valid_locations._select_final_valid_putaway_locations( + limit=limit + ) + + _logger.debug( + "select allowed location for package storage" + " type %s (q=%s, p=%s) found %d locations", + package_type.name, + quants, + products.mapped("name"), + len(valid_locations), + ) + return valid_locations + + def _order_allowed_locations(self, valid_locations): + """Return the ordered list of valid_locations + + By default the order should be the same as self. However, if the + valid_locations list contains locations configured to not mix products, + we must give priority to locations that already contains products + (the ones with less qty first) + """ + valid_no_mix = valid_locations.filtered("do_not_mix_products") + loc_ordered_by_qty = [] + if valid_no_mix: + StockQuant = self.env["stock.quant"] + domain_quant = [("location_id", "in", valid_no_mix.ids)] + loc_ordered_by_qty = [ + item["location_id"][0] + for item in StockQuant.read_group( + domain_quant, + ["location_id", "quantity"], + ["location_id"], + orderby="quantity", + ) + if (float_compare(item["quantity"], 0, precision_digits=2) > 0) + ] + valid_location_ids = set(valid_locations.ids) - set(loc_ordered_by_qty) + ordered_valid_location_ids = loc_ordered_by_qty + [ + id_ for id_ in self.ids if id_ in valid_location_ids + ] + valid_locations = self.browse(ordered_valid_location_ids) + return valid_locations + + @api.depends_context("fixed_child_internal_location") + def _compute_child_internal_location_ids(self): + """ + This will override the child selection by setting self as + the only child. + + TODO: Maybe adding a field on view location in order to compute this + without context changing + """ + if self.env.context.get("fixed_child_internal_location"): + internal_location_id = self.env.context.get("fixed_child_internal_location") + internal_location = self.browse(internal_location_id) + if internal_location_id: + self.update( + { + "child_internal_location_ids": [ + Command.set(internal_location.ids) + ] + } + ) + else: + return super()._compute_child_internal_location_ids() + + def _get_stock_storage_type_putaway_rules( + self, product, package=None, packaging=None + ): + """ + We have retrieved the code from stock module in order to get + the evaluated putaway rules on this location in order to determine + if we should return self or super(). + """ + self = self._check_access_putaway() + products = self.env.context.get("products", self.env["product.product"]) + products |= product + # find package type on package or packaging + package_type = self.env["stock.package.type"] + if package: + package_type = package.package_type_id + elif packaging: + package_type = packaging.package_type_id + + categ = ( + products.categ_id + if len(products.categ_id) == 1 + else self.env["product.category"] + ) + categs = categ + while categ.parent_id: + categ = categ.parent_id + categs |= categ + + putaway_rules = self.putaway_rule_ids.filtered( + lambda rule: (not rule.product_id or rule.product_id in products) + and (not rule.category_id or rule.category_id in categs) + and (not rule.package_type_ids or package_type in rule.package_type_ids) + ) + return putaway_rules + + def _get_putaway_strategy( + self, product, quantity=0, package=None, packaging=None, additional_qty=None + ): + """ + As standard Odoo method will return the first real child of a view, + this is not convenient as if a storage sequence is set on that view, + it won't be applied. + + So, we check if no putaway rule is set on the view, then set the id of + the view to context to bypass the child_internal_location_ids field. + """ + if self.usage == "view": + putaway_rules = self._get_stock_storage_type_putaway_rules( + product=product, package=package, packaging=packaging + ) + if not putaway_rules: + self_fixed_child = self.with_context( + fixed_child_internal_location=self.id + ) + return super(StockLocation, self_fixed_child)._get_putaway_strategy( + product, + quantity=quantity, + package=package, + packaging=packaging, + additional_qty=additional_qty, + ) + return super()._get_putaway_strategy( + product, + quantity=quantity, + package=package, + packaging=packaging, + additional_qty=additional_qty, + ) diff --git a/stock_storage_type/models/stock_package_level.py b/stock_storage_type/models/stock_package_level.py new file mode 100644 index 0000000000..7e9a15ac48 --- /dev/null +++ b/stock_storage_type/models/stock_package_level.py @@ -0,0 +1,99 @@ +# Copyright 2020 Camptocamp SA +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl) + +from odoo import api, fields, models + + +class StockPackageLevel(models.Model): + _inherit = "stock.package_level" + + # We use a domain with the module 'web_domain_field', because if we use a + # many2many with a domain in the view, the onchange updating the many2many + # client side blocks the browser for several seconds if we have thousands + # of locations. + allowed_location_dest_ids = fields.Many2many( + comodel_name="stock.location", + string="Allowed Destinations", + compute="_compute_allowed_location_dest_ids", + ) + + @api.depends( + "package_id", + "package_id.package_type_id", + "package_id.package_type_id.storage_category_capacity_ids", + "package_id.package_type_id.storage_location_sequence_ids", + "package_id.package_type_id.storage_location_sequence_ids.location_id", + "package_id.package_type_id.storage_location_sequence_ids.location_id.leaf_location_ids", # noqa + # Dependency on quant_ids managed by cache invalidation on create/write + "picking_id", + "picking_id.location_dest_id", + "picking_id.package_level_ids.location_dest_id", + ) + def _compute_allowed_location_dest_ids(self): + """ + We compute here a recordset that will be used in a domain like + [("id", "in", allowed_location_dest_ids)] + """ + for pack_level in self: + picking_child_location_dest_ids = self.env["stock.location"].search( + [("id", "child_of", pack_level.picking_id.location_dest_id.id)] + ) + # For outgoing type, we don't set the location dest so avoid + # computing the domain + if ( + pack_level.package_id.package_type_id + and pack_level.picking_type_code != "outgoing" + ): + allowed_locations = pack_level._get_allowed_location_dest_ids() + # TODO check if intersect is needed as we use picking dest loc + # in _get_allowed_location_dest_ids + intersect_locations = ( + allowed_locations & picking_child_location_dest_ids + ) + # Add the pack_level actual location_dest since it is actually + # excluded by the check on incoming stock moves + intersect_locations |= pack_level.location_dest_id + pack_level.allowed_location_dest_ids = intersect_locations + elif isinstance(pack_level.id, models.NewId): + pack_level.allowed_location_dest_ids = ( + pack_level.picking_id.location_dest_id + ) + else: + pack_level.allowed_location_dest_ids = picking_child_location_dest_ids + + def _get_allowed_location_dest_ids(self): + package_locations = self.env["stock.storage.location.sequence"].search( + [ + ( + "package_type_id", + "=", + self.package_id.package_type_id.id, + ), + ("location_id", "child_of", self.picking_id.location_dest_id.id), + ] + ) + all_allowed_locations = set() + products = self.mapped("move_line_ids.product_id") + for pack_loc in package_locations: + pref_loc = pack_loc.location_id + storage_locations = pref_loc.get_storage_locations(products=products) + allowed_locations = storage_locations.select_allowed_locations( + self.package_id.package_type_id, + self.package_id.quant_ids, + products, + ) + all_allowed_locations.update(allowed_locations.ids) + return self.env["stock.location"].browse(all_allowed_locations) + + def recompute_pack_putaway(self): + for level in self: + if not level.package_id.quant_ids: + continue + level.location_dest_id = ( + level.location_dest_id._get_package_type_putaway_strategy( + level.location_dest_id, + level.package_id, + level.mapped("move_line_ids.product_id"), + 1.0, + ) + ) diff --git a/stock_storage_type/models/stock_package_type.py b/stock_storage_type/models/stock_package_type.py new file mode 100644 index 0000000000..1cb6667ffe --- /dev/null +++ b/stock_storage_type/models/stock_package_type.py @@ -0,0 +1,70 @@ +# Copyright 2019 Camptocamp SA +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl) +from odoo import _, api, fields, models + + +class StockPackageType(models.Model): + _inherit = "stock.package.type" + + product_packaging_ids = fields.One2many("product.packaging", "package_type_id") + storage_location_sequence_ids = fields.One2many( + "stock.storage.location.sequence", + "package_type_id", + string="Put-Away sequence", + ) + storage_type_message = fields.Html(compute="_compute_storage_type_message") + height_required = fields.Boolean( + string="Height required for packages", + help=("Height is mandatory for packages configured with this storage type."), + default=False, + ) + barcode = fields.Char(copy=False) + # TODO: Check if this is convenient with the constraint on barcode field + # in core module + active = fields.Boolean(default=True) + + @api.depends("storage_location_sequence_ids") + def _compute_storage_type_message(self): + for package_type in self: + storage_locations = package_type.storage_location_sequence_ids + if storage_locations: + formatted_storage_locations_msgs = [] + last = False + for sl in storage_locations: + # check if we're on the last element + if sl == storage_locations[-1]: + last = True + formatted_storage_locations_msgs.append( + sl._format_package_storage_type_message(last=last) + ) + msg = _( + "When a package with storage type {name} is put away, the " + "strategy will look for an allowed location in the " + "following locations:

" + "{message}

" + "Note: this happens as long as these locations " + "are children of the stock move destination location " + "or as long as these locations are children of the " + "destination location after the (product or category) " + "put-away is applied." + ).format( + name=package_type.name, + message="
".join(formatted_storage_locations_msgs), + ) + else: + msg = _( + 'The "Put-Away sequence" ' + "must be defined in order to put away packages using " + "this package storage type ({storage})." + ).format(storage=package_type.name) + package_type.storage_type_message = msg + + def action_view_storage_locations(self): + return { + "name": _("Put-away sequence"), + "type": "ir.actions.act_window", + "res_model": "stock.storage.location.sequence", + "view_mode": "list", + "domain": [("package_type_id", "=", self.id)], + "context": {"default_package_type_id": self.id}, + } diff --git a/stock_storage_type/models/stock_quant.py b/stock_storage_type/models/stock_quant.py new file mode 100644 index 0000000000..1beb6a7f78 --- /dev/null +++ b/stock_storage_type/models/stock_quant.py @@ -0,0 +1,155 @@ +# Copyright 2020 Camptocamp SA +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl) +from odoo import _, api, models +from odoo.exceptions import ValidationError + + +class StockQuant(models.Model): + _inherit = "stock.quant" + + @api.constrains("package_id", "location_id", "lot_id", "product_id") + def _check_storage_capacities(self): + """ + Check if at least one storage capacity allows the package type + into the quant's location + """ + for quant in self: + location = quant.location_id + package_type = quant.package_id.package_type_id + storage_capacities = location.computed_storage_category_id.capacity_ids + if not quant.package_id or not package_type or not storage_capacities: + continue + allowed_capacities = storage_capacities.filtered( + lambda capacity, package_type=package_type: package_type + == capacity.package_type_id + ) + if not allowed_capacities: + raise ValidationError( + _( + "Package type {storage} is not allowed into " + "Location {location}" + ).format(storage=package_type.name, location=location.name) + ) + allowed = False + package_weight_kg = ( + quant.package_id.pack_weight_in_kg + or quant.package_id.estimated_pack_weight_kg + ) + package_quants = quant.package_id.mapped("quant_ids") + package_products = package_quants.mapped("product_id") + package_lots = package_quants.mapped("lot_id") + other_quants_in_location = self.search( + [ + ("location_id", "=", location.id), + ("id", "not in", package_quants.ids), + ("quantity", ">", 0), + ] + ) + products_in_location = other_quants_in_location.mapped("product_id") + lots_in_location = other_quants_in_location.mapped("lot_id") + capacity_fails = [] + for capacity in allowed_capacities: + # Check content constraints + if capacity.allow_new_product == "empty" and other_quants_in_location: + capacity_fails.append( + _( + "Storage Capacity {storage_capacity} is flagged " + "'only empty'" + " with other quants in location." + ).format(storage_capacity=capacity.display_name) + ) + continue + if capacity.allow_new_product == "same" and ( + len(package_products) > 1 + or len(products_in_location) >= 1 + and package_products != products_in_location + ): + capacity_fails.append( + _( + "Storage Capacity {storage_capacity} is flagged 'do not mix" + " products' but there are other products in " + "location." + ).format(storage_capacity=capacity.display_name) + ) + continue + if capacity.allow_new_product == "same_lot" and ( + len(package_lots) > 1 + or len(lots_in_location) >= 1 + and package_lots != lots_in_location + ): + capacity_fails.append( + _( + "Storage Capacity {storage_capacity} is flagged 'do not mix" + " lots' but there are other lots in " + "location." + ).format(storage_capacity=capacity.display_name) + ) + continue + # Check size constraint + if ( + capacity.storage_category_id.max_height_in_m + and quant.package_id.height_in_m + > capacity.storage_category_id.max_height_in_m + ): + capacity_fails.append( + _( + "Storage Category {storage_category} defines " + "max height of {max_h} but the package is bigger: " + "{height}." + ).format( + storage_category=capacity.storage_category_id.display_name, + max_h=capacity.storage_category_id.max_height_in_m, + height=quant.package_id.height_in_m, + ) + ) + continue + if ( + capacity.storage_category_id.max_weight_in_kg + and package_weight_kg + > capacity.storage_category_id.max_weight_in_kg + ): + capacity_fails.append( + _( + "Storage Category {storage_category} defines " + "max weight of {max_w} but the package is heavier: " + "{weight_kg}." + ).format( + storage_category=capacity.storage_category_id.display_name, + max_w=capacity.storage_category_id.max_weight_in_kg, + weight_kg=package_weight_kg, + ) + ) + continue + # If we get here, it means there is a location storage type + # allowing the package into the location + allowed = True + break + if not allowed: + raise ValidationError( + _( + "Package {package} is not allowed into location {location}," + " because there isn't any storage capacity that allows" + " package type {type} into it:\n\n{fails}" + ).format( + package=quant.package_id.name, + location=location.complete_name, + type=package_type.name, + fails="\n".join(capacity_fails), + ) + ) + + def write(self, vals): + res = super().write(vals) + self._invalidate_package_level_allowed_location_dest_ids() + return res + + @api.model_create_multi + def create(self, vals_list): + res = super().create(vals_list) + self._invalidate_package_level_allowed_location_dest_ids() + return res + + def _invalidate_package_level_allowed_location_dest_ids(self): + self.env["stock.package_level"].invalidate_model( + fnames=["allowed_location_dest_ids"] + ) diff --git a/stock_storage_type/models/stock_quant_package.py b/stock_storage_type/models/stock_quant_package.py new file mode 100644 index 0000000000..03faf669c8 --- /dev/null +++ b/stock_storage_type/models/stock_quant_package.py @@ -0,0 +1,92 @@ +# Copyright 2019 Camptocamp SA +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl) +from odoo import _, api, fields, models +from odoo.exceptions import ValidationError + + +class StockQuantPackage(models.Model): + _inherit = "stock.quant.package" + + pack_weight_in_kg = fields.Float( + help="Technical field, to speed up comparaisons", + compute="_compute_pack_weight_in_kg", + store=True, + ) + height_in_m = fields.Float( + help="Technical field, to speed up comparaisons", + compute="_compute_height_in_m", + store=True, + ) + + @api.depends("pack_weight", "weight_uom_id") + def _compute_pack_weight_in_kg(self): + uom_kg = self.env.ref("uom.product_uom_kgm") + for package in self: + package.pack_weight_in_kg = package.weight_uom_id._compute_quantity( + qty=package.pack_weight, + to_unit=uom_kg, + round=False, + ) + + @api.depends("height", "length_uom_id") + def _compute_height_in_m(self): + uom_meters = self.env.ref("uom.product_uom_meter") + for package in self: + package.height_in_m = package.length_uom_id._compute_quantity( + qty=package.height, + to_unit=uom_meters, + round=False, + ) + + @api.constrains("height", "package_type_id", "product_packaging_id") + def _check_package_type_height_required(self): + for package in self: + if package.package_type_id.height_required and not package.height: + raise ValidationError( + _("The height is mandatory on package {}.").format(package.name) + ) + + def auto_assign_packaging(self): + res = super().auto_assign_packaging() + for package in self: + if not package.package_type_id: + # if no storage type could be set by auto assign, + # fallback on the default product's storage type (if any) + package._sync_package_type_from_single_product() + return res + + @api.model_create_multi + def create(self, vals): + records = super().create(vals) + records._sync_package_type_from_packaging() + return records + + def write(self, vals): + result = super().write(vals) + if vals.get("product_packaging_id"): + self._sync_package_type_from_packaging() + return result + + def _sync_package_type_from_packaging(self): + for package in self: + if package.package_type_id: + # Do not set package storage type for delivery packages + # to not trigger constraint like height requirement + # (we are delivering them, not storing them) + continue + package_type = package.product_packaging_id.package_type_id + if not package_type: + continue + package.package_type_id = package_type + + def _sync_package_type_from_single_product(self): + for package in self: + if package.package_type_id: + # Do not set package type for delivery packages + # to not trigger constraint like height requirement + # (we are delivering them, not storing them) + continue + package_type = package.single_product_id.package_type_id + if not package_type: + continue + package.package_type_id = package_type diff --git a/stock_storage_type/models/stock_storage_category.py b/stock_storage_type/models/stock_storage_category.py new file mode 100644 index 0000000000..5d55f4449e --- /dev/null +++ b/stock_storage_type/models/stock_storage_category.py @@ -0,0 +1,93 @@ +# Copyright 2022 ACSONE SA +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl) +from odoo import api, fields, models + + +class StockStorageCategory(models.Model): + _inherit = "stock.storage.category" + + allow_new_product = fields.Selection( + selection_add=[("same_lot", "If lots are all the same")], + ondelete={"same_lot": "cascade"}, + ) + + computed_location_ids = fields.One2many( + comodel_name="stock.location", inverse_name="computed_storage_category_id" + ) + + # TODO: Move these fields in another module ? + max_height = fields.Float( + string="Max height (mm)", + help="The max height supported for this storage category.", + ) + + max_height_in_m = fields.Float( + help="Technical field, to speed up comparaisons", + compute="_compute_max_height_in_m", + store=True, + ) + weight_uom_id = fields.Many2one( + # Same as product.packing + "uom.uom", + string="Weight Units of Measure", + domain=lambda self: [ + ("category_id", "=", self.env.ref("uom.product_uom_categ_kgm").id) + ], + help="Weight Unit of Measure", + compute=False, + default=lambda self: self.env[ + "product.template" + ]._get_weight_uom_id_from_ir_config_parameter(), + ) + weight_uom_name = fields.Char( + # Same as product.packing + string="Weight unit of measure label", + related="weight_uom_id.name", + readonly=True, + ) + max_weight_in_kg = fields.Float( + help="Technical field, to speed up comparaisons", + compute="_compute_max_weight_in_kg", + store=True, + ) + + length_uom_id = fields.Many2one( + # Same as product.packing + "uom.uom", + "Dimensions Units of Measure", + domain=lambda self: [ + ("category_id", "=", self.env.ref("uom.uom_categ_length").id) + ], + help="UoM for height", + default=lambda self: self.env[ + "product.template" + ]._get_length_uom_id_from_ir_config_parameter(), + ) + + _sql_constraints = [ + ( + "positive_max_height", + "CHECK(max_height >= 0)", + "Max height should be a positive number.", + ), + ] + + @api.depends("max_height", "length_uom_id") + def _compute_max_height_in_m(self): + uom_m = self.env.ref("uom.product_uom_meter") + for slst in self: + slst.max_height_in_m = slst.length_uom_id._compute_quantity( + qty=slst.max_height, + to_unit=uom_m, + round=False, + ) + + @api.depends("max_weight") + def _compute_max_weight_in_kg(self): + uom_kg = self.env.ref("uom.product_uom_kgm") + for slst in self: + slst.max_weight_in_kg = slst.weight_uom_id._compute_quantity( + qty=slst.max_weight, + to_unit=uom_kg, + round=False, + ) diff --git a/stock_storage_type/models/stock_storage_category_capacity.py b/stock_storage_type/models/stock_storage_category_capacity.py new file mode 100644 index 0000000000..7f903be945 --- /dev/null +++ b/stock_storage_type/models/stock_storage_category_capacity.py @@ -0,0 +1,110 @@ +# Copyright 2022 ACSONE SA +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl) +from odoo import _, api, fields, models + + +class StorageCategoryProductCapacity(models.Model): + _inherit = "stock.storage.category.capacity" + + allow_new_product = fields.Selection( + selection=[ + ("empty", "If the location is empty"), + ("same", "If all products are same"), + ("mixed", "Allow mixed products"), + ("same_lot", "If all lots are the same"), + ], + default="mixed", + required=True, + ) + computed_location_ids = fields.One2many( + comodel_name="stock.location", + related="storage_category_id.computed_location_ids", + ) + has_restrictions = fields.Boolean( + compute="_compute_has_restrictions", + help="Technical: This is used to check if we need to display warning message", + ) + + @api.model + def _get_display_name_attributes(self): + """ + Adds the storage capacity attributes to compose the display name + """ + attributes = super()._get_display_name_attributes() + value = self._fields["allow_new_product"].convert_to_export( + self.allow_new_product, self + ) + attributes.append(_("Allow New Product: ") + value) + return attributes + + @api.model + def _compute_display_name_depends(self): + depends = super()._compute_display_name_depends() + depends.append("allow_new_product") + return depends + + @api.depends( + "allow_new_product", + "storage_category_id.max_height", + "storage_category_id.max_weight", + ) + def _compute_has_restrictions(self): + """ + A storage capacity has restrictions when it: + - does not accept mixed products + - or does not accept mixed lots + - or do have a maximum height set on its category + - or do have a maximum weight set on its category + """ + for capacity in self: + capacity.has_restrictions = any( + [ + capacity.allow_new_product != "mixed", + capacity.storage_category_id.max_height, + capacity.storage_category_id.max_weight, + ] + ) + + def _get_product_location_domain(self, products): + """ + Helper to get products location domain + """ + return [ + "|", + # Ideally, we would like a domain which is a strict comparison: + # if we do not mix products, we should be able to filter on == + # product.id. Here, if we can create a move for product B and + # set it's destination in a location already used by product A, + # then all the new moves for product B will be allowed in the + # location. + ("location_will_contain_product_ids", "in", products.ids), + ("location_will_contain_product_ids", "=", False), + ] + + def _domain_location_storage_type(self, candidate_locations, quants, products): + """ + Compute a domain which applies the constraint of the + Stock Storage Category Capacities to select locations among candidate + locations. + """ + self.ensure_one() + location_domain = [ + ("id", "in", candidate_locations.ids), + ("computed_storage_category_id.capacity_ids", "in", self.ids), + ] + # Build the domain using the 'allow_new_product' field + if self.allow_new_product == "empty": + location_domain.append(("location_is_empty", "=", True)) + elif self.allow_new_product == "same": + location_domain += self._get_product_location_domain(products) + elif self.allow_new_product == "same_lot": + lots = quants.mapped("lot_id") + # As same lot should filter also on same product + location_domain += self._get_product_location_domain(products) + location_domain += [ + "|", + # same comment as for the products + ("location_will_contain_lot_ids", "in", lots.ids), + ("location_will_contain_lot_ids", "=", False), + ] + return location_domain diff --git a/stock_storage_type/models/stock_storage_location_sequence.py b/stock_storage_type/models/stock_storage_location_sequence.py new file mode 100644 index 0000000000..e2c5b4312b --- /dev/null +++ b/stock_storage_type/models/stock_storage_location_sequence.py @@ -0,0 +1,92 @@ +# Copyright 2019 Camptocamp SA +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl) +from odoo import _, fields, models + + +class StockStorageLocationSequence(models.Model): + _name = "stock.storage.location.sequence" + _description = "Sequence of locations to put-away the package storage type" + _order = "sequence" + + package_type_id = fields.Many2one("stock.package.type", required=True) + sequence = fields.Integer(required=True) + location_id = fields.Many2one( + "stock.location", + required=True, + ) + location_putaway_strategy = fields.Selection( + related="location_id.pack_putaway_strategy" + ) + location_sequence_cond_ids = fields.Many2many( + string="Conditions", + comodel_name="stock.storage.location.sequence.cond", + relation="stock_location_sequence_cond_rel", + ) + + def _format_package_storage_type_message(self, last=False): + self.ensure_one() + # TODO improve ugly code + type_matching_locations = self.location_id.get_storage_locations().filtered( + lambda location: self.package_type_id + in location.computed_storage_category_id.capacity_ids.mapped( + "package_type_id" + ) + ) + if type_matching_locations: + # Get the selection description + pack_storage_strat = None + pack_storage_strat_selection = self.location_id._fields[ + "pack_putaway_strategy" + ]._description_selection(self.env) + for strat in pack_storage_strat_selection: + if strat[0] == self.location_id.pack_putaway_strategy: + pack_storage_strat = strat[1] + break + msg = ' * {} ({})'.format( + self.location_id.name, + pack_storage_strat, + ) + if last: + # If last, we want to check if restrictions are defined on + # location storage types accepting this package storage type + # TODO improve ugly code + capacities = type_matching_locations.mapped( + "computed_storage_category_id.capacity_ids" + ).filtered( + lambda lst, package_type=self.package_type_id: package_type + == lst.package_type_id + and not lst.has_restrictions + ) + if not capacities: + msg = _( + ' * {location} (WARNING: ' + "restrictions are active on location storage types " + "matching this package storage type)" + ).format(location=self.location_id.name) + else: + msg = _( + ' * {location} ' + "(WARNING: no suitable location matching storage type)" + ).format(location=self.location_id.name) + return msg + + def button_show_locations(self): + xmlid = "stock.action_location_form" + action = self.env["ir.actions.act_window"]._for_xml_id(xmlid) + action["domain"] = [ + ("parent_path", "=ilike", f"{self.location_id.parent_path}%"), + ( + "computed_storage_capacity_ids", + "in", + self.package_type_id.storage_category_capacity_ids.ids, + ), + ] + return action + + def can_be_applied(self, putaway_location, quant, product): + """Check if conditions are met.""" + self.ensure_one() + for cond in self.location_sequence_cond_ids: + if not cond.evaluate(self, putaway_location, quant, product): + return False + return True diff --git a/stock_storage_type/models/stock_storage_location_sequence_cond.py b/stock_storage_type/models/stock_storage_location_sequence_cond.py new file mode 100644 index 0000000000..b29d026929 --- /dev/null +++ b/stock_storage_type/models/stock_storage_location_sequence_cond.py @@ -0,0 +1,140 @@ +# Copyright 2022 ACSONE SA/NV +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). +import logging +import textwrap + +from odoo import _, api, exceptions, fields, models +from odoo.tools import safe_eval + +_logger = logging.getLogger(__name__) + + +class StockStorageLocationSequenceCond(models.Model): + _name = "stock.storage.location.sequence.cond" + _description = "Stock Storage Location Sequence Condition" + + name = fields.Char(required=True) + + condition_type = fields.Selection( + selection=[("code", "Execute code")], default="code", required=True + ) + code_snippet = fields.Text(required=True) + code_snippet_docs = fields.Text( + compute="_compute_code_snippet_docs", + default=lambda self: self._default_code_snippet_docs(), + ) + + active = fields.Boolean( + default=True, + ) + + _sql_constraints = [ + ( + "name", + "EXCLUDE (name WITH =) WHERE (active = True)", + "Stock storage location sequence condition name must be unique", + ) + ] + + def _compute_code_snippet_docs(self): + for rec in self: + rec.code_snippet_docs = textwrap.dedent(rec._default_code_snippet_docs()) + + @api.constrains("condition_type", "code_snippet") + def _check_condition_type_code(self): + for rec in self.filtered(lambda c: c.condition_type == "code"): + if not rec._code_snippet_valued(): + raise exceptions.UserError( + _( + "Condition type is set to `Code`: " + "you must provide a piece of code" + ) + ) + + def _default_code_snippet_docs(self): + return """ + Available vars: + * storage_location_sequence + * condition + * putaway_location + * quant (recordset) + * product + * env + * datetime + * dateutil + * time + * user + * exceptions + + Must initialize a boolean 'result' variable set to True when condition is met + + """ + + def _get_code_snippet_eval_context( + self, storage_location_sequence, putaway_location, quant, product + ): + """Prepare the context used when evaluating python code + :returns: dict -- evaluation context given to safe_eval + """ + self.ensure_one() + return { + "env": self.env, + "user": self.env.user, + "condition": self, + "putaway_location": putaway_location, + "quant": quant, + "product": product, + "datetime": safe_eval.datetime, + "dateutil": safe_eval.dateutil, + "time": safe_eval.time, + "storage_location_sequence": storage_location_sequence, + "exceptions": safe_eval.wrap_module( + exceptions, ["UserError", "ValidationError"] + ), + } + + def _exec_code(self, storage_location_sequence, putaway_location, quant, product): + self.ensure_one() + if not self._code_snippet_valued(): + return False + eval_ctx = self._get_code_snippet_eval_context( + storage_location_sequence, putaway_location, quant, product + ) + snippet = self.code_snippet + safe_eval.safe_eval(snippet, eval_ctx, mode="exec", nocopy=True) + result = eval_ctx.get("result") + if not isinstance(result, bool): + raise exceptions.UserError( + _("code_snippet should return boolean value into `result` variable.") + ) + if not result: + _logger.debug( + f"Condition {self.name} not met:\n" + f"* putaway sequence: {storage_location_sequence.id}\n" + f"* putaway location: {putaway_location.name}\n" + f"* quants: {quant.ids}\n" + f"* product: {product.display_name}\n" + ) + return result + + def _code_snippet_valued(self): + self.ensure_one() + snippet = self.code_snippet or "" + return bool( + [ + not line.startswith("#") + for line in (snippet.splitlines()) + if line.strip("") + ] + ) + + def evaluate(self, storage_location_sequence, putaway_location, quant, product): + self.ensure_one() + if self.condition_type == "code": + return self._exec_code( + storage_location_sequence, putaway_location, quant, product + ) + condition_type = self.condition_type + raise exceptions.UserError( + _(f"Not able to evaluate condition of type {condition_type}") + ) diff --git a/stock_storage_type/pyproject.toml b/stock_storage_type/pyproject.toml new file mode 100644 index 0000000000..4231d0cccb --- /dev/null +++ b/stock_storage_type/pyproject.toml @@ -0,0 +1,3 @@ +[build-system] +requires = ["whool"] +build-backend = "whool.buildapi" diff --git a/stock_storage_type/readme/CONFIGURATION.rst b/stock_storage_type/readme/CONFIGURATION.rst new file mode 100644 index 0000000000..00a0c3f399 --- /dev/null +++ b/stock_storage_type/readme/CONFIGURATION.rst @@ -0,0 +1,31 @@ +Got to "Inventory > Settings > Storage Types", to define Package Storage Types +and Location Storage Types. + +Package Storage Type can be defined on Product Packaging form view from the +product form view. + +Location Storage Type can be added to any stock location and will be computed +automatically as Allowed Locations Storage Types on said stock location's +children location. + + +- Pack put-away strategy + +On stock locations, you can define a "Pack put-away strategy" as "Ordered bins", +so that any move, having this locations as its destination, will be put-away +on a children location, according to the restrictions from storage types. + +- Put-away sequence + +For any package storage types, you must define a Put-away sequence (i.e. stock +location to search) where such a package is allowed to be put-away. Locations +will be processed sequentially and the first one having an allowed child +location (according to restrictions) will be used to put away. + +A good practice here, is to set a location accepting this storage type without +any restriction as the last location in the sequence, to act as a fallback +if no other location could be found before. + +If a location with a 'none' strategy is set in the sequence and matches with the +move line's destination, it will stop evaluating the next locations in the +sequence. diff --git a/stock_storage_type/readme/CONTRIBUTORS.md b/stock_storage_type/readme/CONTRIBUTORS.md new file mode 100644 index 0000000000..1cc50cead7 --- /dev/null +++ b/stock_storage_type/readme/CONTRIBUTORS.md @@ -0,0 +1,7 @@ +- Akim Juillerat \ +- Guewen Baconnier \ +- Raphaël Reverdy \ +- Jacques-Etienne Baudoux \ +- Laurent Mignon \ +- Fernando La Chica - GreenICe \ +- Denis Roussel \ diff --git a/stock_storage_type/readme/DESCRIPTION.md b/stock_storage_type/readme/DESCRIPTION.md new file mode 100644 index 0000000000..4c8d7f1fc4 --- /dev/null +++ b/stock_storage_type/readme/DESCRIPTION.md @@ -0,0 +1,52 @@ +This module introduces two new models in order to manage stock moves with +packages according to the packaging and stock location properties. + +- Stock package storage type (stock.package.storage.type) + + This model is linked to product.packaging and defines the type of + storage related to a specific packaging. + +- Stock location storage type (stock.location.storage.type) + + This models is linked to stock.location and defines the types of + storage that are allowed for a specific location. + +Therefore a Stock location storage type can include different Stock +package storage type in order to validate the destination of a move with +package into a stock location. Moreover Stock location storage type can +include product, size or lot restrictions for the stock locations it's +defined on, so that a move with package will only be allowed if it +doesn't violate the restrictions defined (cf +stock_location_storage_type_strategy). + +Moreover, this module implements "storage type put-away strategy" in +order to compute a put-away location using storage types. + +The standard put-away strategy is applied *before* the storage type +put-away strategy as the former relies on product or product category +and the latter relies on stock packages. + +In other words, when a move is assigned, Odoo standard put-away strategy +will be applied to compute a new destination on the stock move lines, +according to the product. After this first "put-away computation", the +"storage type" put-away strategy is applied, if the reserved quant is +linked to a package defining a package storage type. + +Storage locations linked to the package storage are processed +sequentially, if said storage location is a child of the move line's +destination location (i.e either the put-away location or the move's +destination location). For each location, their packs storage strategy +is applied as well as the restrictions defined on the stock location +storage types. If no suitable location is found, the next location in +the sequence will be searched and so on. + +For the packs putaway strategy "none", the location is considered as is. +For the "ordered children" strategy, children locations are sorted by +first by max height which is a physical constraint to respect, then pack +putaway sequence which allow to favor for example some level or +corridor, and finally by name. + +At the end, if found location is not the same as the original +destination location, the putaway strategies are applied (e.g.: A "none" +pack putaway strategy is set on computed location and a putaway rule +exists on that one). diff --git a/stock_storage_type/readme/ROADMAP.md b/stock_storage_type/readme/ROADMAP.md new file mode 100644 index 0000000000..6d9dcd6300 --- /dev/null +++ b/stock_storage_type/readme/ROADMAP.md @@ -0,0 +1,48 @@ +Currently, the module supports only strategies applied on packages +(`stock.quant.package`). For implementations that do not use packages, +it would be possible to add compatibility with product packaging. + +The information needed from a package are: + +- the storage type, to know which strategy is applied +- the dimensions and weight, to apply constraints + +If we want to support product packaging, we would need to: + +- guess the product packaging of a move line based on the product and + quantities (multiple of a packaging quantity, for instance 8000 would + be a pallet if the pallet has 2000 units, 1900 would be Box if the Box + has 100 units) +- from the product packaging, we know the storage type and dimensions + +Everywhere the module is using `package_id`, we would have to check +this: + +- use the package if a package is set +- else, use the computed packaging + +About Unit of Measures: + +In v13, there is an assumption of height to be expressed in mm and +weight in kg. In v14, packaging can be expressed in differents units. +Explicit fields are introduced like max_weight_in_kg in order make +simple and efficient computations. + +## Limitation + +If the locations structure is using views intensively in order to +separate storage types kindly (not mixing them), Odoo standard method to +get putaway strategy is returning the first child if a move location +destination is a view. + +This is not convenient if we want to set specific strategies on that +view. So, we override standard process by returning the view itself (if +no putaway is set). + +This can lead to a change on standard behavior as people will need to +change manually the location destination for pickings with views as +default destination. + +Idea: maybe adding a field on view locations to say 'this is a view but +don't apply standard child location selection' could help filtering view +candidates. diff --git a/stock_storage_type/security/ir.model.access.csv b/stock_storage_type/security/ir.model.access.csv new file mode 100644 index 0000000000..ed6a03817a --- /dev/null +++ b/stock_storage_type/security/ir.model.access.csv @@ -0,0 +1,5 @@ +id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink +access_stock_storage_location_sequence_user,access_stock_storage_location_sequence_user,model_stock_storage_location_sequence,base.group_user,1,0,0,0 +access_stock_storage_location_sequence_manager,access_stock_storage_location_sequence_manager,model_stock_storage_location_sequence,stock.group_stock_manager,1,1,1,1 +access_stock_storage_location_sequence_cond_user,access_stock_storage_location_sequence_cond_user,model_stock_storage_location_sequence_cond,base.group_user,1,0,0,0 +access_stock_storage_location_sequence_cond_manager,access_stock_storage_location_sequence_cond_manager,model_stock_storage_location_sequence_cond,stock.group_stock_manager,1,1,1,1 diff --git a/stock_storage_type/static/description/icon.png b/stock_storage_type/static/description/icon.png new file mode 100644 index 0000000000..3a0328b516 Binary files /dev/null and b/stock_storage_type/static/description/icon.png differ diff --git a/stock_storage_type/static/description/index.html b/stock_storage_type/static/description/index.html new file mode 100644 index 0000000000..f683c3b06a --- /dev/null +++ b/stock_storage_type/static/description/index.html @@ -0,0 +1,526 @@ + + + + + +Stock Storage Type + + + +
+

Stock Storage Type

+ + +

Beta License: AGPL-3 OCA/wms Translate me on Weblate Try me on Runboat

+
+
This module introduces two new models in order to manage stock moves +with
+
packages according to the packaging and stock location properties.
+
+
    +
  • Stock package storage type (stock.package.storage.type)

    +

    This model is linked to product.packaging and defines the type of +storage related to a specific packaging.

    +
  • +
  • Stock location storage type (stock.location.storage.type)

    +

    This models is linked to stock.location and defines the types of +storage that are allowed for a specific location.

    +
  • +
+

Therefore a Stock location storage type can include different Stock +package storage type in order to validate the destination of a move with +package into a stock location. Moreover Stock location storage type can +include product, size or lot restrictions for the stock locations it’s +defined on, so that a move with package will only be allowed if it +doesn’t violate the restrictions defined (cf +stock_location_storage_type_strategy).

+

Moreover, this module implements “storage type put-away strategy” in +order to compute a put-away location using storage types.

+

The standard put-away strategy is applied before the storage type +put-away strategy as the former relies on product or product category +and the latter relies on stock packages.

+

In other words, when a move is assigned, Odoo standard put-away strategy +will be applied to compute a new destination on the stock move lines, +according to the product. After this first “put-away computation”, the +“storage type” put-away strategy is applied, if the reserved quant is +linked to a package defining a package storage type.

+

Storage locations linked to the package storage are processed +sequentially, if said storage location is a child of the move line’s +destination location (i.e either the put-away location or the move’s +destination location). For each location, their packs storage strategy +is applied as well as the restrictions defined on the stock location +storage types. If no suitable location is found, the next location in +the sequence will be searched and so on.

+

For the packs putaway strategy “none”, the location is considered as is. +For the “ordered children” strategy, children locations are sorted by +first by max height which is a physical constraint to respect, then pack +putaway sequence which allow to favor for example some level or +corridor, and finally by name.

+

At the end, if found location is not the same as the original +destination location, the putaway strategies are applied (e.g.: A “none” +pack putaway strategy is set on computed location and a putaway rule +exists on that one).

+

Table of contents

+ +
+

Known issues / Roadmap

+

Currently, the module supports only strategies applied on packages +(stock.quant.package). For implementations that do not use packages, +it would be possible to add compatibility with product packaging.

+

The information needed from a package are:

+
    +
  • the storage type, to know which strategy is applied
  • +
  • the dimensions and weight, to apply constraints
  • +
+

If we want to support product packaging, we would need to:

+
    +
  • guess the product packaging of a move line based on the product and +quantities (multiple of a packaging quantity, for instance 8000 would +be a pallet if the pallet has 2000 units, 1900 would be Box if the Box +has 100 units)
  • +
  • from the product packaging, we know the storage type and dimensions
  • +
+

Everywhere the module is using package_id, we would have to check +this:

+
    +
  • use the package if a package is set
  • +
  • else, use the computed packaging
  • +
+

About Unit of Measures:

+

In v13, there is an assumption of height to be expressed in mm and +weight in kg. In v14, packaging can be expressed in differents units. +Explicit fields are introduced like max_weight_in_kg in order make +simple and efficient computations.

+
+

Limitation

+

If the locations structure is using views intensively in order to +separate storage types kindly (not mixing them), Odoo standard method to +get putaway strategy is returning the first child if a move location +destination is a view.

+

This is not convenient if we want to set specific strategies on that +view. So, we override standard process by returning the view itself (if +no putaway is set).

+

This can lead to a change on standard behavior as people will need to +change manually the location destination for pickings with views as +default destination.

+

Idea: maybe adding a field on view locations to say ‘this is a view but +don’t apply standard child location selection’ could help filtering view +candidates.

+
+
+
+

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 to smash it by providing a detailed and welcomed +feedback.

+

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

+
+
+

Credits

+
+

Authors

+
    +
  • Camptocamp
  • +
  • BCIM
  • +
+
+
+

Contributors

+ +
+
+

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.

+

Current maintainers:

+

jbaudoux rousseldenis

+

This module is part of the OCA/wms project on GitHub.

+

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

+
+
+
+ + diff --git a/stock_storage_type/tests/__init__.py b/stock_storage_type/tests/__init__.py new file mode 100644 index 0000000000..06655c55be --- /dev/null +++ b/stock_storage_type/tests/__init__.py @@ -0,0 +1,9 @@ +from . import ( + test_auto_assign_storage_type, + test_package_height_required, + test_package_type_message, + test_stock_location, + test_storage_type, + test_storage_type_move, + test_storage_type_putaway_strategy, +) diff --git a/stock_storage_type/tests/common.py b/stock_storage_type/tests/common.py new file mode 100644 index 0000000000..36b156358f --- /dev/null +++ b/stock_storage_type/tests/common.py @@ -0,0 +1,143 @@ +# Copyright 2020 Camptocamp SA +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl) +from odoo.tests import TransactionCase + + +class TestStorageTypeCommon(TransactionCase): + @classmethod + def setUpClass(cls): + super().setUpClass() + cls.env = cls.env(context=dict(cls.env.context, tracking_disable=True)) + ref = cls.env.ref + cls.warehouse = ref("stock.warehouse0") + # set two steps reception on warehouse + cls.warehouse.reception_steps = "two_steps" + + cls.suppliers_location = ref("stock.stock_location_suppliers") + cls.input_location = ref("stock.stock_location_company") + cls.stock_location = ref("stock.stock_location_stock") + + cls.cardboxes_location = ref("stock_storage_type.stock_location_cardboxes") + cls.pallets_location = ref("stock_storage_type.stock_location_pallets") + cls.pallets_reserve_location = ref( + "stock_storage_type.stock_location_pallets_reserve" + ) + cls.areas = ( + cls.cardboxes_location | cls.pallets_location | cls.pallets_reserve_location + ) + cls.location_sequence_pallet = ref( + "stock_storage_type.stock_package_storage_location_pallets" + ) + + cls.cardboxes_bin_1_location = ref( + "stock_storage_type.stock_location_cardboxes_bin_1" + ) + cls.cardboxes_bin_2_location = ref( + "stock_storage_type.stock_location_cardboxes_bin_2" + ) + cls.cardboxes_bin_3_location = ref( + "stock_storage_type.stock_location_cardboxes_bin_3" + ) + cls.cardboxes_bin_4_location = ref( + "stock_storage_type.stock_location_cardboxes_bin_4" + ) + cls.env["stock.location"]._parent_store_compute() + cls.pallets_bin_1_location = ref( + "stock_storage_type.stock_location_pallets_bin_1" + ) + cls.pallets_bin_2_location = ref( + "stock_storage_type.stock_location_pallets_bin_2" + ) + cls.pallets_bin_3_location = ref( + "stock_storage_type.stock_location_pallets_bin_3" + ) + cls.pallets_bin_4_location = ref( + "stock_storage_type.stock_location_pallets_bin_4" + ) + + cls.receipts_picking_type = ref("stock.picking_type_in") + cls.internal_picking_type = ref("stock.picking_type_internal") + + cls.product = ref("product.product_product_9") + cls.product2 = cls.env["product.product"].create( + {"name": "Product B", "type": "product"} + ) + cls.product3 = cls.env["product.product"].create( + {"name": "Product C", "type": "product"} + ) + cls.product_lot = ref("stock.product_cable_management_box") + + cls.cardboxes_package_storage_type = ref( + "stock_storage_type.package_storage_type_cardboxes" + ) + cls.pallets_package_storage_type = ref( + "stock_storage_type.package_storage_type_pallets" + ) + cls.cardboxes_location_storage_type = ref( + "stock_storage_type.location_storage_type_cardboxes" + ) + cls.pallets_location_storage_type = ref( + "stock_storage_type.location_storage_type_pallets" + ) + + cls.product_cardbox_product_packaging = ref( + "stock_storage_type." "product_product_9_packaging_4_cardbox" + ) + cls.product_pallet_product_packaging = ref( + "stock_storage_type." "product_product_9_packaging_48_pallet" + ) + cls.pallet_pack_type = ref("stock_storage_type." "package_storage_type_pallets") + cls.product_lot_cardbox_product_packaging = cls.env["product.packaging"].create( + { + "name": "5 units cardbox", + "qty": 5, + "product_id": cls.product_lot.id, + "package_type_id": cls.cardboxes_package_storage_type.id, + } + ) + cls.product_lot_pallets_product_packaging = cls.env["product.packaging"].create( + { + "name": "20 units pallet", + "qty": 20, + "product_id": cls.product_lot.id, + "package_type_id": cls.pallets_package_storage_type.id, + } + ) + cls.internal_picking_type.write({"show_entire_packs": True}) + # show_reserved must be set here because it changes the behaviour of + # put_in_pack operation: + # if show_reserved: quantity must be set on stock.picking.move_line_ids + # if not show_reserved: quantity must be set on + # stock.picking.move_line_nosuggest_ids + cls.receipts_picking_type.write( + {"show_entire_packs": True, "show_reserved": True} + ) + + @classmethod + def _update_qty_in_location( + cls, location, product, quantity, package=None, lot=None + ): + quants = cls.env["stock.quant"]._gather( + product, location, lot_id=lot, package_id=package, strict=True + ) + # this method adds the quantity to the current quantity, so remove it + quantity -= sum(quants.mapped("quantity")) + cls.env["stock.quant"]._update_available_quantity( + product, location, quantity, package_id=package, lot_id=lot + ) + + @classmethod + def _create_single_move(cls, product): + picking_type = cls.warehouse.int_type_id + move_vals = { + "name": product.name, + "picking_type_id": picking_type.id, + "product_id": product.id, + "product_uom_qty": 2.0, + "product_uom": product.uom_id.id, + "location_id": cls.input_location.id, + "location_dest_id": picking_type.default_location_dest_id.id, + "state": "confirmed", + "procure_method": "make_to_stock", + } + return cls.env["stock.move"].create(move_vals) diff --git a/stock_storage_type/tests/test_auto_assign_storage_type.py b/stock_storage_type/tests/test_auto_assign_storage_type.py new file mode 100644 index 0000000000..efcf2c1c9b --- /dev/null +++ b/stock_storage_type/tests/test_auto_assign_storage_type.py @@ -0,0 +1,89 @@ +# Copyright 2020 Camptocamp SA +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl) +from .common import TestStorageTypeCommon + + +class TestAutoAssignStorageType(TestStorageTypeCommon): + @classmethod + def setUpClass(cls): + super().setUpClass() + cls.product_packaging = cls.product_lot_pallets_product_packaging + cls.package_storage_type = cls.product_packaging.package_type_id + + # Create a new package type for auto assignation + vals = { + "name": "Auto Assigned Package Type", + } + cls.auto_assigned_package_type = cls.env["stock.package.type"].create(vals) + + def test_auto_assign_package_storage_type_without_packaging_id(self): + """Packages without `packaging_id` are internal packages and they + are intended to be stored in the warehouse. + On such packages storage type is automatically defined. + """ + package = self.env["stock.quant.package"].create( + {"name": "TEST", "product_packaging_id": self.product_packaging.id} + ) + self.assertEqual(package.package_type_id, self.package_storage_type) + + def test_auto_assign_packaging(self): + """ + Test the auto assignation for package type from + the default package type on the product + + - Set the default package type on the product + - Create a move and validate it + - The package type should be set on package + """ + + # Set a default package on the product + self.product.package_type_id = self.auto_assigned_package_type + + confirmed_move = self._create_single_move(self.product) + confirmed_move.location_dest_id = self.pallets_bin_1_location + confirmed_move._assign_picking() + self._update_qty_in_location( + confirmed_move.location_id, + confirmed_move.product_id, + confirmed_move.product_qty, + ) + confirmed_move._action_assign() + picking = confirmed_move.picking_id + picking.action_confirm() + picking.move_line_ids.quantity = 10.0 + first_package = picking.action_put_in_pack() + + picking.button_validate() + + self.assertEqual(self.auto_assigned_package_type, first_package.package_type_id) + + def test_auto_assign_no_packaging(self): + """ + Test the non auto assignation for package type from + the default package type on the product + + - Unset the default package type on the product + - Create a move and validate it + - The package type should not be set on package + """ + + # Set a default package on the product + self.product.package_type_id = False + + confirmed_move = self._create_single_move(self.product) + confirmed_move.location_dest_id = self.pallets_bin_1_location + confirmed_move._assign_picking() + self._update_qty_in_location( + confirmed_move.location_id, + confirmed_move.product_id, + confirmed_move.product_qty, + ) + confirmed_move._action_assign() + picking = confirmed_move.picking_id + picking.action_confirm() + picking.move_line_ids.quantity = 10.0 + first_package = picking.action_put_in_pack() + + picking.button_validate() + + self.assertFalse(first_package.package_type_id) diff --git a/stock_storage_type/tests/test_package_height_required.py b/stock_storage_type/tests/test_package_height_required.py new file mode 100644 index 0000000000..bda19fc729 --- /dev/null +++ b/stock_storage_type/tests/test_package_height_required.py @@ -0,0 +1,21 @@ +# Copyright 2020 Camptocamp SA +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl) +from odoo.exceptions import ValidationError + +from .common import TestStorageTypeCommon + + +class TestStorageTypeMove(TestStorageTypeCommon): + def test_package_storage_type_height_required(self): + packaging = self.product_lot_pallets_product_packaging + storage_type = packaging.package_type_id + # Without 'height_required' + self.env["stock.quant.package"].create( + {"name": "TEST1", "product_packaging_id": packaging.id} + ) + # With 'height_required' + storage_type.height_required = True + with self.assertRaises(ValidationError): + self.env["stock.quant.package"].create( + {"name": "TEST2", "product_packaging_id": packaging.id} + ) diff --git a/stock_storage_type/tests/test_package_type_message.py b/stock_storage_type/tests/test_package_type_message.py new file mode 100644 index 0000000000..7a77d490d8 --- /dev/null +++ b/stock_storage_type/tests/test_package_type_message.py @@ -0,0 +1,34 @@ +# Copyright 2022 ACSONE SA +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl) +from odoo.tests import TransactionCase + + +class TestStorageType(TransactionCase): + @classmethod + def setUpClass(cls): + super().setUpClass() + cls.env = cls.env(context=dict(cls.env.context, tracking_disable=True)) + + cls.stock_location = cls.env.ref("stock.stock_location_stock") + cls.pallets_location_storage_type = cls.env.ref( + "stock_storage_type.location_storage_type_pallets" + ) + cls.pallets_uk_location_storage_type = cls.env.ref( + "stock_storage_type.location_storage_type_pallets_uk" + ) + cls.cardboxes_location_storage_type = cls.env.ref( + "stock_storage_type.location_storage_type_cardboxes" + ) + cls.cardboxes_stock = cls.env.ref("stock_storage_type.stock_location_cardboxes") + cls.cardboxes_bin_1 = cls.env.ref( + "stock_storage_type.stock_location_cardboxes_bin_1" + ) + cls.cardboxes_bin_2 = cls.env.ref( + "stock_storage_type.stock_location_cardboxes_bin_2" + ) + cls.cardboxes_bin_3 = cls.env.ref( + "stock_storage_type.stock_location_cardboxes_bin_3" + ) + cls.cardboxes_bin_4 = cls.env.ref( + "stock_storage_type.stock_location_cardboxes_bin_4" + ) diff --git a/stock_storage_type/tests/test_stock_location.py b/stock_storage_type/tests/test_stock_location.py new file mode 100644 index 0000000000..370f3295d9 --- /dev/null +++ b/stock_storage_type/tests/test_stock_location.py @@ -0,0 +1,293 @@ +# Copyright 2020 Camptocamp SA +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl) +from .common import TestStorageTypeCommon + + +class TestStockLocation(TestStorageTypeCommon): + @classmethod + def setUpClass(cls): + super().setUpClass() + ref = cls.env.ref + cls.pallets_reserve_bin_1_location = ref( + "stock_storage_type.stock_location_pallets_reserve_bin_1" + ) + cls.pallets_reserve_bin_2_location = ref( + "stock_storage_type.stock_location_pallets_reserve_bin_2" + ) + cls.pallets_reserve_bin_3_location = ref( + "stock_storage_type.stock_location_pallets_reserve_bin_3" + ) + cls.pallets_reserve_bin_4_location = ref( + "stock_storage_type.stock_location_pallets_reserve_bin_4" + ) + + def test_get_ordered_leaf_locations(self): + sublocation = self.stock_location.copy( + { + "name": "Sub-location", + "pack_putaway_strategy": "ordered_locations", + "location_id": self.stock_location.id, + } + ) + self.areas.write({"location_id": sublocation.id}) + + # Test with the same max_height on all related storage types (0 here) + ordered_locations = sublocation.get_storage_locations(self.product) + self.assertEqual( + ordered_locations.ids, + ( + self.cardboxes_bin_1_location + | self.cardboxes_bin_2_location + | self.cardboxes_bin_3_location + | self.cardboxes_bin_4_location + | self.pallets_bin_1_location + | self.pallets_bin_2_location + | self.pallets_bin_3_location + | self.pallets_bin_4_location + | self.pallets_reserve_bin_1_location + | self.pallets_reserve_bin_2_location + | self.pallets_reserve_bin_3_location + | self.pallets_reserve_bin_4_location + ).ids, + ) + # Set the max_height on pallets storage type higher than the others + self.pallets_location_storage_type.storage_category_id.max_height = 2 + self.cardboxes_location_storage_type.storage_category_id.max_height = 1 + ordered_locations = sublocation.get_storage_locations(self.product) + self.assertEqual( + ordered_locations.ids, + ( + self.cardboxes_bin_1_location + | self.cardboxes_bin_2_location + | self.cardboxes_bin_3_location + | self.cardboxes_bin_4_location + | self.pallets_bin_1_location + | self.pallets_bin_2_location + | self.pallets_bin_3_location + | self.pallets_bin_4_location + | self.pallets_reserve_bin_1_location + | self.pallets_reserve_bin_2_location + | self.pallets_reserve_bin_3_location + | self.pallets_reserve_bin_4_location + ).ids, + ) + # Set the max_height on cardboxes storage type higher than the others + self.pallets_location_storage_type.storage_category_id.max_height = 1 + self.cardboxes_location_storage_type.storage_category_id.max_height = 2 + ordered_locations = sublocation.get_storage_locations(self.product) + self.assertEqual( + ordered_locations.ids, + ( + self.pallets_bin_1_location + | self.pallets_bin_2_location + | self.pallets_bin_3_location + | self.pallets_bin_4_location + | self.pallets_reserve_bin_1_location + | self.pallets_reserve_bin_2_location + | self.pallets_reserve_bin_3_location + | self.pallets_reserve_bin_4_location + | self.cardboxes_bin_1_location + | self.cardboxes_bin_2_location + | self.cardboxes_bin_3_location + | self.cardboxes_bin_4_location + ).ids, + ) + + def test_will_contain_product_ids(self): + location = self.pallets_bin_1_location + location.computed_storage_category_id.allow_new_product = "same" + + self._update_qty_in_location(location, self.product, 10) + self.assertEqual(location.location_will_contain_product_ids, self.product) + + # the moves and move lines created are not really valid, but we don't care, it's + # only to have "in_move_ids" and "in_move_line_ids" on the location + self.env["stock.move"].create( + { + "name": "test", + "product_id": self.product2.id, + "location_id": self.stock_location.id, + "location_dest_id": location.id, + "product_uom": self.product2.uom_id.id, + "product_uom_qty": 10, + "state": "waiting", + } + ) + self.assertEqual( + location.location_will_contain_product_ids, self.product | self.product2 + ) + + ml_move = self.env["stock.move"].create( + { + "name": "test", + "product_id": self.product3.id, + "location_id": self.stock_location.id, + "location_dest_id": location.location_id.id, + "product_uom": self.product2.uom_id.id, + "product_uom_qty": 10, + "state": "waiting", + } + ) + self.env["stock.move.line"].create( + { + "product_id": self.product3.id, + "location_id": self.stock_location.id, + "location_dest_id": location.id, + "product_uom_id": self.product3.uom_id.id, + "reserved_uom_qty": 10, + "move_id": ml_move.id, + "company_id": self.env.company.id, + } + ) + self.assertEqual( + location.location_will_contain_product_ids, + self.product | self.product2 | self.product3, + ) + + location.computed_storage_category_id.allow_new_product = "mixed" + self.assertEqual( + location.location_will_contain_product_ids, + self.env["product.product"].browse(), + ) + + def test_will_contain_product_ids_quantity(self): + """ + Put a product quantity in pallet bin 1 location + Create a move that will void that location + Transfer the quantity + Odoo lets a quant with zero quantity + The location_will_contain_product_ids should be void + """ + location = self.pallets_bin_1_location + location.computed_storage_category_id.allow_new_product = "same" + + self._update_qty_in_location(location, self.product, 10) + self.assertEqual(location.location_will_contain_product_ids, self.product) + ml_move = self.env["stock.move"].create( + { + "name": "test", + "product_id": self.product.id, + "location_id": location.id, + "location_dest_id": self.env.ref("stock.stock_location_customers").id, + "product_uom": self.product.uom_id.id, + "product_uom_qty": 10, + } + ) + ml_move._action_confirm() + ml_move._action_assign() + + ml_move.quantity_done = 10.0 + ml_move._action_done() + # Odoo lets a zero quantity quant before scheduler do the garbage collector + quant = self.env["stock.quant"].search( + [("product_id", "=", self.product.id), ("location_id", "=", location.id)] + ) + self.assertTrue(quant) + self.assertEqual(0.0, quant.quantity) + self.assertFalse(location.location_will_contain_product_ids) + + def test_will_contain_lot_ids(self): + location = self.pallets_bin_1_location + location.computed_storage_category_id.allow_new_product = "same_lot" + lot_values = {"product_id": self.product.id, "company_id": self.env.company.id} + lot1 = self.env["stock.lot"].create(lot_values) + lot2 = self.env["stock.lot"].create(lot_values) + + self._update_qty_in_location(location, self.product, 10, lot=lot1) + self.assertEqual(location.location_will_contain_lot_ids, lot1) + + # the moves and move lines created are not really valid, but we don't care, it's + # only to have "in_move_ids" and "in_move_line_ids" on the location + ml_move = self.env["stock.move"].create( + { + "name": "test", + "product_id": self.product.id, + "location_id": self.stock_location.id, + "location_dest_id": location.location_id.id, + "product_uom": self.product2.uom_id.id, + "product_uom_qty": 10, + "state": "waiting", + } + ) + self.env["stock.move.line"].create( + { + "product_id": self.product.id, + "lot_id": lot2.id, + "location_id": self.stock_location.id, + "location_dest_id": location.id, + "product_uom_id": self.product.uom_id.id, + "reserved_uom_qty": 10, + "move_id": ml_move.id, + "company_id": self.env.company.id, + } + ) + self.assertEqual(location.location_will_contain_lot_ids, lot1 | lot2) + + location.computed_storage_category_id.allow_new_product = "mixed" + self.assertEqual( + location.location_will_contain_lot_ids, + self.env["stock.lot"].browse(), + ) + + def test_will_contain_product_lot_ids_quantity(self): + """ + Put a product quantity lot in pallet bin 1 location + Create a move that will void that location + Transfer the quantity + Odoo lets a quant with zero quantity + The location_will_contain_product_ids should be void + """ + location = self.pallets_bin_1_location + location.computed_storage_category_id.allow_new_product = "same_lot" + + lot_values = {"product_id": self.product.id, "company_id": self.env.company.id} + lot1 = self.env["stock.lot"].create(lot_values) + + self._update_qty_in_location(location, self.product, 10, lot=lot1) + self.assertEqual(location.location_will_contain_lot_ids, lot1) + ml_move = self.env["stock.move"].create( + { + "name": "test", + "product_id": self.product.id, + "location_id": location.id, + "location_dest_id": self.env.ref("stock.stock_location_customers").id, + "product_uom": self.product.uom_id.id, + "product_uom_qty": 10, + } + ) + ml_move._action_confirm() + ml_move._action_assign() + + ml_move.quantity_done = 10.0 + ml_move._action_done() + # Odoo lets a zero quantity quant before scheduler do the garbage collector + quant = self.env["stock.quant"].search( + [("product_id", "=", self.product.id), ("location_id", "=", location.id)] + ) + self.assertTrue(quant) + self.assertEqual(0.0, quant.quantity) + self.assertFalse(location.location_will_contain_lot_ids) + + def test_location_is_empty_non_internal(self): + location = self.env.ref("stock.stock_location_customers") + # we always consider an non-internal location empty, the put-away + # rules do not apply and we can add as many quants as we want + self.assertTrue(location.location_is_empty) + self._update_qty_in_location(location, self.product, 10) + self.assertTrue(location.location_is_empty) + + def test_location_is_empty(self): + location = self.pallets_reserve_bin_1_location + self.assertTrue(location.only_empty) + self.assertTrue(location.location_is_empty) + self._update_qty_in_location(location, self.product, 10) + self.assertFalse(location.location_is_empty) + + # When the location has no "only_empty" storage type, we don't + # care about if it is empty or not, we keep it as True so we + # can always put things inside. Not computing it prevents + # useless race conditions on concurrent writes. + location.computed_storage_category_id.capacity_ids.filtered( + lambda c: c.allow_new_product == "empty" + ).allow_new_product = "mixed" + self.assertTrue(location.location_is_empty) diff --git a/stock_storage_type/tests/test_storage_type.py b/stock_storage_type/tests/test_storage_type.py new file mode 100644 index 0000000000..4b829750cd --- /dev/null +++ b/stock_storage_type/tests/test_storage_type.py @@ -0,0 +1,194 @@ +# Copyright 2020 Camptocamp SA +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl) +from odoo.tests import TransactionCase + + +class TestStorageType(TransactionCase): + @classmethod + def setUpClass(cls): + super().setUpClass() + cls.env = cls.env(context=dict(cls.env.context, tracking_disable=True)) + cls.location_sequence_pallet = cls.env.ref( + "stock_storage_type.stock_package_storage_location_pallets" + ) + + cls.stock_location = cls.env.ref("stock.stock_location_stock") + cls.pallets_location_storage_type = cls.env.ref( + "stock_storage_type.location_storage_type_pallets" + ) + cls.pallets_uk_location_storage_type = cls.env.ref( + "stock_storage_type.location_storage_type_pallets_uk" + ) + cls.cardboxes_location_storage_type = cls.env.ref( + "stock_storage_type.location_storage_type_cardboxes" + ) + cls.cardboxes_stock = cls.env.ref("stock_storage_type.stock_location_cardboxes") + cls.cardboxes_bin_1 = cls.env.ref( + "stock_storage_type.stock_location_cardboxes_bin_1" + ) + cls.cardboxes_bin_2 = cls.env.ref( + "stock_storage_type.stock_location_cardboxes_bin_2" + ) + cls.cardboxes_bin_3 = cls.env.ref( + "stock_storage_type.stock_location_cardboxes_bin_3" + ) + cls.cardboxes_bin_4 = cls.env.ref( + "stock_storage_type.stock_location_cardboxes_bin_4" + ) + + def test_location_allowed_storage_types(self): + # As cardboxes location storage type is defined on parent stock + # location_storage_type_ids + self.assertEqual( + self.cardboxes_stock.computed_storage_category_id.capacity_ids, + self.cardboxes_location_storage_type, + ) + # It is what's allowed on the parent stock + self.assertEqual( + self.cardboxes_stock.computed_storage_category_id.capacity_ids, + self.cardboxes_location_storage_type, + ) + # and also what's allowed on the children + self.assertEqual( + self.cardboxes_bin_1.computed_storage_category_id.capacity_ids, + self.cardboxes_location_storage_type, + ) + self.assertEqual( + self.cardboxes_bin_2.computed_storage_category_id.capacity_ids, + self.cardboxes_location_storage_type, + ) + self.assertEqual( + self.cardboxes_bin_3.computed_storage_category_id.capacity_ids, + self.cardboxes_location_storage_type, + ) + special_cardbox = self.env["stock.storage.category"].create( + { + "name": "Special Cardboxes", + } + ) + # If I change on a child, it will only be applied on this child + special_cardboxes = self.cardboxes_location_storage_type.copy( + {"storage_category_id": special_cardbox.id} + ) + self.cardboxes_bin_1.storage_category_id = special_cardbox + self.assertEqual( + self.cardboxes_bin_1.computed_storage_category_id.capacity_ids, + special_cardboxes, + ) + # and not on his parent nor siblings + self.assertEqual( + self.cardboxes_stock.computed_storage_category_id.capacity_ids, + self.cardboxes_location_storage_type, + ) + self.assertEqual( + self.cardboxes_bin_2.computed_storage_category_id.capacity_ids, + self.cardboxes_location_storage_type, + ) + self.assertEqual( + self.cardboxes_bin_3.computed_storage_category_id.capacity_ids, + self.cardboxes_location_storage_type, + ) + # If I create a child bin on cardboxes bin 1, it will use the first + # parent's storage type + bin_1_child = self.env["stock.location"].create( + {"name": "Carboxes bin 1 child", "location_id": self.cardboxes_bin_1.id} + ) + self.assertEqual( + bin_1_child.computed_storage_category_id.capacity_ids, special_cardboxes + ) + + def test_location_leaf_locations(self): + cardboxes_leaves = self.env["stock.location"].search( + [("id", "child_of", self.cardboxes_stock.id), ("child_ids", "=", False)] + ) + + self.assertEqual(self.cardboxes_stock.leaf_location_ids, cardboxes_leaves) + all_stock_leaves = self.env["stock.location"].search( + [("id", "child_of", self.stock_location.id), ("child_ids", "=", False)] + ) + self.assertEqual(self.stock_location.leaf_location_ids, all_stock_leaves) + + def test_location_leaf_locations_on_leaf(self): + self.cardboxes_bin_4.active = False + self.assertEqual( + self.cardboxes_stock.leaf_location_ids, + self.cardboxes_bin_1 | self.cardboxes_bin_2 | self.cardboxes_bin_3, + ) + + def test_location_max_height(self): + self.pallets_location_storage_type.storage_category_id.max_height = 2 + self.cardboxes_location_storage_type.storage_category_id.max_height = 0 + category_id = self.pallets_location_storage_type.storage_category_id.id + test_location = self.env["stock.location"].create( + { + "name": "TEST", + "storage_category_id": category_id, + } + ) + # Should be the max height of pallets storage category (2) + self.assertEqual(test_location.max_height, 2) + self.cardboxes_location_storage_type.storage_category_id.max_height = 1 + test_location.storage_category_id = ( + self.cardboxes_location_storage_type.storage_category_id + ) + # Should be the max height of cardboxes storage category (2) + self.assertEqual(test_location.max_height, 1) + + def test_storage_type_max_height_in_meters(self): + # Set the 'max_height' as meters and check that 'max_height_in_m' is equal + uom_meter = self.env.ref("uom.product_uom_meter") + self.pallets_location_storage_type.storage_category_id.length_uom_id = uom_meter + self.pallets_location_storage_type.storage_category_id.max_height = 100 + self.assertEqual( + self.pallets_location_storage_type.storage_category_id.max_height_in_m, 100 + ) + # Then set the UoM to centimeters and check that max_height_in_m is + # reduced by a factor 100 + uom_cm = self.env.ref("uom.product_uom_cm") + self.pallets_location_storage_type.storage_category_id.length_uom_id = uom_cm + self.assertEqual( + self.pallets_location_storage_type.storage_category_id.max_height_in_m, 1 + ) + + def test_archive_package_storage_type(self): + target = self.env.ref("stock_storage_type.package_storage_type_pallets") + all_package_storage_types = self.env["stock.package.type"].search([]) + self.assertIn(target, all_package_storage_types) + target.active = False + all_package_storage_types = self.env["stock.package.type"].search([]) + self.assertNotIn(target, all_package_storage_types) + + def test_package_message(self): + """ + Test for the message displayed on Stock Package Type forms + """ + pallets = self.env.ref("stock_storage_type.package_storage_type_pallets") + message = "When a package with storage type Pallets is put away, the " + message += "strategy will look for an allowed location in the " + message += "following locations:" + self.assertIn(message, pallets.storage_type_message) + + message = ( + "Pallets reserve storage area (WARNING: restrictions are active on " + "location storage types matching this package storage type)" + ) + + self.assertIn(message, pallets.storage_type_message) + + def test_sequence_to_location_menu(self): + action = self.location_sequence_pallet.button_show_locations() + self.assertIn( + ( + "computed_storage_capacity_ids", + "in", + self.location_sequence_pallet.package_type_id.storage_category_capacity_ids.ids, + ), + action["domain"], + ) + + def test_storage_capacity_display(self): + self.assertEqual( + self.cardboxes_stock.computed_storage_category_id.capacity_ids.display_name, + "Cardboxes x 1.0 (Package: Cardboxes - " + "Allow New Product: Allow mixed products)", + ) diff --git a/stock_storage_type/tests/test_storage_type_move.py b/stock_storage_type/tests/test_storage_type_move.py new file mode 100644 index 0000000000..68042a5d69 --- /dev/null +++ b/stock_storage_type/tests/test_storage_type_move.py @@ -0,0 +1,468 @@ +# Copyright 2020 Camptocamp SA +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl) +from odoo.exceptions import ValidationError +from odoo.tools.safe_eval import const_eval + +from .common import TestStorageTypeCommon + + +class TestStorageTypeMove(TestStorageTypeCommon): + @classmethod + def setUpClass(cls): + super().setUpClass() + cls.areas.write({"pack_putaway_strategy": "ordered_locations"}) + + def assert_package_level_domain(self, json_domain, expected_locations): + domain = const_eval(json_domain) + self.assertEqual(len(domain), 1) + self.assertEqual(domain[0][0], "id") + self.assertEqual(domain[0][1], "in") + self.assertEqual(sorted(domain[0][2]), sorted(expected_locations.ids)) + + def _test_confirmed_move(self, product=None): + confirmed_move = self._create_single_move(product or self.product) + confirmed_move.location_dest_id = self.pallets_bin_1_location.id + move_to_assign = self._create_single_move(self.product) + (confirmed_move | move_to_assign)._assign_picking() + package = self.env["stock.quant.package"].create( + # {"product_packaging_id": self.product_pallet_product_packaging.id} + {"package_type_id": self.pallet_pack_type.id} + ) + self._update_qty_in_location( + move_to_assign.location_id, + move_to_assign.product_id, + move_to_assign.product_qty, + package=package, + ) + move_to_assign._action_assign() + return move_to_assign + + def test_not_only_empty_confirmed_move(self): + self.pallets_location_storage_type.write({"allow_new_product": "mixed"}) + move = self._test_confirmed_move() + self.assertEqual( + move.move_line_ids.location_dest_id, self.pallets_bin_1_location + ) + + def test_only_empty_confirmed_move(self): + self.pallets_location_storage_type.write({"allow_new_product": "empty"}) + move = self._test_confirmed_move() + self.assertNotEqual( + move.move_line_ids.location_dest_id, self.pallets_bin_1_location + ) + + def test_do_not_mix_products_confirmed_move_ok(self): + self.pallets_location_storage_type.write({"allow_new_product": "same"}) + move = self._test_confirmed_move() + self.assertEqual( + move.move_line_ids.location_dest_id, self.pallets_bin_1_location + ) + + def test_do_not_mix_products_confirmed_move_nok(self): + self.pallets_location_storage_type.write({"allow_new_product": "same"}) + move_other_product = self._test_confirmed_move( + self.env.ref("product.product_product_10") + ) + self.assertNotEqual( + move_other_product.move_line_ids.location_dest_id, + self.pallets_bin_1_location, + ) + + def test_package_level_location_dest_domain_only_empty(self): + # Set pallets location type as only empty + self.pallets_location_storage_type.write({"allow_new_product": "empty"}) + # Create picking + in_picking = self.env["stock.picking"].create( + { + "picking_type_id": self.receipts_picking_type.id, + "location_id": self.suppliers_location.id, + "location_dest_id": self.input_location.id, + "move_ids": [ + ( + 0, + 0, + { + "name": self.product.name, + "location_id": self.suppliers_location.id, + "location_dest_id": self.input_location.id, + "product_id": self.product.id, + "product_uom_qty": 96.0, + "product_uom": self.product.uom_id.id, + }, + ) + ], + } + ) + # Mark as todo + in_picking.action_confirm() + # Put in pack + in_picking.move_line_ids.quantity = 48.0 + first_package = in_picking.action_put_in_pack() + # Ensure packaging is set properly on pack + first_package.product_packaging_id = self.product_pallet_product_packaging + # Put in pack again + ml_without_package = in_picking.move_line_ids.filtered( + lambda ml: not ml.result_package_id + ) + ml_without_package.quantity = 48.0 + second_pack = in_picking.action_put_in_pack() + # Ensure packaging is set properly on pack + second_pack.product_packaging_id = self.product_pallet_product_packaging + + # Validate picking + in_picking.button_validate() + # Assign internal picking + int_picking = in_picking.move_ids.move_dest_ids.picking_id + int_picking.action_assign() + self.assertEqual(int_picking.location_dest_id, self.stock_location) + self.assertEqual( + int_picking.move_ids.mapped("location_dest_id"), self.stock_location + ) + # First move line goes into pallets bin 1 + # Second move line goes into pallets bin 2 + self.assertEqual( + int_picking.move_line_ids.mapped("location_dest_id"), + self.pallets_bin_1_location | self.pallets_bin_2_location, + ) + self.assertEqual( + int_picking.package_level_ids.mapped("location_dest_id"), + self.pallets_bin_1_location | self.pallets_bin_2_location, + ) + package_type_locations = int_picking.package_level_ids.mapped( + "package_id.package_type_id." "storage_location_sequence_ids.location_id" + ) + possible_locations = self.env["stock.location"].search( + [ + ( + "computed_storage_category_id.capacity_ids", + "in", + int_picking.package_level_ids.mapped( + "package_id.package_type_id.storage_category_capacity_ids" + ).ids, + ), + ("location_id", "child_of", int_picking.location_dest_id.id), + ("id", "in", package_type_locations.mapped("leaf_location_ids").ids), + ] + ) + only_empty_possible_locations = possible_locations.filtered( + lambda location: not location.quant_ids + ) + + for level in int_picking.package_level_ids: + self.assertEqual( + level.allowed_location_dest_ids, + only_empty_possible_locations + - ( + # remove the destination of other levels but keep the current one + int_picking.package_level_ids.mapped("location_dest_id") + ) + | level.location_dest_id, + ) + + # Update qty in a bin to ensure it's not in possible locations anymore + self.env["stock.quant"]._update_available_quantity( + self.product, self.pallets_bin_3_location, 1.0 + ) + only_empty_possible_locations_2 = possible_locations.filtered( + lambda location: not location.quant_ids + ) + self.assertEqual( + only_empty_possible_locations, + only_empty_possible_locations_2 | self.pallets_bin_3_location, + ) + + for level in int_picking.package_level_ids: + self.assertEqual( + level.allowed_location_dest_ids, + only_empty_possible_locations_2 + - ( + # remove the destination of other levels but keep the current one + int_picking.package_level_ids.mapped("location_dest_id") + ) + | level.location_dest_id, + ) + + # Creating a new possible location must be reflected in domain + pallets_bin_4_location = self.env["stock.location"].create( + {"name": "Pallets bin 4", "location_id": self.pallets_location.id} + ) + + for level in int_picking.package_level_ids: + self.assertEqual( + level.allowed_location_dest_ids, + (only_empty_possible_locations_2 | pallets_bin_4_location) + - ( + # remove the destination of other levels but keep the current one + int_picking.package_level_ids.mapped("location_dest_id") + ) + | level.location_dest_id, + ) + + def test_package_level_location_dest_domain_mixed(self): + # Mark picking to allow creation and use of existing lots in order + # to register two times the same lot in different packages + self.receipts_picking_type.use_existing_lots = True + self.cardboxes_location_storage_type.write({"allow_new_product": "same_lot"}) + # Create picking + in_picking = self.env["stock.picking"].create( + { + "picking_type_id": self.receipts_picking_type.id, + "location_id": self.suppliers_location.id, + "location_dest_id": self.input_location.id, + "move_ids": [ + ( + 0, + 0, + { + "name": self.product.name, + "location_id": self.suppliers_location.id, + "location_dest_id": self.input_location.id, + "product_id": self.product.id, + "product_uom_qty": 52.0, + "product_uom": self.product.uom_id.id, + }, + ), + ( + 0, + 0, + { + "name": self.product_lot.name, + "location_id": self.suppliers_location.id, + "location_dest_id": self.input_location.id, + "product_id": self.product_lot.id, + "product_uom_qty": 15.0, + "product_uom": self.product.uom_id.id, + }, + ), + ], + } + ) + # Mark as todo + in_picking.action_confirm() + # Put in pack + in_picking.move_line_ids.filtered( + lambda ml: ml.product_id == self.product + ).quantity = 48.0 + first_package = in_picking.action_put_in_pack() + # Ensure packaging is set properly on pack + first_package.product_packaging_id = self.product_pallet_product_packaging + # Put in pack again + product_ml_without_package = in_picking.move_line_ids.filtered( + lambda ml: ml.product_id == self.product and not ml.result_package_id + ) + product_ml_without_package.quantity = 4.0 + second_pack = in_picking.action_put_in_pack() + # Ensure packaging is set properly on pack + second_pack.product_packaging_id = self.product_cardbox_product_packaging + # Create lots to be used on move lines + lot_a0001 = self.env["stock.lot"].create( + { + "name": "A0001", + "product_id": self.product_lot.id, + "company_id": self.env.user.company_id.id, + } + ) + lot_a0002 = self.env["stock.lot"].create( + { + "name": "A0002", + "product_id": self.product_lot.id, + "company_id": self.env.user.company_id.id, + } + ) + # Put in pack lot product + in_picking.move_line_ids.filtered( + lambda ml: ml.product_id == self.product_lot + ).write({"quantity": 5.0, "lot_id": lot_a0001.id}) + third_pack = in_picking.action_put_in_pack() + # Ensure packaging is set properly on pack + third_pack.product_packaging_id = self.product_lot_cardbox_product_packaging + # Put in pack lot product again + product_lot_ml_without_package = in_picking.move_line_ids.filtered( + lambda ml: ml.product_id == self.product_lot and not ml.result_package_id + ) + product_lot_ml_without_package.write({"quantity": 5.0, "lot_id": lot_a0002.id}) + fourth_pack = in_picking.action_put_in_pack() + # Ensure packaging is set properly on pack + fourth_pack.product_packaging_id = self.product_lot_cardbox_product_packaging + # Put in pack lot product again ... again (to have two times same lot) + product_lot_ml_without_package = in_picking.move_line_ids.filtered( + lambda ml: ml.product_id == self.product_lot and not ml.result_package_id + ) + product_lot_ml_without_package.write({"quantity": 5.0, "lot_id": lot_a0002.id}) + fifth_pack = in_picking.action_put_in_pack() + # Ensure packaging is set properly on pack + fifth_pack.product_packaging_id = self.product_lot_cardbox_product_packaging + # Validate picking + in_picking.button_validate() + # Assign internal picking + int_picking = in_picking.move_ids.move_dest_ids.picking_id + int_picking.action_assign() + + def _get_possible_locations(package_level): + storage_type = package_level.package_id.package_type_id + package_type_locations = storage_type.storage_location_sequence_ids.mapped( + "location_id.leaf_location_ids" + ) + possible_locations = self.env["stock.location"].search( + [ + ( + "computed_storage_category_id.capacity_ids", + "in", + storage_type.storage_category_capacity_ids.ids, + ), + ( + "location_id", + "child_of", + package_level.picking_id.location_dest_id.id, + ), + ("id", "in", package_type_locations.ids), + ] + ) + return ( + possible_locations + - package_level.picking_id.package_level_ids.mapped("location_dest_id") + | pack_level.location_dest_id + ) + + def _levels_for(packages): + return self.env["stock.package_level"].search( + [ + ("package_id", "in", packages.ids), + ("picking_id", "=", int_picking.id), + ] + ) + + first_level = _levels_for(first_package) + self.assertEqual(len(first_level), 1) + # Pallet into pallets bin + self.assertEqual(first_level.location_dest_id, self.pallets_bin_1_location) + + second_level = _levels_for(second_pack) + # Cardbox into cardbox bin + self.assertEqual(len(second_level), 1) + self.assertEqual(second_level.location_dest_id, self.cardboxes_bin_1_location) + + third_level = _levels_for(third_pack) + + # Cardbox with different product go into different cardbox location + self.assertEqual(len(third_level), 1) + self.assertEqual(third_level.location_dest_id, self.cardboxes_bin_3_location) + + fourth_fifth_levels = _levels_for(fourth_pack | fifth_pack) + # Cardbox with same product but different lot go into different + # cardbox location + # Cardbox with same product same lot go into same location + self.assertEqual(len(fourth_fifth_levels), 2) + self.assertEqual( + fourth_fifth_levels.location_dest_id, self.cardboxes_bin_2_location + ) + + for pack_level in ( + first_level | second_level | third_level | fourth_fifth_levels + ): + # Check domain + self.assertEqual( + pack_level.allowed_location_dest_ids, + _get_possible_locations(pack_level), + ) + + # Set the quantities done in order to avoid immediate transfer wizard + for move_line in pack_level.move_line_ids: + move_line.quantity = move_line.reserved_qty + + second_level.location_dest_id = third_level.location_dest_id + with self.assertRaises(ValidationError): + int_picking.button_validate() + + def test_stock_move_no_package(self): + """ + Create a stock move for a product with lot restriction + Don't put it in a package + Check that lot restriction is well applied + """ + # Constrain Cardbox Capacity to accept same lots only + self.cardboxes_location_storage_type.write({"allow_new_product": "same_lot"}) + # Set a quantity in cardbox bin 2 to make sure constraint is applied + self.env["stock.quant"]._update_available_quantity( + self.env.ref("product.product_product_10"), + self.cardboxes_bin_2_location, + 1.0, + ) + + # As we don't put in pack in this flow, we need to set a default + # package type on the product level in order to get the specialized putaway + # to be applied + self.product_lot.package_type_id = self.cardboxes_package_storage_type + + # Create picking + in_picking = self.env["stock.picking"].create( + { + "picking_type_id": self.receipts_picking_type.id, + "location_id": self.suppliers_location.id, + "location_dest_id": self.input_location.id, + "move_ids": [ + ( + 0, + 0, + { + "name": self.product.name, + "location_id": self.suppliers_location.id, + "location_dest_id": self.input_location.id, + "product_id": self.product.id, + "product_uom_qty": 8.0, + "product_uom": self.product.uom_id.id, + "picking_type_id": self.receipts_picking_type.id, + }, + ), + ( + 0, + 0, + { + "name": self.product_lot.name, + "location_id": self.suppliers_location.id, + "location_dest_id": self.input_location.id, + "product_id": self.product_lot.id, + "product_uom_qty": 10.0, + "product_uom": self.product_lot.uom_id.id, + "picking_type_id": self.receipts_picking_type.id, + }, + ), + ], + } + ) + # Mark as todo + in_picking.action_confirm() + + # Fill in the lots during the incoming process + product_lot_ml = in_picking.move_line_ids.filtered( + lambda ml: ml.product_id == self.product_lot + ) + product_lot_ml.write({"quantity": 5.0, "lot_name": "A0001"}) + product_lot_ml.copy({"quantity": 3.0, "lot_name": "A0002"}) + + product_ml = in_picking.move_line_ids.filtered( + lambda ml: ml.product_id == self.product + ) + + product_ml.write({"quantity": 8.0}) + + in_picking._action_done() + + int_picking = in_picking.move_ids.mapped("move_dest_ids.picking_id") + + lot_lines = int_picking.move_line_ids.filtered( + lambda line: line.product_id == self.product_lot + ) + destination_ids = lot_lines.mapped("location_dest_id.id") + # Check if the destinations are all different + self.assertEqual( + len(set(destination_ids)), + len(destination_ids), + ) + + lot_ids = lot_lines.mapped("lot_id.id") + # Check if the lots are all different + self.assertEqual( + len(set(lot_ids)), + len(lot_ids), + ) diff --git a/stock_storage_type/tests/test_storage_type_putaway_strategy.py b/stock_storage_type/tests/test_storage_type_putaway_strategy.py new file mode 100644 index 0000000000..78a4271cb6 --- /dev/null +++ b/stock_storage_type/tests/test_storage_type_putaway_strategy.py @@ -0,0 +1,814 @@ +# Copyright 2019 Camptocamp SA +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl) +from .common import TestStorageTypeCommon + + +class TestPutawayStorageTypeStrategy(TestStorageTypeCommon): + @classmethod + def setUpClass(cls): + super().setUpClass() + cls.areas.write({"pack_putaway_strategy": "ordered_locations"}) + + def test_storage_strategy_ordered_locations_cardboxes(self): + # Create picking + in_picking = self.env["stock.picking"].create( + { + "picking_type_id": self.receipts_picking_type.id, + "location_id": self.suppliers_location.id, + "location_dest_id": self.input_location.id, + "move_ids": [ + ( + 0, + 0, + { + "name": self.product.name, + "location_id": self.suppliers_location.id, + "location_dest_id": self.input_location.id, + "product_id": self.product.id, + "product_uom_qty": 8.0, + "product_uom": self.product.uom_id.id, + }, + ) + ], + } + ) + # Mark as todo + in_picking.action_confirm() + # Put in pack + in_picking.move_line_ids.quantity = 4.0 + first_package = in_picking.action_put_in_pack() + # Ensure packaging is set properly on pack + first_package.product_packaging_id = self.product_cardbox_product_packaging + # Put in pack again + ml_without_package = in_picking.move_line_ids.filtered( + lambda ml: not ml.result_package_id + ) + ml_without_package.quantity = 4.0 + second_pack = in_picking.action_put_in_pack() + # Ensure packaging is set properly on pack + second_pack.product_packaging_id = self.product_cardbox_product_packaging + + # Validate picking + in_picking.button_validate() + # Assign internal picking + int_picking = in_picking.move_ids.move_dest_ids.picking_id + int_picking.action_assign() # TODO drop ? + self.assertEqual(int_picking.location_dest_id, self.stock_location) + self.assertEqual( + int_picking.move_ids.mapped("location_dest_id"), self.stock_location + ) + self.assertEqual( + int_picking.move_line_ids.mapped("location_dest_id"), + self.cardboxes_bin_1_location, + ) + # Archive all leaf locations. Ensure that the intermediate location is + # not returned as a valid leaf location and that the next location in + # the sequence (reserve) is selected + int_picking.do_unreserve() + cardboxes_stock = self.env.ref("stock_storage_type.stock_location_cardboxes") + cardboxes_stock.child_ids.write({"active": False}) + int_picking.action_assign() + self.assertEqual(int_picking.location_dest_id, self.stock_location) + self.assertEqual( + int_picking.move_ids.mapped("location_dest_id"), self.stock_location + ) + reserve_cardbox = self.env.ref( + "stock_storage_type.stock_location_cardboxes_reserve_bin_1" + ) + self.assertEqual( + int_picking.move_line_ids.mapped("location_dest_id"), reserve_cardbox + ) + + def test_storage_strategy_only_empty_ordered_locations_pallets(self): + # Set pallets location type as only empty + self.pallets_location_storage_type.write({"allow_new_product": "empty"}) + # Set a quantity in pallet bin 2 to make sure constraint is applied + self.env["stock.quant"]._update_available_quantity( + self.product, self.pallets_bin_2_location, 1.0 + ) + # Create picking + in_picking = self.env["stock.picking"].create( + { + "picking_type_id": self.receipts_picking_type.id, + "location_id": self.suppliers_location.id, + "location_dest_id": self.input_location.id, + "move_ids": [ + ( + 0, + 0, + { + "name": self.product.name, + "location_id": self.suppliers_location.id, + "location_dest_id": self.input_location.id, + "product_id": self.product.id, + "product_uom_qty": 96.0, + "product_uom": self.product.uom_id.id, + }, + ) + ], + } + ) + # Mark as todo + in_picking.action_confirm() + # Put in pack + in_picking.move_line_ids.quantity = 48.0 + first_package = in_picking.action_put_in_pack() + # Ensure packaging is set properly on pack + first_package.product_packaging_id = self.product_pallet_product_packaging + # Put in pack again + ml_without_package = in_picking.move_line_ids.filtered( + lambda ml: not ml.result_package_id + ) + ml_without_package.quantity = 48.0 + second_pack = in_picking.action_put_in_pack() + # Ensure packaging is set properly on pack + second_pack.product_packaging_id = self.product_pallet_product_packaging + + # Validate picking + in_picking.button_validate() + # Assign internal picking + int_picking = in_picking.move_ids.move_dest_ids.picking_id + int_picking.action_assign() + self.assertEqual(int_picking.location_dest_id, self.stock_location) + self.assertEqual( + int_picking.move_ids.mapped("location_dest_id"), self.stock_location + ) + # First move line goes into pallets bin 1 + # Second move line goes into pallets bin 3 as bin 1 is planned for + # first move line and bin 2 is already used + self.assertEqual( + int_picking.move_line_ids.mapped("location_dest_id"), + self.pallets_bin_1_location | self.pallets_bin_3_location, + ) + + def test_storage_strategy_max_weight_ordered_locations_pallets(self): + # Add a category for max_weight 50 + category_50 = self.env["stock.storage.category"].create( + {"name": "Pallets max 50 kg", "max_weight": 50} + ) + + # Define new pallets location type with a max weight on bin 2 + light_location_storage_type = self.pallets_location_storage_type.copy( + {"allow_new_product": "empty", "storage_category_id": category_50.id} + ) + self.pallets_bin_2_location.write({"storage_category_id": category_50.id}) + self.assertEqual( + self.pallets_bin_2_location.storage_category_id.capacity_ids, + light_location_storage_type, + ) + # Create picking + in_picking = self.env["stock.picking"].create( + { + "picking_type_id": self.receipts_picking_type.id, + "location_id": self.suppliers_location.id, + "location_dest_id": self.input_location.id, + "move_ids": [ + ( + 0, + 0, + { + "name": self.product.name, + "location_id": self.suppliers_location.id, + "location_dest_id": self.input_location.id, + "product_id": self.product.id, + "product_uom_qty": 96.0, + "product_uom": self.product.uom_id.id, + }, + ) + ], + } + ) + # Mark as todo + in_picking.action_confirm() + # Put in pack + in_picking.move_line_ids.quantity = 48.0 + first_package = in_picking.action_put_in_pack() + # Ensure packaging is set properly on pack + first_package.product_packaging_id = self.product_pallet_product_packaging + first_package.onchange_product_packaging_id() + self.assertEqual(first_package.pack_weight, 60) + # Put in pack again + ml_without_package = in_picking.move_line_ids.filtered( + lambda ml: not ml.result_package_id + ) + ml_without_package.quantity = 48.0 + second_pack = in_picking.action_put_in_pack() + # Ensure packaging is set properly on pack + second_pack.product_packaging_id = self.product_pallet_product_packaging + second_pack.onchange_product_packaging_id() + self.assertEqual(second_pack.pack_weight, 60) + # Validate picking + in_picking.button_validate() + # Assign internal picking + int_picking = in_picking.move_ids.move_dest_ids.picking_id + int_picking.action_assign() + self.assertEqual(int_picking.location_dest_id, self.stock_location) + self.assertEqual( + int_picking.move_ids.mapped("location_dest_id"), self.stock_location + ) + # First move line goes into pallets bin 1 + # Second move line goes into pallets bin 3 as bin 1 is planned for + # first move line and bin 2 is already used + self.assertEqual( + int_picking.move_line_ids.mapped("location_dest_id"), + self.pallets_bin_1_location | self.pallets_bin_3_location, + ) + + def test_storage_strategy_no_products_lots_mix_ordered_locations_cardboxes(self): + self.cardboxes_location_storage_type.write({"allow_new_product": "same_lot"}) + # Set a quantity in cardbox bin 2 to make sure constraint is applied + self.env["stock.quant"]._update_available_quantity( + self.env.ref("product.product_product_10"), + self.cardboxes_bin_2_location, + 1.0, + ) + # Create picking + in_picking = self.env["stock.picking"].create( + { + "picking_type_id": self.receipts_picking_type.id, + "location_id": self.suppliers_location.id, + "location_dest_id": self.input_location.id, + "move_ids": [ + ( + 0, + 0, + { + "name": self.product.name, + "location_id": self.suppliers_location.id, + "location_dest_id": self.input_location.id, + "product_id": self.product.id, + "product_uom_qty": 8.0, + "product_uom": self.product.uom_id.id, + "picking_type_id": self.receipts_picking_type.id, + }, + ), + ( + 0, + 0, + { + "name": self.product_lot.name, + "location_id": self.suppliers_location.id, + "location_dest_id": self.input_location.id, + "product_id": self.product_lot.id, + "product_uom_qty": 10.0, + "product_uom": self.product_lot.uom_id.id, + "picking_type_id": self.receipts_picking_type.id, + }, + ), + ], + } + ) + # Mark as todo + in_picking.action_confirm() + # Put in pack product + in_picking.move_line_ids.filtered( + lambda ml: ml.product_id == self.product + ).quantity = 4.0 + product_first_package = in_picking.action_put_in_pack() + # Ensure packaging is set properly on pack + product_first_package.product_packaging_id = ( + self.product_cardbox_product_packaging + ) + # Put in pack product again + product_ml_without_package = in_picking.move_line_ids.filtered( + lambda ml: not ml.result_package_id and ml.product_id == self.product + ) + product_ml_without_package.quantity = 4.0 + product_second_pack = in_picking.action_put_in_pack() + # Ensure packaging is set properly on pack + product_second_pack.product_packaging_id = ( + self.product_cardbox_product_packaging + ) + + # Put in pack product lot + product_lot_ml = in_picking.move_line_ids.filtered( + lambda ml: not ml.result_package_id and ml.product_id == self.product_lot + ) + product_lot_ml.write({"quantity": 5.0, "lot_name": "A0001"}) + product_lot_first_pack = in_picking.action_put_in_pack() + # Ensure packaging is set properly on pack + product_lot_first_pack.product_packaging_id = ( + self.product_lot_cardbox_product_packaging + ) + # Put in pack product lot again + product_lot_ml_without_package = in_picking.move_line_ids.filtered( + lambda ml: not ml.result_package_id and ml.product_id == self.product_lot + ) + product_lot_ml_without_package.write({"quantity": 5.0, "lot_name": "A0002"}) + product_lot_second_pack = in_picking.action_put_in_pack() + # Ensure packaging is set properly on pack + product_lot_second_pack.product_packaging_id = ( + self.product_lot_cardbox_product_packaging + ) + + # Validate picking + in_picking.button_validate() + # Assign internal picking + int_picking = in_picking.move_ids.mapped("move_dest_ids.picking_id") + int_picking.action_assign() + self.assertEqual(int_picking.location_dest_id, self.stock_location) + self.assertEqual( + int_picking.move_ids.mapped("location_dest_id"), self.stock_location + ) + product_mls = int_picking.move_line_ids.filtered( + lambda ml: ml.product_id == self.product + ) + self.assertEqual( + product_mls.mapped("location_dest_id"), self.cardboxes_bin_1_location + ) + product_lot_mls = int_picking.move_line_ids.filtered( + lambda ml: ml.product_id == self.product_lot + ) + self.assertEqual( + product_lot_mls.mapped("location_dest_id"), + self.cardboxes_bin_3_location | self.cardboxes_bin_4_location, + ) + + def test_storage_strategy_none_in_sequence(self): + """When a location has a strategy 'none' in sequence, stop + + We can use it to stop computing the put-away when the destination + location match, for instance to use a setup with a sequence: + + 1. Stock: None + 2. Stock/Cardboxes Reserve: ordered locations + + If a move is created with destination 'Cardboxes Reserve', the put-away + rule stops at the rule 1. so the move stays in 'Cardboxes Reserve. + Then, the destination is changed to 'Stock/Cardboxes Reserve' and + a recomputation is done, the put-away for Bin 1 is applied. + + """ + move = self._create_single_move(self.product) + # move.location_dest_id = self.cardboxes_location.id + move._assign_picking() + package = self.env["stock.quant.package"].create( + {"product_packaging_id": self.product_lot_cardbox_product_packaging.id} + ) + self._update_qty_in_location( + move.location_id, move.product_id, move.product_qty, package=package + ) + + # configure a new sequence with none in the parent location + self.cardboxes_package_storage_type.storage_location_sequence_ids.unlink() + self.warehouse.lot_stock_id.pack_putaway_strategy = "none" + self.warehouse.lot_stock_id.storage_category_id = ( + self.cardboxes_location_storage_type.storage_category_id + ) + self.env["stock.storage.location.sequence"].create( + { + "package_type_id": self.cardboxes_package_storage_type.id, + "location_id": self.warehouse.lot_stock_id.id, + "sequence": 1, + } + ) + self.env["stock.storage.location.sequence"].create( + { + "package_type_id": self.cardboxes_package_storage_type.id, + "location_id": self.cardboxes_location.id, + "sequence": 2, + } + ) + + move._action_assign() + move_line = move.move_line_ids + package_level = move_line.package_level_id + + self.assertEqual( + package_level.location_dest_id, + self.warehouse.lot_stock_id, + "the move line's destination must stay in Stock as we have" + " a 'none' strategy on it and it is in the sequence", + ) + + package_level.location_dest_id = self.cardboxes_location + # if we reapply the strategy, it should now apply the ordered + # location of the cardbox location + package_level.recompute_pack_putaway() + + self.assertTrue( + package_level.location_dest_id in self.cardboxes_location.child_ids + ) + + def test_storage_strategy_do_not_mix_products_reuse_location(self): + """Location with restriction 'do_not_mix_products' should have priority + + When locations are configured with 'do_not_mix_products' the strategy + must give priority to location that already contains the product + (less qty first). + """ + StockLocation = self.env["stock.location"] + self.cardboxes_location_storage_type.write({"allow_new_product": "same"}) + product = self.product + packaging = self.product_cardbox_product_packaging + dest_location = self.cardboxes_location + package = self.env["stock.quant.package"].create( + {"name": "TEST1", "product_packaging_id": packaging.id} + ) + self.env["stock.quant"].create( + { + "product_id": product.id, + "package_id": package.id, + "location_id": self.input_location.id, + } + ) + + location = StockLocation._get_package_type_putaway_strategy( + dest_location, package, product, 1.0 + ) + + # No location with given product -> the first bin should be returned + self.assertEqual(location, self.cardboxes_bin_1_location) + + # Set a quantity in cardbox bin 4 to trigger the priority on the + # location that already contains the product + self.env["stock.quant"]._update_available_quantity( + product, + self.cardboxes_bin_3_location, + 10.0, + ) + location = StockLocation._get_package_type_putaway_strategy( + dest_location, package, product, 1.0 + ) + self.assertEqual(location, self.cardboxes_bin_3_location) + + # Set less quantity on bin 4. Since it's the location with less quantity + # that should have priority + self.env["stock.quant"]._update_available_quantity( + product, + self.cardboxes_bin_4_location, + 1.0, + ) + location = StockLocation._get_package_type_putaway_strategy( + dest_location, package, product, 1.0 + ) + self.assertEqual(location, self.cardboxes_bin_4_location) + + def test_storage_strategy_none_in_sequence_to_fixes(self): + """When a location has a strategy 'none' in sequence, stop + + We can use it to stop computing the put-away when the destination + location match, In such a case, if the location match and a putaway + rule is defined on the product for this destination location, + the location destination will be the location from the putaway rule. + + This is very usefull to support fixed location putaway + + Ex: + product putaway: + * in: Cardboxes + * out: cardboxes_bin_4_location + + sequence: + 1. Stock/Cardboxes: None + + If a move is created with destination "Stock", the put-away rule stops + at sequence 1. Since a put away exists on the product for 'Cardboxes', + the product putaway is applied and the final destination is + 'cardboxes_bin_4_location' + + """ + move = self._create_single_move(self.product) + move._assign_picking() + self.assertEqual(move.location_dest_id, self.stock_location) + package = self.env["stock.quant.package"].create( + {"product_packaging_id": self.product_lot_cardbox_product_packaging.id} + ) + self._update_qty_in_location( + move.location_id, move.product_id, move.product_qty, package=package + ) + + # configure a new sequence with none in the parent location + self.cardboxes_package_storage_type.storage_location_sequence_ids.unlink() + self.cardboxes_location.pack_putaway_strategy = "none" + self.env["stock.storage.location.sequence"].create( + { + "package_type_id": self.cardboxes_package_storage_type.id, + "location_id": self.cardboxes_location.id, + "sequence": 1, + } + ) + + # create a put away rule on the product from cardboxes to bin 4 + self.env["stock.putaway.rule"].create( + { + "location_in_id": self.cardboxes_location.id, + "location_out_id": self.cardboxes_bin_4_location.id, + "product_id": self.product.id, + } + ) + + move._action_assign() + move_line = move.move_line_ids + package_level = move_line.package_level_id + + self.assertEqual( + package_level.location_dest_id, + self.cardboxes_bin_4_location, + ) + + def test_storage_strategy_sequence_condition(self): + """If a condition is not met on storage location sequence, it's ignored""" + move = self._create_single_move(self.product) + move2 = self._create_single_move(self.product2) + (move | move2)._assign_picking() + original_location_dest = move.location_dest_id + package = self.env["stock.quant.package"].create( + {"product_packaging_id": self.product_lot_cardbox_product_packaging.id} + ) + self._update_qty_in_location( + move.location_id, move.product_id, move.product_qty, package=package + ) + self._update_qty_in_location( + move2.location_id, move2.product_id, move2.product_qty, package=package + ) + + # configure a new sequence with none in the parent location + self.cardboxes_package_storage_type.storage_location_sequence_ids.unlink() + self.warehouse.lot_stock_id.pack_putaway_strategy = "none" + self.warehouse.lot_stock_id.storage_category_id = ( + self.cardboxes_location_storage_type.storage_category_id + ) + condition = self.env["stock.storage.location.sequence.cond"].create( + {"name": "Always False", "code_snippet": "result = False"} + ) + self.none_sequence = self.env["stock.storage.location.sequence"].create( + { + "package_type_id": self.cardboxes_package_storage_type.id, + "location_id": self.warehouse.lot_stock_id.id, + "sequence": 1, + "location_sequence_cond_ids": [(6, 0, condition.ids)], + } + ) + self.env["stock.storage.location.sequence"].create( + { + "package_type_id": self.cardboxes_package_storage_type.id, + "location_id": self.cardboxes_location.id, + "sequence": 2, + } + ) + + (move | move2)._action_assign() + move_line = move.move_line_ids + package_level = move_line.package_level_id + + self.assertIn( + package_level.location_dest_id, + self.cardboxes_location.child_ids, + "the move line's destination must go into the cardbox location" + " since the the first sequence is ignored due to the False" + " condition on it", + ) + + # if we update the condition to always be True, reset the + # location_dest on the package_level and reapply the put away strategy + # the move line's destination must be in Stock as we have a 'none' + # strategy the first putaway sequence + condition.code_snippet = "result = True" + package_level.location_dest_id = original_location_dest.id + package_level.recompute_pack_putaway() + + self.assertEqual( + package_level.location_dest_id, + self.warehouse.lot_stock_id, + "the move line's destination must stay in Stock as we have" + " a 'none' strategy on it and it is in the sequence", + ) + + package_level.location_dest_id = self.cardboxes_location + # if we reapply the strategy, it should now apply the ordered + # location of the cardbox location + package_level.recompute_pack_putaway() + + self.assertTrue( + package_level.location_dest_id in self.cardboxes_location.child_ids + ) + + def test_default_product_package_type(self): + # We try to move a product that is not contained in a package + # but has a default package type + # Destination location should become Pallet location + move = self._create_single_move(self.product) + move._assign_picking() + original_location_dest = move.location_dest_id + # Change default product package type + move.product_id.package_type_id = self.pallets_package_storage_type + self._update_qty_in_location( + move.location_id, move.product_id, move.product_qty + ) + move._action_assign() + move_line = move.move_line_ids + + self.assertEqual(move_line.location_dest_id, self.pallets_bin_1_location) + + self.assertNotEqual( + original_location_dest, + move_line.location_dest_id, + ) + + def test_storage_strategy_ordered_locations_cardboxes_with_new_leaf_putaway(self): + """ + In this scenario, we check that a storage strategy is well applied + but, then, check that a standard putaway rule has been applied too. + + Storage rule applied: for Cardboxes + Putaway rule: From Carboxes bin location 1 to Cardbox leaf 1 + + Location Structure: + + Stock + -- Cardbox Bin + ----- Cardbox leaf + """ + + # Create the fixed location + self.fix_location = self.env["stock.location"].create( + { + "name": "Cardbox 1 Fixed", + "location_id": self.cardboxes_bin_1_location.id, + } + ) + + # Create the putaway rule + self.env["stock.putaway.rule"].create( + { + "product_id": self.product.id, + "location_in_id": self.cardboxes_bin_1_location.id, + "location_out_id": self.fix_location.id, + } + ) + self.cardboxes_bin_1_location.pack_putaway_strategy = "none" + + # Create the sequence for Bin 1 + + self.env["stock.storage.location.sequence"].create( + { + "package_type_id": self.cardboxes_package_storage_type.id, + "location_id": self.cardboxes_bin_1_location.id, + "sequence": 1, + } + ) + + # Create picking + in_picking = self.env["stock.picking"].create( + { + "picking_type_id": self.receipts_picking_type.id, + "location_id": self.suppliers_location.id, + "location_dest_id": self.input_location.id, + "move_ids": [ + ( + 0, + 0, + { + "name": self.product.name, + "location_id": self.suppliers_location.id, + "location_dest_id": self.input_location.id, + "product_id": self.product.id, + "product_uom_qty": 8.0, + "product_uom": self.product.uom_id.id, + }, + ) + ], + } + ) + # Mark as todo + in_picking.action_confirm() + # Put in pack + in_picking.move_line_ids.quantity = 4.0 + first_package = in_picking.action_put_in_pack() + # Ensure packaging is set properly on pack + first_package.product_packaging_id = self.product_cardbox_product_packaging + # Put in pack again + ml_without_package = in_picking.move_line_ids.filtered( + lambda ml: not ml.result_package_id + ) + ml_without_package.quantity = 4.0 + second_pack = in_picking.action_put_in_pack() + # Ensure packaging is set properly on pack + second_pack.product_packaging_id = self.product_cardbox_product_packaging + + # Validate picking + in_picking.button_validate() + # Assign internal picking + int_picking = in_picking.move_ids.move_dest_ids.picking_id + int_picking.action_assign() # TODO drop ? + self.assertEqual(int_picking.location_dest_id, self.stock_location) + self.assertEqual( + int_picking.move_ids.mapped("location_dest_id"), self.stock_location + ) + self.assertEqual( + int_picking.move_line_ids.mapped("location_dest_id"), + self.fix_location, + ) + + def test_storage_strategy_with_view(self): + """ + Create a new locations structure: + - Stock + - Food (View) + - Food A (View) + - Pallet 1 + - Pallet 2 + - Food B (View) + - Cardbox 1 + - Cardbox 2 + + A storage sequence strategy is set on Food (View) to + go to Cardboxes (Food B) for package type cardboxes + + A putaway rule is set on Food B to go to Cardbox2 for + product + + Check the product goes well to Cardbox2 + """ + self.food_view = self.env["stock.location"].create( + { + "name": "Food View", + "location_id": self.warehouse.lot_stock_id.id, + "usage": "view", + } + ) + + self.food_pallets = self.env["stock.location"].create( + { + "name": "Food A", + "location_id": self.food_view.id, + "usage": "view", + } + ) + + self.food_pallet_1 = self.env["stock.location"].create( + { + "name": "Food Pallet 1", + "location_id": self.food_pallets.id, + } + ) + self.food_pallet_2 = self.env["stock.location"].create( + { + "name": "Food Pallet 2", + "location_id": self.food_pallets.id, + } + ) + + self.food_cardboxes = self.env["stock.location"].create( + { + "name": "Food B", + "location_id": self.food_view.id, + "storage_category_id": self.env.ref( + "stock_storage_type.storage_category_cardboxes" + ).id, + "usage": "view", + } + ) + + self.food_cardbox_1 = self.env["stock.location"].create( + { + "name": "Food Cardbox 1", + "location_id": self.food_cardboxes.id, + } + ) + self.food_cardbox_2 = self.env["stock.location"].create( + { + "name": "Food Cardbox 2", + "location_id": self.food_cardboxes.id, + } + ) + + self.env["stock.putaway.rule"].create( + { + "product_id": self.product.id, + "location_in_id": self.food_cardboxes.id, + "location_out_id": self.food_cardbox_2.id, + } + ) + + self.food_view.pack_putaway_strategy = "none" + + self.env["stock.storage.location.sequence"].create( + { + "package_type_id": self.cardboxes_package_storage_type.id, + "location_id": self.food_cardboxes.id, + "sequence": 1, + } + ) + + move = self._create_single_move(self.product) + move.location_dest_id = self.food_view + move._assign_picking() + package = self.env["stock.quant.package"].create( + {"product_packaging_id": self.product_lot_cardbox_product_packaging.id} + ) + self._update_qty_in_location( + move.location_id, move.product_id, move.product_qty, package=package + ) + + move._action_assign() + move_line = move.move_line_ids + package_level = move_line.package_level_id + + self.assertEqual( + package_level.location_dest_id, + self.food_cardbox_2, + "the move line's destination must stay in Stock as we have" + " a 'none' strategy on it and it is in the sequence", + ) diff --git a/stock_storage_type/views/product_template.xml b/stock_storage_type/views/product_template.xml new file mode 100644 index 0000000000..fdc622c527 --- /dev/null +++ b/stock_storage_type/views/product_template.xml @@ -0,0 +1,19 @@ + + + + product.template_procurement.inherit + product.template + + + + + + + + diff --git a/stock_storage_type/views/stock_location.xml b/stock_storage_type/views/stock_location.xml new file mode 100644 index 0000000000..2bcb3ba94c --- /dev/null +++ b/stock_storage_type/views/stock_location.xml @@ -0,0 +1,33 @@ + + + + stock.location.form.inherit (in stock_storage_type) + stock.location + + + + + + + + + + + + + diff --git a/stock_storage_type/views/stock_package_level.xml b/stock_storage_type/views/stock_package_level.xml new file mode 100644 index 0000000000..aad44314e1 --- /dev/null +++ b/stock_storage_type/views/stock_package_level.xml @@ -0,0 +1,43 @@ + + + + Package Level Inherit (in stock_storage_type) + stock.package_level + + + + + + + [("id", "in", allowed_location_dest_ids)] + + + + + Package Level Tree Picking Inherit (in stock_storage_type) + stock.package_level + + + + + + + [("id", "in", allowed_location_dest_ids)] + + +