From 4b8fadba6a4283410b9c2e5d2957f54f91a19b6b Mon Sep 17 00:00:00 2001 From: Denis Roussel Date: Thu, 22 May 2025 11:20:56 +0200 Subject: [PATCH 01/12] [IMP] stock_storage_type: Allow to re-apply putaway rules on computed location for a selected move line --- .../models/stock_storage_category.py | 35 ++++++++++++++++++- .../test_storage_type_putaway_strategy.py | 7 ++++ 2 files changed, 41 insertions(+), 1 deletion(-) diff --git a/stock_storage_type/models/stock_storage_category.py b/stock_storage_type/models/stock_storage_category.py index 0196e1cc1a8..d383e22b4ce 100644 --- a/stock_storage_type/models/stock_storage_category.py +++ b/stock_storage_type/models/stock_storage_category.py @@ -1,6 +1,7 @@ # Copyright 2022 ACSONE SA # License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl) from odoo import api, fields, models +from odoo.osv.expression import AND, OR class StockStorageCategory(models.Model): @@ -164,7 +165,39 @@ def _domain_location_storage_category( quants=quants, ) if allow_new_product == "empty": - location_domain.append(("location_is_empty", "=", True)) + # We should include the destination location of the current + # stock move line to avoid excluding it if already selected + # Indeed, if the current move line point to the last void location, + # calling the putaway apply will recompute the destination location + # to the related stock.move destination as the rules consider + # there is no more room available (which is not true). + exclude_sml_ids = self.env.context.get("exclude_sml_ids") + if exclude_sml_ids: + lines_locations = ( + self.env["stock.move.line"].browse(exclude_sml_ids).location_dest_id + ) + if lines_locations: + location_domain = AND( + [ + location_domain, + OR( + [ + [ + ("location_is_empty", "=", False), + ("id", "in", lines_locations.ids), + ], + [("location_is_empty", "=", True)], + ] + ), + ] + ) + else: + location_domain = AND( + [ + location_domain, + [("location_is_empty", "=", True)], + ] + ) elif allow_new_product == "same": location_domain += self._get_product_location_domain(products) elif allow_new_product == "same_lot": diff --git a/stock_storage_type/tests/test_storage_type_putaway_strategy.py b/stock_storage_type/tests/test_storage_type_putaway_strategy.py index a2e2f82b120..6174fb024cf 100644 --- a/stock_storage_type/tests/test_storage_type_putaway_strategy.py +++ b/stock_storage_type/tests/test_storage_type_putaway_strategy.py @@ -143,6 +143,13 @@ def test_storage_strategy_only_empty_ordered_locations_pallets(self): self.pallets_bin_1_location | self.pallets_bin_3_location, ) + # Try to re-apply the putaways to check the same destinations are selected + int_picking.move_line_ids._apply_putaway_strategy() + 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): """Test pallet max weight constraint on a location. From 5ff4ef3d7afb555f24eb2fd16462fb9d687b89f4 Mon Sep 17 00:00:00 2001 From: Denis Roussel Date: Thu, 22 May 2025 14:26:23 +0200 Subject: [PATCH 02/12] [FIX] stock_storage_type: Allow to recompute putaways if location has been populated In the case of no product mixing, if a destination location has already been computed and in the meantime, the location has been populated by another product, the computation still propose the same location. Exclude locations in that case. --- stock_storage_type/models/stock_location.py | 47 +++++++ .../models/stock_storage_category.py | 29 +++-- .../test_storage_type_putaway_strategy.py | 120 ++++++++++++++++++ 3 files changed, 185 insertions(+), 11 deletions(-) diff --git a/stock_storage_type/models/stock_location.py b/stock_storage_type/models/stock_location.py index 72d2de071cd..626b024d291 100644 --- a/stock_storage_type/models/stock_location.py +++ b/stock_storage_type/models/stock_location.py @@ -142,6 +142,23 @@ class StockLocation(models.Model): compute="_compute_only_empty", store=True, recursive=True ) + has_potential_product_mix_exception = fields.Boolean( + compute="_compute_has_potential_product_mix_exception", + store=True, + index=True, + help="This will represent a situation where several moves are pointing" + "to the location for different products and the location does" + "not allow mixed products.", + ) + has_potential_lot_mix_exception = fields.Boolean( + compute="_compute_has_potential_lot_mix_exception", + store=True, + index=True, + help="This will represent a situation where several moves are pointing" + "to the location for different product lots and the location does" + "not allow mixed lots.", + ) + def init(self): # pylint: disable=missing-return super().init() if not index_exists(self._cr, "stock_move_line_location_state_index"): @@ -155,6 +172,36 @@ def init(self): # pylint: disable=missing-return """ ) + @api.depends("do_not_mix_lots", "location_will_contain_lot_ids") + def _compute_has_potential_lot_mix_exception(self): + locations_with_exception = self.browse() + locations_without_exception = self.browse() + for location in self: + if ( + location._should_compute_will_contain_lot_ids() + and len(location.location_will_contain_lot_ids) > 1 + ): + locations_with_exception |= location + else: + locations_without_exception |= location + locations_with_exception.has_potential_lot_mix_exception = True + locations_without_exception.has_potential_lot_mix_exception = False + + @api.depends("do_not_mix_lots", "location_will_contain_product_ids") + def _compute_has_potential_product_mix_exception(self): + locations_with_exception = self.browse() + locations_without_exception = self.browse() + for location in self: + if ( + location._should_compute_will_contain_product_ids() + and len(location.location_will_contain_product_ids) > 1 + ): + locations_with_exception |= location + else: + locations_without_exception |= location + locations_with_exception.has_potential_product_mix_exception = True + locations_without_exception.has_potential_product_mix_exception = False + @api.depends( "usage", "computed_storage_category_id.allow_new_product", diff --git a/stock_storage_type/models/stock_storage_category.py b/stock_storage_type/models/stock_storage_category.py index d383e22b4ce..a82807a8975 100644 --- a/stock_storage_type/models/stock_storage_category.py +++ b/stock_storage_type/models/stock_storage_category.py @@ -133,17 +133,24 @@ 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), - ] + # 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. + + # Take only locations that has no potential different products + # in it. + return OR( + [ + [ + ("has_potential_product_mix_exception", "=", False), + ("location_will_contain_product_ids", "in", products.ids), + ], + [("location_will_contain_product_ids", "=", False)], + ] + ) def _domain_location_storage_category( self, candidate_locations, quants, products, package_type diff --git a/stock_storage_type/tests/test_storage_type_putaway_strategy.py b/stock_storage_type/tests/test_storage_type_putaway_strategy.py index 6174fb024cf..ffb71e84bb5 100644 --- a/stock_storage_type/tests/test_storage_type_putaway_strategy.py +++ b/stock_storage_type/tests/test_storage_type_putaway_strategy.py @@ -836,3 +836,123 @@ def test_storage_strategy_with_view(self): "the move line's destination must stay in Stock as we have" " a 'none' strategy on it and it is in the sequence", ) + + def test_storage_strategy_same_ordered_locations_pallets_reapply(self): + """ + Check if location is well recomputed after filling it with another move + and after emptying other ones that are after in the ordering + + - The location is first computed on the last free one + - The location is filled in with another move + - The move's location destination is recomputed + """ + # Set pallets location type as only empty and remove specific pallet condition + self.pallets_location_storage_type.storage_category_id.write( + {"allow_new_product": "same"} + ) + self.pallets_location_storage_type.storage_category_id.allow_new_product_ids = ( + False + ) + # Set another product in bin 2 and bin 3 + self.env["stock.quant"]._update_available_quantity( + self.product2, self.pallets_bin_2_location, 1.0 + ) + self.env["stock.quant"]._update_available_quantity( + self.product3, self.pallets_bin_3_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.qty_done = 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.qty_done = 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[0].mapped("location_dest_id"), + self.pallets_bin_1_location, + ) + + self.env["stock.quant"].with_context(inventory_mode=True).create( + { + "product_id": self.product3.id, + "location_id": self.pallets_bin_1_location.id, + "inventory_quantity": 10.0, + } + )._apply_inventory() + + # Void the bin 3 + quant = self.env["stock.quant"].search( + [ + ("product_id", "=", self.product3.id), + ("location_id", "=", self.pallets_bin_3_location.id), + ] + ) + quant.location_id = self.env.ref("stock.stock_location_customers") + + # Try to re-apply the putaways to check the good destination is selected + int_picking.move_line_ids._apply_putaway_strategy() + self.assertNotEqual( + int_picking.move_line_ids[0].mapped("location_dest_id"), + self.pallets_bin_1_location, + ) + self.assertEqual( + int_picking.move_line_ids[0].mapped("location_dest_id"), + self.pallets_bin_3_location, + ) + + self.env["stock.quant"].with_context(inventory_mode=True).create( + { + "product_id": self.product.id, + "location_id": self.pallets_bin_3_location.id, + "inventory_quantity": 10.0, + } + )._apply_inventory() + + int_picking.move_line_ids._apply_putaway_strategy() + self.assertEqual( + int_picking.move_line_ids[0].mapped("location_dest_id"), + self.pallets_bin_3_location, + ) From 15d88b097f2558bcbecb0a96dfb3a56b7451efa2 Mon Sep 17 00:00:00 2001 From: Denis Roussel Date: Thu, 22 May 2025 14:29:27 +0200 Subject: [PATCH 03/12] [IMP] stock_storage_type: Use of AND() instead of appending domains --- .../models/stock_storage_category.py | 28 +++++++++++++------ 1 file changed, 20 insertions(+), 8 deletions(-) diff --git a/stock_storage_type/models/stock_storage_category.py b/stock_storage_type/models/stock_storage_category.py index a82807a8975..e8f188b975a 100644 --- a/stock_storage_type/models/stock_storage_category.py +++ b/stock_storage_type/models/stock_storage_category.py @@ -129,6 +129,17 @@ def _compute_has_restrictions(self): ] ) + def _get_product_lot_location_domain(self, lots): + """ + Helper to get product lots domain + """ + return [ + "|", + # same comment as for the products + ("location_will_contain_lot_ids", "in", lots.ids), + ("location_will_contain_lot_ids", "=", False), + ] + def _get_product_location_domain(self, products): """ Helper to get products location domain @@ -206,17 +217,18 @@ def _domain_location_storage_category( ] ) elif allow_new_product == "same": - location_domain += self._get_product_location_domain(products) + location_domain = AND( + [location_domain, self._get_product_location_domain(products)] + ) elif 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), - ] + location_domain = AND( + [location_domain, self._get_product_location_domain(products)] + ) + location_domain = AND( + [location_domain, self._get_product_lot_location_domain(lots)] + ) return location_domain def get_allow_new_product( From ef2eb1b347d4743966068e8165d4f7fbd56e45c0 Mon Sep 17 00:00:00 2001 From: Denis Roussel Date: Thu, 22 May 2025 14:35:09 +0200 Subject: [PATCH 04/12] [IMP] stock_storage_type: Rely on ORM in order to get locations per quantity The current implementation was triggerring queries (read_group). Rely now on ORM to get records as 'quant_ids' field exists on locations --- stock_storage_type/models/stock_location.py | 23 ++++++++++----------- 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/stock_storage_type/models/stock_location.py b/stock_storage_type/models/stock_location.py index 626b024d291..ac02a5dfb1b 100644 --- a/stock_storage_type/models/stock_location.py +++ b/stock_storage_type/models/stock_location.py @@ -7,7 +7,7 @@ from odoo import api, fields, models from odoo.fields import Command -from odoo.tools import float_compare, index_exists +from odoo.tools import float_compare, groupby, index_exists _logger = logging.getLogger(__name__) OUT_MOVE_LINE_DOMAIN = [ @@ -682,18 +682,17 @@ def _order_allowed_locations(self, valid_locations): 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", + for location, items in groupby( + valid_no_mix.quant_ids.sorted("quantity"), + lambda quant: quant.location_id, + ): + loc_ordered_by_qty.extend( + [ + location.id + for item in items + if (float_compare(item["quantity"], 0, precision_digits=2) > 0) + ] ) - 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 From a637d54b49c56a47e28555593b39541b89784bb5 Mon Sep 17 00:00:00 2001 From: Denis Roussel Date: Thu, 22 May 2025 15:02:39 +0200 Subject: [PATCH 05/12] [IMP] stock_storage_type: Remove unused test file --- stock_storage_type/tests/__init__.py | 1 - .../tests/test_package_type_message.py | 34 ------------------- 2 files changed, 35 deletions(-) delete mode 100644 stock_storage_type/tests/test_package_type_message.py diff --git a/stock_storage_type/tests/__init__.py b/stock_storage_type/tests/__init__.py index ba7aa132776..bdd58279b1c 100644 --- a/stock_storage_type/tests/__init__.py +++ b/stock_storage_type/tests/__init__.py @@ -1,7 +1,6 @@ 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, diff --git a/stock_storage_type/tests/test_package_type_message.py b/stock_storage_type/tests/test_package_type_message.py deleted file mode 100644 index 7a77d490d86..00000000000 --- a/stock_storage_type/tests/test_package_type_message.py +++ /dev/null @@ -1,34 +0,0 @@ -# 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" - ) From a4ae66551c26fedb36f578881b8128efe210051c Mon Sep 17 00:00:00 2001 From: Denis Roussel Date: Thu, 22 May 2025 15:34:01 +0200 Subject: [PATCH 06/12] [REF] stock_storage_type: Rely on stock_location_fill_state for empty locations --- stock_storage_type/__manifest__.py | 1 + stock_storage_type/models/stock_location.py | 61 +------------------ .../models/stock_storage_category.py | 10 ++- .../tests/test_stock_location.py | 25 -------- 4 files changed, 9 insertions(+), 88 deletions(-) diff --git a/stock_storage_type/__manifest__.py b/stock_storage_type/__manifest__.py index 03e0ce4b10f..dcd1372c1da 100644 --- a/stock_storage_type/__manifest__.py +++ b/stock_storage_type/__manifest__.py @@ -14,6 +14,7 @@ "application": False, "installable": True, "depends": [ + "stock_location_fill_state", "stock_move_line_reserved_quant", "stock_putaway_hook", "stock_quant_package_dimension", diff --git a/stock_storage_type/models/stock_location.py b/stock_storage_type/models/stock_location.py index ac02a5dfb1b..8940787958f 100644 --- a/stock_storage_type/models/stock_location.py +++ b/stock_storage_type/models/stock_location.py @@ -58,15 +58,7 @@ class StockLocation(models.Model): "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" policy).', - 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( @@ -310,9 +302,6 @@ def _should_compute_will_contain_product_ids(self): 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", @@ -350,54 +339,6 @@ def _compute_location_will_contain_lot_ids(self): ).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.qty_done", - "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 l: not l._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=["qty_done:sum"], - groupby=["location_id"], - ): - location_id = group["location_id"][0] - out_qty_by_location[location_id] = group["qty_done"] - 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, diff --git a/stock_storage_type/models/stock_storage_category.py b/stock_storage_type/models/stock_storage_category.py index e8f188b975a..00aefbb5862 100644 --- a/stock_storage_type/models/stock_storage_category.py +++ b/stock_storage_type/models/stock_storage_category.py @@ -201,10 +201,14 @@ def _domain_location_storage_category( OR( [ [ - ("location_is_empty", "=", False), + ( + "fill_state", + "in", + ("filled", "being_filled"), + ), ("id", "in", lines_locations.ids), ], - [("location_is_empty", "=", True)], + [("fill_state", "in", ("empty", "being_emptied"))], ] ), ] @@ -213,7 +217,7 @@ def _domain_location_storage_category( location_domain = AND( [ location_domain, - [("location_is_empty", "=", True)], + [("fill_state", "in", ("empty", "being_emptied"))], ] ) elif allow_new_product == "same": diff --git a/stock_storage_type/tests/test_stock_location.py b/stock_storage_type/tests/test_stock_location.py index 6381ee18195..7bdbd0df43e 100644 --- a/stock_storage_type/tests/test_stock_location.py +++ b/stock_storage_type/tests/test_stock_location.py @@ -267,28 +267,3 @@ def test_will_contain_product_lot_ids_quantity(self): 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" rule, 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. - category = location.computed_storage_category_id - category.allow_new_product_ids.filtered( - lambda rule: rule.allow_new_product == "empty" - ).allow_new_product = "mixed" - self.assertTrue(location.location_is_empty) From e7ac6a68ace5c07b9afd004e8278682d475c6e36 Mon Sep 17 00:00:00 2001 From: Denis Roussel Date: Mon, 26 May 2025 10:09:23 +0200 Subject: [PATCH 07/12] [IMP] stock_storage_type: Ensure that a being emptied location can be filled in If every possible destination location (e.g.: for pickable stock) is filled in with products, but with one outgoing move is in progress (picked but not yet validated), the location should become available to store incoming products. The putaway strategy should propose that location. So, add a condition to select 'empty' and 'being emptied' locations along with the 'location_will_contain_product/lot_ids' property to avoid too much restriction --- stock_storage_type/models/stock_location.py | 10 +- .../models/stock_storage_category.py | 28 ++- .../test_storage_type_putaway_strategy.py | 170 ++++++++++++++++++ 3 files changed, 197 insertions(+), 11 deletions(-) diff --git a/stock_storage_type/models/stock_location.py b/stock_storage_type/models/stock_location.py index 8940787958f..2a4c94ffdc2 100644 --- a/stock_storage_type/models/stock_location.py +++ b/stock_storage_type/models/stock_location.py @@ -164,13 +164,14 @@ def init(self): # pylint: disable=missing-return """ ) - @api.depends("do_not_mix_lots", "location_will_contain_lot_ids") + @api.depends("do_not_mix_lots", "location_will_contain_lot_ids", "fill_state") def _compute_has_potential_lot_mix_exception(self): locations_with_exception = self.browse() locations_without_exception = self.browse() for location in self: if ( - location._should_compute_will_contain_lot_ids() + location.fill_state not in ("empty", "being_emptied") + and location._should_compute_will_contain_lot_ids() and len(location.location_will_contain_lot_ids) > 1 ): locations_with_exception |= location @@ -179,13 +180,14 @@ def _compute_has_potential_lot_mix_exception(self): locations_with_exception.has_potential_lot_mix_exception = True locations_without_exception.has_potential_lot_mix_exception = False - @api.depends("do_not_mix_lots", "location_will_contain_product_ids") + @api.depends("do_not_mix_lots", "location_will_contain_product_ids", "fill_state") def _compute_has_potential_product_mix_exception(self): locations_with_exception = self.browse() locations_without_exception = self.browse() for location in self: if ( - location._should_compute_will_contain_product_ids() + location.fill_state not in ("empty", "being_emptied") + and location._should_compute_will_contain_product_ids() and len(location.location_will_contain_product_ids) > 1 ): locations_with_exception |= location diff --git a/stock_storage_type/models/stock_storage_category.py b/stock_storage_type/models/stock_storage_category.py index 00aefbb5862..36ab6044f0a 100644 --- a/stock_storage_type/models/stock_storage_category.py +++ b/stock_storage_type/models/stock_storage_category.py @@ -133,12 +133,22 @@ def _get_product_lot_location_domain(self, lots): """ Helper to get product lots domain """ - return [ - "|", - # same comment as for the products - ("location_will_contain_lot_ids", "in", lots.ids), - ("location_will_contain_lot_ids", "=", False), - ] + lot_domain = OR( + [ + [ + ("location_will_contain_lot_ids", "in", lots.ids), + ], + [ + ("location_will_contain_lot_ids", "=", False), + ], + ] + ) + + location_domain = OR( + [lot_domain, [("fill_state", "in", ("empty", "being_emptied"))]] + ) + + return location_domain def _get_product_location_domain(self, products): """ @@ -153,7 +163,7 @@ def _get_product_location_domain(self, products): # Take only locations that has no potential different products # in it. - return OR( + product_domain = OR( [ [ ("has_potential_product_mix_exception", "=", False), @@ -162,6 +172,10 @@ def _get_product_location_domain(self, products): [("location_will_contain_product_ids", "=", False)], ] ) + location_domain = OR( + [product_domain, [("fill_state", "in", ("empty", "being_emptied"))]] + ) + return location_domain def _domain_location_storage_category( self, candidate_locations, quants, products, package_type diff --git a/stock_storage_type/tests/test_storage_type_putaway_strategy.py b/stock_storage_type/tests/test_storage_type_putaway_strategy.py index ffb71e84bb5..8a3da2bd688 100644 --- a/stock_storage_type/tests/test_storage_type_putaway_strategy.py +++ b/stock_storage_type/tests/test_storage_type_putaway_strategy.py @@ -956,3 +956,173 @@ def test_storage_strategy_same_ordered_locations_pallets_reapply(self): int_picking.move_line_ids[0].mapped("location_dest_id"), self.pallets_bin_3_location, ) + + def test_storage_strategy_same_lot_emptying(self): + """ + Ensure that if a location is being emptied, it becomes available + for a new movement. + + So: + - Fill in all carboxes location bins. + - Create an OUT move with quantity done (location will be 'being emptied') + - Do the reception + - Check the internal move will go to the BIN 1 + + """ + self.product_other = self.env.ref("product.product_product_10") + self.cardboxes_location_storage_type.storage_category_id.write( + {"allow_new_product": "same_lot"} + ) + # Fill in all locations + self.env["stock.quant"]._update_available_quantity( + self.product_other, + self.cardboxes_bin_1_location, + 1.0, + ) + self.env["stock.quant"]._update_available_quantity( + self.product_other, + self.cardboxes_bin_2_location, + 1.0, + ) + self.env["stock.quant"]._update_available_quantity( + self.product_other, + self.cardboxes_bin_3_location, + 1.0, + ) + self.env["stock.quant"]._update_available_quantity( + self.product_other, + self.cardboxes_bin_4_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 + ).qty_done = 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.qty_done = 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({"qty_done": 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({"qty_done": 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 + ) + + # Create a move to pick a bin location, so that location fill state + # will be 'being emptied'. + customers = self.env.ref("stock.stock_location_customers") + pick_picking = self.env["stock.picking"].create( + { + "picking_type_id": self.internal_picking_type.id, + "location_id": self.cardboxes_bin_1_location.id, + "location_dest_id": customers.id, + "move_ids": [ + ( + 0, + 0, + { + "name": self.product.name, + "location_id": self.cardboxes_bin_1_location.id, + "location_dest_id": customers.id, + "product_id": self.product_other.id, + "product_uom_qty": 8.0, + "product_uom": self.product_other.uom_id.id, + "picking_type_id": self.internal_picking_type.id, + }, + ), + ], + } + ) + + pick_picking.action_assign() + self.assertEqual( + pick_picking.move_line_ids.location_id, + self.cardboxes_bin_1_location, + ) + + pick_picking.move_line_ids.qty_done = 8.0 + + self.assertEqual("being_emptied", self.cardboxes_bin_1_location.fill_state) + + # 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 + ) From 7fe9543d8f3bd47aa18b88b4dfb422dd00b983e7 Mon Sep 17 00:00:00 2001 From: Jacques-Etienne Baudoux Date: Thu, 13 Nov 2025 22:55:52 +0100 Subject: [PATCH 08/12] [FIX] stock_storage_type: location being emptied Fix computation of location will contain product/lot --- stock_storage_type/models/stock_location.py | 86 +++++++++++++++++++-- 1 file changed, 79 insertions(+), 7 deletions(-) diff --git a/stock_storage_type/models/stock_location.py b/stock_storage_type/models/stock_location.py index 2a4c94ffdc2..ba477e927d7 100644 --- a/stock_storage_type/models/stock_location.py +++ b/stock_storage_type/models/stock_location.py @@ -308,6 +308,7 @@ def _should_compute_will_contain_lot_ids(self): "quant_ids.quantity", "in_move_ids", "in_move_line_ids", + "out_move_line_ids", "do_not_mix_products", ) def _compute_location_will_contain_product_ids(self): @@ -316,18 +317,53 @@ def _compute_location_will_contain_product_ids(self): no_product = self.env["product.product"].browse() rec.location_will_contain_product_ids = no_product else: + non_fully_reserved_quants = rec.quant_ids.filtered( + lambda q: float_compare( + q.quantity, 0, precision_rounding=q.product_uom_id.rounding + ) + > 0 + and float_compare( + q.reserved_quantity, + q.quantity, + precision_rounding=q.product_uom_id.rounding, + ) + < 0 + ) + # Products that are obviously in the location products = ( - rec.mapped("quant_ids") - .filtered(lambda q: q.quantity > 0) - .product_id - | rec.mapped("in_move_ids.product_id") + non_fully_reserved_quants.product_id | rec.mapped("in_move_line_ids.product_id") + | rec.mapped("in_move_ids.product_id") + ) + # For fully reserved quants, ensure the product is not being emptied + remaining_quants = rec.quant_ids.filtered( + lambda q, products=products: q.product_id not in products ) + if remaining_quants: + for product, quants_by_product in groupby( + remaining_quants, lambda q: q.product_id + ): + quantity = sum(map(lambda q: q.quantity, quants_by_product)) + picked_quantity = sum( + ml.qty_done + for ml in rec.out_move_line_ids + if ml.product_id == product + ) + if ( + float_compare( + quantity, + picked_quantity, + precision_rounding=product.uom_id.rounding, + ) + > 0 + ): + products |= product rec.location_will_contain_product_ids = products @api.depends( "quant_ids.quantity", "in_move_line_ids", + "out_move_line_ids", "do_not_mix_lots", ) def _compute_location_will_contain_lot_ids(self): @@ -336,9 +372,45 @@ def _compute_location_will_contain_lot_ids(self): 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") + non_fully_reserved_quants = rec.quant_ids.filtered( + lambda q: float_compare( + q.quantity, 0, precision_rounding=q.product_uom_id.rounding + ) + > 0 + and float_compare( + q.reserved_quantity, + q.quantity, + precision_rounding=q.product_uom_id.rounding, + ) + < 0 + ) + # Lots that are obviously in the location + lots = non_fully_reserved_quants.lot_id | rec.mapped( + "in_move_line_ids.lot_id" + ) + # For fully reserved quants, ensure the lot is not being emptied + remaining_quants = rec.quant_ids.filtered( + lambda q, lots=lots: q.lot_id and q.lot_id not in lots + ) + if remaining_quants: + for lot, quants_by_product in groupby( + remaining_quants, lambda q: q.lot_id + ): + quantity = sum(map(lambda q: q.quantity, quants_by_product)) + picked_quantity = sum( + ml.qty_done + for ml in rec.out_move_line_ids + if ml.lot_id == lot + ) + if ( + float_compare( + quantity, + picked_quantity, + precision_rounding=lot.product_id.uom_id.rounding, + ) + > 0 + ): + lots |= lot rec.location_will_contain_lot_ids = lots # method provided by "stock_putaway_hook" From 20151705be3e790e02a1dce4b31be725d2c99c8b Mon Sep 17 00:00:00 2001 From: Jacques-Etienne Baudoux Date: Thu, 13 Nov 2025 21:47:52 +0100 Subject: [PATCH 09/12] stock_storage_type: drop content constrain The content constrain can be manage by stock_location_product_restriction or stock_location_package_restriction modules. Removed from this module --- stock_storage_type/models/stock_quant.py | 56 ++----------------- .../tests/test_storage_type_move.py | 9 --- 2 files changed, 5 insertions(+), 60 deletions(-) diff --git a/stock_storage_type/models/stock_quant.py b/stock_storage_type/models/stock_quant.py index effebd99c38..d1acacad287 100644 --- a/stock_storage_type/models/stock_quant.py +++ b/stock_storage_type/models/stock_quant.py @@ -30,58 +30,14 @@ def _check_storage_capacities(self): "Location {location}" ).format(storage=package_type.name, location=location.name) ) + package = quant.package_id package_weight_kg = ( - quant.package_id.pack_weight_in_kg - or quant.package_id.estimated_pack_weight_kg + package.pack_weight_in_kg or package.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") error = None category = location.computed_storage_category_id - allow_new_product = category.get_allow_new_product( - product=quant.product_id, - package_type=package_type, - package=quant.package_id, - quants=quant, - ) - # Check content constraints - if allow_new_product == "empty" and other_quants_in_location: - error = _( - "Storage Category {category} is flagged " - "'only empty' with other quants in location." - ).format(category=category.display_name) - elif allow_new_product == "same" and ( - len(package_products) > 1 - or len(products_in_location) >= 1 - and package_products != products_in_location - ): - error = _( - "Storage Category {category} is flagged 'do not mix" - " products' but there are other products in " - "location." - ).format(category=category.display_name) - elif allow_new_product == "same_lot" and ( - len(package_lots) > 1 - or len(lots_in_location) >= 1 - and package_lots != lots_in_location - ): - error = _( - "Storage Category {category} is flagged 'do not mix" - " lots' but there are other lots in " - "location." - ).format(category=category.display_name) # Check size constraint - elif ( + if ( category.max_height_in_m and quant.package_id.height_in_m > category.max_height_in_m ): @@ -94,7 +50,7 @@ def _check_storage_capacities(self): max_h=category.max_height_in_m, height=quant.package_id.height_in_m, ) - elif ( + if ( category.max_weight_in_kg and package_weight_kg > category.max_weight_in_kg ): @@ -107,8 +63,6 @@ def _check_storage_capacities(self): max_w=category.max_weight_in_kg, weight_kg=package_weight_kg, ) - # If we get here, it means there is a storage category - # allowing the package into the location if error: raise ValidationError( _( @@ -116,7 +70,7 @@ def _check_storage_capacities(self): " because there isn't any rules that allows" " package type {type} into it:\n\n{error}" ).format( - package=quant.package_id.name, + package=package.name, location=location.complete_name, type=package_type.name, error=error, diff --git a/stock_storage_type/tests/test_storage_type_move.py b/stock_storage_type/tests/test_storage_type_move.py index 1915122a3a6..28e20fadac1 100644 --- a/stock_storage_type/tests/test_storage_type_move.py +++ b/stock_storage_type/tests/test_storage_type_move.py @@ -1,6 +1,5 @@ # 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 @@ -375,14 +374,6 @@ def _levels_for(packages): _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.qty_done = 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 From 2acdf927c735927e613ef426bde85a5935c993d9 Mon Sep 17 00:00:00 2001 From: oca-ci Date: Thu, 16 Apr 2026 07:42:28 +0000 Subject: [PATCH 10/12] [UPD] Update stock_storage_type.pot --- .../i18n/stock_storage_type.pot | 66 ++++++++----------- 1 file changed, 26 insertions(+), 40 deletions(-) diff --git a/stock_storage_type/i18n/stock_storage_type.pot b/stock_storage_type/i18n/stock_storage_type.pot index 09502e30c7a..f25b3571d5a 100644 --- a/stock_storage_type/i18n/stock_storage_type.pot +++ b/stock_storage_type/i18n/stock_storage_type.pot @@ -202,6 +202,16 @@ msgstr "" msgid "Execute code" msgstr "" +#. module: stock_storage_type +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_location__has_potential_lot_mix_exception +msgid "Has Potential Lot Mix Exception" +msgstr "" + +#. module: stock_storage_type +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_location__has_potential_product_mix_exception +msgid "Has Potential Product Mix Exception" +msgstr "" + #. module: stock_storage_type #: model:ir.model.fields,field_description:stock_storage_type.field_stock_storage_category__has_restrictions msgid "Has Restrictions" @@ -289,11 +299,6 @@ msgstr "" 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" @@ -526,33 +531,6 @@ msgid "" " heavier: {weight_kg}." msgstr "" -#. module: stock_storage_type -#. odoo-python -#: code:addons/stock_storage_type/models/stock_quant.py:0 -#, python-format -msgid "" -"Storage Category {category} 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 Category {category} 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 Category {category} is flagged 'only empty' with other quants in " -"location." -msgstr "" - #. module: stock_storage_type #: model:ir.ui.menu,name:stock_storage_type.stock_storage_location_sequence_cond_menu msgid "Storage Location Sequence Conditions" @@ -616,6 +594,22 @@ msgid "" " 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_location__has_potential_lot_mix_exception +msgid "" +"This will represent a situation where several moves are pointingto the " +"location for different product lots and the location doesnot allow mixed " +"lots." +msgstr "" + +#. module: stock_storage_type +#: model:ir.model.fields,help:stock_storage_type.field_stock_location__has_potential_product_mix_exception +msgid "" +"This will represent a situation where several moves are pointingto the " +"location for different products and the location doesnot allow mixed " +"products." +msgstr "" + #. module: stock_storage_type #: model:ir.model.fields,help:stock_storage_type.field_stock_storage_category__length_uom_id msgid "UoM for height" @@ -657,14 +651,6 @@ msgstr "" 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\" policy)." -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" From 28db9958c4145616c824830797b30d4ce8f5778c Mon Sep 17 00:00:00 2001 From: OCA-git-bot Date: Thu, 16 Apr 2026 07:54:44 +0000 Subject: [PATCH 11/12] [BOT] post-merge updates --- README.md | 2 +- stock_storage_type/README.rst | 2 +- stock_storage_type/__manifest__.py | 2 +- stock_storage_type/static/description/index.html | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index c505822d9a5..17cbc0d29f3 100644 --- a/README.md +++ b/README.md @@ -76,7 +76,7 @@ addon | version | maintainers | summary [stock_release_channel_show_volume](stock_release_channel_show_volume/) | 16.0.1.1.0 | | Display volumes of stock release channels [stock_release_channel_show_weight](stock_release_channel_show_weight/) | 16.0.1.1.0 | | Display weights of stock release channels [stock_release_channel_warehouse_calendar](stock_release_channel_warehouse_calendar/) | 16.0.1.0.0 | jbaudoux | Glue module between release channel and warehouse calendar -[stock_storage_type](stock_storage_type/) | 16.0.2.0.3 | jbaudoux rousseldenis | Manage packages and locations storage types +[stock_storage_type](stock_storage_type/) | 16.0.2.1.0 | jbaudoux rousseldenis | Manage packages and locations storage types [stock_storage_type_putaway_abc](stock_storage_type_putaway_abc/) | 16.0.1.0.0 | | Advanced storage strategy ABC for WMS [stock_warehouse_flow](stock_warehouse_flow/) | 16.0.1.1.0 | | Configure routing flow for stock moves [stock_warehouse_flow_delivery_refresh](stock_warehouse_flow_delivery_refresh/) | 16.0.1.0.0 | | Allow to refresh delivery flow when carrier changes diff --git a/stock_storage_type/README.rst b/stock_storage_type/README.rst index e679aad239b..9e17c82b3ee 100644 --- a/stock_storage_type/README.rst +++ b/stock_storage_type/README.rst @@ -11,7 +11,7 @@ Stock Storage Type !! This file is generated by oca-gen-addon-readme !! !! changes will be overwritten. !! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! - !! source digest: sha256:c46ce97684eda7baf75edac00f005ea18baac91d81a4f0df32ba6f49ce1b34ee + !! source digest: sha256:ad18c92cb64972423bce045cb542b198d72561d86c746e5db7e7ccc9962cbec5 !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! .. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png diff --git a/stock_storage_type/__manifest__.py b/stock_storage_type/__manifest__.py index dcd1372c1da..c1208bcbf81 100644 --- a/stock_storage_type/__manifest__.py +++ b/stock_storage_type/__manifest__.py @@ -4,7 +4,7 @@ { "name": "Stock Storage Type", "summary": "Manage packages and locations storage types", - "version": "16.0.2.0.3", + "version": "16.0.2.1.0", "development_status": "Beta", "category": "Warehouse Management", "website": "https://github.com/OCA/wms", diff --git a/stock_storage_type/static/description/index.html b/stock_storage_type/static/description/index.html index 4cdf66d916d..814131b2264 100644 --- a/stock_storage_type/static/description/index.html +++ b/stock_storage_type/static/description/index.html @@ -372,7 +372,7 @@

