diff --git a/stock_vertical_lift_storage_type/models/vertical_lift_operation_put.py b/stock_vertical_lift_storage_type/models/vertical_lift_operation_put.py index 79a081531..054295ae5 100644 --- a/stock_vertical_lift_storage_type/models/vertical_lift_operation_put.py +++ b/stock_vertical_lift_storage_type/models/vertical_lift_operation_put.py @@ -57,10 +57,44 @@ class VerticalLiftOperationPut(models.Model): return tuple(updated_transitions) - def _has_storage_type(self): + def _has_storage_type_domain(self): move_line = self.current_move_line_id - storage_type = move_line.package_id.package_storage_type_id - return bool(storage_type) + package_storage_type = move_line.package_id.package_storage_type_id + # When a put-away is done based on the package's storage type and no + # destination is found, we can have 2 reasons: + # + # 1. No location is available according to the storage types rules, + # for instance because they are full + # 2. The storage type of the package doesn't have any location + # configured in the shuttle's locations + # + # We want to differentiate the 2 cases and handle them differently. + # For 2. we consider the storage type is not meant to be in any + # shuttle's tray but the user still tries: act the same way as if we + # had no storage type at all, the user has to scan the tray type. + # For 1. we try to find a destination location and if none is found, + # a notification indicates that the shuttle is full. + # In any case, the user should always be able to scan a different + # tray type. + return [ + ("id", "!=", self.location_id.id), + ("id", "child_of", self.location_id.id), + ( + "allowed_location_storage_type_ids", + "in", + package_storage_type.location_storage_type_ids.ids, + ), + ] + + def _has_storage_type(self): + domain = self._has_storage_type_domain() + # we don't care about order and count, only if we have at least one + # configured location for the storage type: sorting by id and limit by + # 1 will give the best explain plan + compatible_locations = self.env["stock.location"].search( + domain, limit=1, order="id" + ) + return bool(compatible_locations) def _putaway_with_storage_type(self): move_line = self.current_move_line_id @@ -74,6 +108,8 @@ class VerticalLiftOperationPut(models.Model): move_line.package_level_id.location_dest_id = new_destination self.fetch_tray() return True + # no destination found: all the trays for this tray type are full, or rejected + # by the location storage type rules return False def _put_away_with_storage_type_failed(self): diff --git a/stock_vertical_lift_storage_type/tests/test_put.py b/stock_vertical_lift_storage_type/tests/test_put.py index 083266d12..0d98c52b6 100644 --- a/stock_vertical_lift_storage_type/tests/test_put.py +++ b/stock_vertical_lift_storage_type/tests/test_put.py @@ -21,8 +21,24 @@ class TestPut(VerticalLiftCase): {"name": "Small 8x", "only_empty": True} ) - # storage type used for Tray 1A PackageStorageType = cls.env["stock.package.storage.type"] + # package storage type used only to putaway a package temporarily in + # the vertical lift view location before being put in a shuttle, which + # will not be configured in any shuttle's locations, we'll use when + # the operator knows that a good has to go in a shuttle, but doesn't + # know yet in which tray type: when the first putaway is done, the good + # stays in the vertical lift view (above the shuttles), then, when the + # user scans the package in a shuttle, they have to scan a tray type. + cls.package_storage_type_buffer = PackageStorageType.create( + { + "name": "VLift Box", + "location_storage_type_ids": [ + (4, cls.location_storage_type_buffer.id), + ], + } + ) + # storage type used for Tray 1A, user won't have to scan a tray type + # when this storage type is already set on the package cls.package_storage_type_small_8x = PackageStorageType.create( { "name": "Small 8x", @@ -58,9 +74,7 @@ class TestPut(VerticalLiftCase): } ) - cls.package = cls.env["stock.quant.package"].create( - {"package_storage_type_id": cls.package_storage_type_small_8x.id} - ) + cls.package = cls.env["stock.quant.package"].create({}) cls._update_qty_in_location( cls.wh.wh_input_stock_loc_id, cls.product_socks, 10, package=cls.package ) @@ -97,6 +111,7 @@ class TestPut(VerticalLiftCase): ) def test_storage_type_put_away(self): + self.package.package_storage_type_id = self.package_storage_type_small_8x move_line = self.int_picking.move_line_ids self.assertEqual(move_line.location_dest_id, self.vertical_lift_loc) self.assertEqual( @@ -113,8 +128,38 @@ class TestPut(VerticalLiftCase): # the dest location was Vertical Lift, it has been change to Vertical # Lift/Shuttle 1, and the computation from there took the first cell # available, we should be the pos x1 and y1 in the tray A. - self.assertTrue(move_line.location_dest_id, self.location_1a_x1y1) + self.assertEqual(move_line.location_dest_id, self.location_1a_x1y1) + self.assertEqual( + move_line.package_level_id.location_dest_id, self.location_1a_x1y1 + ) # the state goes straight to "save", as we don't need to scan the tray type # when a putaway is available self.assertEqual(operation.state, "save") + + def test_storage_type_not_configured(self): + # if we do have a storage type, but the storage type is not used in the + # shuttle (although it may be used on the lift view location, so we can have an + # initial putaway rule that puts the package in the lift view location first), + # we have to ask to scan a tray type + self.package.package_storage_type_id = self.package_storage_type_buffer + move_line = self.int_picking.move_line_ids + self.assertEqual(move_line.location_dest_id, self.vertical_lift_loc) + self.assertEqual( + move_line.package_level_id.location_dest_id, self.vertical_lift_loc + ) + + operation = self._open_screen("put") + # we begin with an empty screen, user has to scan a package, product, + # or lot + self.assertEqual(operation.state, "scan_source") + operation.on_barcode_scanned(self.package.name) + + self.assertEqual(operation.current_move_line_id, move_line) + # it stays here, as the put-away has no rule to put the "buffer box" in + # any location + self.assertEqual(move_line.location_dest_id, self.vertical_lift_loc) + + # and the user has to scan a tray type (so we are back to the normal + # flow, tested in stock_vertical_lift) + self.assertEqual(operation.state, "scan_tray_type")