Stock Storage Type

!! This file is generated by oca-gen-addon-readme !! !! changes will be overwritten. !! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -!! source digest: sha256:c46ce97684eda7baf75edac00f005ea18baac91d81a4f0df32ba6f49ce1b34ee +!! source digest: sha256:ad18c92cb64972423bce045cb542b198d72561d86c746e5db7e7ccc9962cbec5 !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -->

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

This module extends package types Odoo feature in order to better manage stock From 76f0f923dd731b761644dfad4ed75882276392d8 Mon Sep 17 00:00:00 2001 From: Weblate Date: Thu, 16 Apr 2026 07:54:55 +0000 Subject: [PATCH 12/12] Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Translation: wms-16.0/wms-16.0-stock_storage_type Translate-URL: https://translation.odoo-community.org/projects/wms-16-0/wms-16-0-stock_storage_type/ --- stock_storage_type/i18n/de.po | 66 +++++++---------- stock_storage_type/i18n/es.po | 69 ++++++++---------- stock_storage_type/i18n/es_AR.po | 69 ++++++++---------- stock_storage_type/i18n/fr.po | 66 +++++++---------- stock_storage_type/i18n/it.po | 117 +++++++++++++++++-------------- 5 files changed, 175 insertions(+), 212 deletions(-) diff --git a/stock_storage_type/i18n/de.po b/stock_storage_type/i18n/de.po index f1f6fdef903..60e59921386 100644 --- a/stock_storage_type/i18n/de.po +++ b/stock_storage_type/i18n/de.po @@ -205,6 +205,16 @@ msgstr "" msgid "Execute code" msgstr "" +#. module: stock_storage_type +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_location__has_potential_lot_mix_exception +msgid "Has Potential Lot Mix Exception" +msgstr "" + +#. module: stock_storage_type +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_location__has_potential_product_mix_exception +msgid "Has Potential Product Mix Exception" +msgstr "" + #. module: stock_storage_type #: model:ir.model.fields,field_description:stock_storage_type.field_stock_storage_category__has_restrictions msgid "Has Restrictions" @@ -292,11 +302,6 @@ msgstr "" 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" @@ -530,33 +535,6 @@ msgid "" "heavier: {weight_kg}." msgstr "" -#. module: stock_storage_type -#. odoo-python -#: code:addons/stock_storage_type/models/stock_quant.py:0 -#, python-format -msgid "" -"Storage Category {category} 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 Category {category} 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 Category {category} is flagged 'only empty' with other quants in " -"location." -msgstr "" - #. module: stock_storage_type #: model:ir.ui.menu,name:stock_storage_type.stock_storage_location_sequence_cond_menu msgid "Storage Location Sequence Conditions" @@ -623,6 +601,22 @@ msgid "" "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_location__has_potential_lot_mix_exception +msgid "" +"This will represent a situation where several moves are pointingto the " +"location for different product lots and the location doesnot allow mixed " +"lots." +msgstr "" + +#. module: stock_storage_type +#: model:ir.model.fields,help:stock_storage_type.field_stock_location__has_potential_product_mix_exception +msgid "" +"This will represent a situation where several moves are pointingto the " +"location for different products and the location doesnot allow mixed " +"products." +msgstr "" + #. module: stock_storage_type #: model:ir.model.fields,help:stock_storage_type.field_stock_storage_category__length_uom_id msgid "UoM for height" @@ -664,14 +658,6 @@ msgstr "" 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\" policy)." -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" diff --git a/stock_storage_type/i18n/es.po b/stock_storage_type/i18n/es.po index de3769f093e..64a9f061bb5 100644 --- a/stock_storage_type/i18n/es.po +++ b/stock_storage_type/i18n/es.po @@ -211,6 +211,16 @@ msgstr "No Mezclar Productos" msgid "Execute code" msgstr "Ejecutar código" +#. module: stock_storage_type +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_location__has_potential_lot_mix_exception +msgid "Has Potential Lot Mix Exception" +msgstr "" + +#. module: stock_storage_type +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_location__has_potential_product_mix_exception +msgid "Has Potential Product Mix Exception" +msgstr "" + #. module: stock_storage_type #: model:ir.model.fields,field_description:stock_storage_type.field_stock_storage_category__has_restrictions msgid "Has Restrictions" @@ -298,11 +308,6 @@ msgstr "Ubicación de la hoja" 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" @@ -537,33 +542,6 @@ msgid "" "heavier: {weight_kg}." msgstr "" -#. module: stock_storage_type -#. odoo-python -#: code:addons/stock_storage_type/models/stock_quant.py:0 -#, python-format -msgid "" -"Storage Category {category} 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 Category {category} 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 Category {category} is flagged 'only empty' with other quants in " -"location." -msgstr "" - #. module: stock_storage_type #: model:ir.ui.menu,name:stock_storage_type.stock_storage_location_sequence_cond_menu msgid "Storage Location Sequence Conditions" @@ -634,6 +612,22 @@ 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_location__has_potential_lot_mix_exception +msgid "" +"This will represent a situation where several moves are pointingto the " +"location for different product lots and the location doesnot allow mixed " +"lots." +msgstr "" + +#. module: stock_storage_type +#: model:ir.model.fields,help:stock_storage_type.field_stock_location__has_potential_product_mix_exception +msgid "" +"This will represent a situation where several moves are pointingto the " +"location for different products and the location doesnot allow mixed " +"products." +msgstr "" + #. module: stock_storage_type #: model:ir.model.fields,help:stock_storage_type.field_stock_storage_category__length_uom_id msgid "UoM for height" @@ -675,14 +669,6 @@ msgstr "" 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\" policy)." -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" @@ -728,6 +714,9 @@ msgstr "" 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" +#~ msgid "Location Is Empty" +#~ msgstr "La ubicación está vacía" + #, python-format #~ msgid "" #~ " * {location} (WARNING: restrictions are " diff --git a/stock_storage_type/i18n/es_AR.po b/stock_storage_type/i18n/es_AR.po index b9926cb8cc5..e426b755069 100644 --- a/stock_storage_type/i18n/es_AR.po +++ b/stock_storage_type/i18n/es_AR.po @@ -207,6 +207,16 @@ msgstr "No Mezcla Productos" msgid "Execute code" msgstr "Ejecutar código" +#. module: stock_storage_type +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_location__has_potential_lot_mix_exception +msgid "Has Potential Lot Mix Exception" +msgstr "" + +#. module: stock_storage_type +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_location__has_potential_product_mix_exception +msgid "Has Potential Product Mix Exception" +msgstr "" + #. module: stock_storage_type #: model:ir.model.fields,field_description:stock_storage_type.field_stock_storage_category__has_restrictions msgid "Has Restrictions" @@ -294,11 +304,6 @@ msgstr "Ubicación Hoja" 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" @@ -534,33 +539,6 @@ msgid "" "heavier: {weight_kg}." msgstr "" -#. module: stock_storage_type -#. odoo-python -#: code:addons/stock_storage_type/models/stock_quant.py:0 -#, python-format -msgid "" -"Storage Category {category} 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 Category {category} 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 Category {category} is flagged 'only empty' with other quants in " -"location." -msgstr "" - #. module: stock_storage_type #: model:ir.ui.menu,name:stock_storage_type.stock_storage_location_sequence_cond_menu msgid "Storage Location Sequence Conditions" @@ -627,6 +605,22 @@ msgid "" "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_location__has_potential_lot_mix_exception +msgid "" +"This will represent a situation where several moves are pointingto the " +"location for different product lots and the location doesnot allow mixed " +"lots." +msgstr "" + +#. module: stock_storage_type +#: model:ir.model.fields,help:stock_storage_type.field_stock_location__has_potential_product_mix_exception +msgid "" +"This will represent a situation where several moves are pointingto the " +"location for different products and the location doesnot allow mixed " +"products." +msgstr "" + #. module: stock_storage_type #: model:ir.model.fields,help:stock_storage_type.field_stock_storage_category__length_uom_id msgid "UoM for height" @@ -668,14 +662,6 @@ msgstr "" 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\" policy)." -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" @@ -717,6 +703,9 @@ msgstr "campo técnico: stock.moves de entrada pendientes en la ubicación" 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" +#~ msgid "Location Is Empty" +#~ msgstr "La Ubicación está Vacía" + #~ msgid "Height is mandatory for packages configured with this storage type." #~ msgstr "" #~ "La Altura es obligatoria para paquetes configurados con este tipo de " diff --git a/stock_storage_type/i18n/fr.po b/stock_storage_type/i18n/fr.po index e962c88b90c..c07754001f6 100644 --- a/stock_storage_type/i18n/fr.po +++ b/stock_storage_type/i18n/fr.po @@ -207,6 +207,16 @@ msgstr "" msgid "Execute code" msgstr "" +#. module: stock_storage_type +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_location__has_potential_lot_mix_exception +msgid "Has Potential Lot Mix Exception" +msgstr "" + +#. module: stock_storage_type +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_location__has_potential_product_mix_exception +msgid "Has Potential Product Mix Exception" +msgstr "" + #. module: stock_storage_type #: model:ir.model.fields,field_description:stock_storage_type.field_stock_storage_category__has_restrictions msgid "Has Restrictions" @@ -294,11 +304,6 @@ msgstr "" 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" @@ -532,33 +537,6 @@ msgid "" "heavier: {weight_kg}." msgstr "" -#. module: stock_storage_type -#. odoo-python -#: code:addons/stock_storage_type/models/stock_quant.py:0 -#, python-format -msgid "" -"Storage Category {category} 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 Category {category} 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 Category {category} is flagged 'only empty' with other quants in " -"location." -msgstr "" - #. module: stock_storage_type #: model:ir.ui.menu,name:stock_storage_type.stock_storage_location_sequence_cond_menu msgid "Storage Location Sequence Conditions" @@ -625,6 +603,22 @@ msgid "" "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_location__has_potential_lot_mix_exception +msgid "" +"This will represent a situation where several moves are pointingto the " +"location for different product lots and the location doesnot allow mixed " +"lots." +msgstr "" + +#. module: stock_storage_type +#: model:ir.model.fields,help:stock_storage_type.field_stock_location__has_potential_product_mix_exception +msgid "" +"This will represent a situation where several moves are pointingto the " +"location for different products and the location doesnot allow mixed " +"products." +msgstr "" + #. module: stock_storage_type #: model:ir.model.fields,help:stock_storage_type.field_stock_storage_category__length_uom_id msgid "UoM for height" @@ -666,14 +660,6 @@ msgstr "" 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\" policy)." -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" diff --git a/stock_storage_type/i18n/it.po b/stock_storage_type/i18n/it.po index 2d7e271d093..8ba81803ccc 100644 --- a/stock_storage_type/i18n/it.po +++ b/stock_storage_type/i18n/it.po @@ -221,6 +221,16 @@ msgstr "Non mescolare prodotti" msgid "Execute code" msgstr "Esegui codice" +#. module: stock_storage_type +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_location__has_potential_lot_mix_exception +msgid "Has Potential Lot Mix Exception" +msgstr "" + +#. module: stock_storage_type +#: model:ir.model.fields,field_description:stock_storage_type.field_stock_location__has_potential_product_mix_exception +msgid "Has Potential Product Mix Exception" +msgstr "" + #. module: stock_storage_type #: model:ir.model.fields,field_description:stock_storage_type.field_stock_storage_category__has_restrictions msgid "Has Restrictions" @@ -309,11 +319,6 @@ msgstr "Ubicazione terminale" 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" @@ -560,39 +565,6 @@ msgstr "" "La categoria stoccaggio {category} ha un peso massimo di {max_w} ma il collo " "è più pesante: {weight_kg}." -#. module: stock_storage_type -#. odoo-python -#: code:addons/stock_storage_type/models/stock_quant.py:0 -#, python-format -msgid "" -"Storage Category {category} is flagged 'do not mix lots' but there are other " -"lots in location." -msgstr "" -"La categoria stoccaggio {category} è impostata a 'non mescolare i lotti' ma " -"ci sono altri lotti nell'ubicazione." - -#. module: stock_storage_type -#. odoo-python -#: code:addons/stock_storage_type/models/stock_quant.py:0 -#, python-format -msgid "" -"Storage Category {category} is flagged 'do not mix products' but there are " -"other products in location." -msgstr "" -"La categoria stoccaggio {category} è impostata a 'non mescolare i prodotti' " -"ma ci sono altri prodotti nell'ubicazione." - -#. module: stock_storage_type -#. odoo-python -#: code:addons/stock_storage_type/models/stock_quant.py:0 -#, python-format -msgid "" -"Storage Category {category} is flagged 'only empty' with other quants in " -"location." -msgstr "" -"La categoria stoccaggio {category} è impostata a 'solo vuote' con altri " -"quanti nell'ubicazione." - #. module: stock_storage_type #: model:ir.ui.menu,name:stock_storage_type.stock_storage_location_sequence_cond_menu msgid "Storage Location Sequence Conditions" @@ -670,6 +642,22 @@ msgstr "" "Questo rappresenta la categoria stoccaggio che verrà utilizzata. Dipende " "dalla categoria impostata nell'ubicazione o in uno dei suoi padri." +#. module: stock_storage_type +#: model:ir.model.fields,help:stock_storage_type.field_stock_location__has_potential_lot_mix_exception +msgid "" +"This will represent a situation where several moves are pointingto the " +"location for different product lots and the location doesnot allow mixed " +"lots." +msgstr "" + +#. module: stock_storage_type +#: model:ir.model.fields,help:stock_storage_type.field_stock_location__has_potential_product_mix_exception +msgid "" +"This will represent a situation where several moves are pointingto the " +"location for different products and the location doesnot allow mixed " +"products." +msgstr "" + #. module: stock_storage_type #: model:ir.model.fields,help:stock_storage_type.field_stock_storage_category__length_uom_id msgid "UoM for height" @@ -703,9 +691,9 @@ msgid "" "away is applied." msgstr "" "Quando i colli con tipo {name} sono depositati, la strategia cerca una " -"ubicazione disponibile nelle seguenti ubicazioni:

{message}
<" -"br/>Nota: questo accade finché queste ubicazioni sono figlie della " -"ubicazione destinazione del movimento di magazzino o finché queste " +"ubicazione disponibile nelle seguenti ubicazioni:

{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)." @@ -717,17 +705,6 @@ msgstr "" 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\" policy)." -msgstr "" -"Campo tecnico: Vero se l'ubicazione è vuota e non ci sono prodotti in arrivo " -"in sospeso. Calcolato solo se l'ubicazione deve verificare se vuota (ha una " -"politica \"solo vuote\")." - #. 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" @@ -772,6 +749,42 @@ msgstr "campo tecnico: i stock.moves in attesa nell'ubicazione" 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" +#~ msgid "Location Is Empty" +#~ msgstr "Ubicazione vuota" + +#, python-format +#~ msgid "" +#~ "Storage Category {category} is flagged 'do not mix lots' but there are " +#~ "other lots in location." +#~ msgstr "" +#~ "La categoria stoccaggio {category} è impostata a 'non mescolare i lotti' " +#~ "ma ci sono altri lotti nell'ubicazione." + +#, python-format +#~ msgid "" +#~ "Storage Category {category} is flagged 'do not mix products' but there " +#~ "are other products in location." +#~ msgstr "" +#~ "La categoria stoccaggio {category} è impostata a 'non mescolare i " +#~ "prodotti' ma ci sono altri prodotti nell'ubicazione." + +#, python-format +#~ msgid "" +#~ "Storage Category {category} is flagged 'only empty' with other quants in " +#~ "location." +#~ msgstr "" +#~ "La categoria stoccaggio {category} è impostata a 'solo vuote' con altri " +#~ "quanti nell'ubicazione." + +#~ 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\" policy)." +#~ msgstr "" +#~ "Campo tecnico: Vero se l'ubicazione è vuota e non ci sono prodotti in " +#~ "arrivo in sospeso. Calcolato solo se l'ubicazione deve verificare se " +#~ "vuota (ha una politica \"solo vuote\")." + #, python-format #~ msgid "" #~ " * {location} (WARNING: restrictions are "