diff --git a/stock_picking_group_by_max_weight/README.rst b/stock_picking_group_by_max_weight/README.rst index 5a7ebbf02873..38d7cb0beeb9 100644 --- a/stock_picking_group_by_max_weight/README.rst +++ b/stock_picking_group_by_max_weight/README.rst @@ -7,7 +7,7 @@ Stock Picking Group By Max Weight !! This file is generated by oca-gen-addon-readme !! !! changes will be overwritten. !! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! - !! source digest: sha256:a80e538459c481d69cbbc746b4dde01e52c5034473a44491aa11edf2e8a79918 + !! source digest: sha256:90d60c02dfe3ddafa506ad96c05d12b923ba4c2ce7a93ca6d41d407b3eb86905 !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! .. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png diff --git a/stock_picking_group_by_max_weight/models/stock_move.py b/stock_picking_group_by_max_weight/models/stock_move.py index fbd6724f80c3..f5cdd5ed8797 100644 --- a/stock_picking_group_by_max_weight/models/stock_move.py +++ b/stock_picking_group_by_max_weight/models/stock_move.py @@ -22,3 +22,7 @@ def _search_picking_for_assignation_domain(self): ] ) return domain + + def _assign_picking_post_process(self, new=False): + self.picking_id._split_for_max_weight() + return super()._assign_picking_post_process(new=new) diff --git a/stock_picking_group_by_max_weight/models/stock_picking.py b/stock_picking_group_by_max_weight/models/stock_picking.py index 5745f281fb7e..75116bc9a1e5 100644 --- a/stock_picking_group_by_max_weight/models/stock_picking.py +++ b/stock_picking_group_by_max_weight/models/stock_picking.py @@ -1,5 +1,6 @@ # Copyright 2023 ACSONE SA/NV # License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). +from collections import defaultdict from odoo import api, fields, models from odoo.tools import float_compare @@ -18,11 +19,13 @@ class StockPicking(models.Model): states={"done": [("readonly", True)], "cancel": [("readonly", True)]}, ) + @property + def _assignation_max_weight_precision(self): + return self._fields["assignation_max_weight"].get_digits(self.env)[1] + @api.depends("picking_type_id.group_pickings_maxweight", "weight", "state") def _compute_assignation_max_weight(self): - precision_digits = self._fields["assignation_max_weight"].get_digits(self.env)[ - 1 - ] + precision_digits = self._assignation_max_weight_precision for picking in self: max_weight = ( picking.picking_type_id.group_pickings_maxweight - picking.weight @@ -54,3 +57,63 @@ def init(self): This has to be called in every overriding module """ self._create_index_for_grouping() + + def _should_be_split_for_max_weight(self): + """ + Return True if the picking should be split + """ + self.ensure_one() + return ( + self.state not in ["done", "cancel"] + and not self.printed + and self.picking_type_id.group_pickings_maxweight + and float_compare( + self.weight, + self.picking_type_id.group_pickings_maxweight, + precision_digits=self._assignation_max_weight_precision, + ) + > 0 + ) + + def _split_for_max_weight(self): + """ + Split the picking in several pickings if the total weight is + greater than the max weight of the picking type + """ + precision_digits = self._assignation_max_weight_precision + for picking in self: + if not picking._should_be_split_for_max_weight(): + continue + # we create batch of move's ids to reassign. To do so, we + # iterate over the move lines and we add the move to the current + # batch while the total weight of the batch is less than the max + # weight of the picking type. When the total weight of the batch + # is exceeded, we create a new batch of ids to reassign. + ids_to_reassign_by_batch = defaultdict(list) + batch_id = 0 + total_weight = 0 + # we must ignore the first batch of ids to reassign because it + # will be the current picking + max_weight = picking.picking_type_id.group_pickings_maxweight + for move in picking.move_ids: + total_weight += move.weight + if ( + float_compare( + total_weight, + max_weight, + precision_digits=precision_digits, + ) + > 0 + ): + batch_id += 1 + total_weight = move.weight + ids_to_reassign_by_batch[batch_id].append(move.id) + # we create the new pickings for each batch except the first one + # which is the current picking + first = True + for ids_to_reassign in ids_to_reassign_by_batch.values(): + if first: + first = False + continue + moves = self.env["stock.move"].browse(ids_to_reassign) + moves._assign_picking() diff --git a/stock_picking_group_by_max_weight/readme/newsfragments/.gitignore b/stock_picking_group_by_max_weight/readme/newsfragments/.gitignore new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/stock_picking_group_by_max_weight/readme/newsfragments/1451.feature b/stock_picking_group_by_max_weight/readme/newsfragments/1451.feature new file mode 100644 index 000000000000..d275d01b0264 --- /dev/null +++ b/stock_picking_group_by_max_weight/readme/newsfragments/1451.feature @@ -0,0 +1,9 @@ +Split moves into different pickings at assignment time if the total weight +of the picking is greater than the maximum weight allowed on the picking type. + +Prior to this release only new moves created after the creation of an open picking +were put into a new picking upon assignment if the total weight of the picking +would exceed the maximum weight allowed on the picking type. This release improves +this behavior by also splitting existing moves into different pickings after the +moves have been assigned to a picking. IOW, in a post-assignment step, the system +will split picking lines into different pickings when the total weight is exceeded. diff --git a/stock_picking_group_by_max_weight/static/description/index.html b/stock_picking_group_by_max_weight/static/description/index.html index 78f97982a193..6ee5900c3bef 100644 --- a/stock_picking_group_by_max_weight/static/description/index.html +++ b/stock_picking_group_by_max_weight/static/description/index.html @@ -367,7 +367,7 @@

Stock Picking Group By Max Weight

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

Beta License: AGPL-3 OCA/stock-logistics-workflow Translate me on Weblate Try me on Runboat

This module allows to filter picking candidates for moves assignation based diff --git a/stock_picking_group_by_max_weight/tests/test_group_maxweight.py b/stock_picking_group_by_max_weight/tests/test_group_maxweight.py index a3913fe8ef29..90a57f02f9c3 100644 --- a/stock_picking_group_by_max_weight/tests/test_group_maxweight.py +++ b/stock_picking_group_by_max_weight/tests/test_group_maxweight.py @@ -97,3 +97,40 @@ def test_group_max_weight_several_quantities(self): self._set_line(sale_form, self.product_3, 1.0) self.assertEqual(2, len(sale.picking_ids)) + + def test_split_at_creation(self): + """Test that a SO with 2 lines that will exceed the max weight if set into + the same picking, will be split""" + self.product.weight = 6.0 + self.product_2.weight = 3.0 + # the max weight is 8.0 + # we create a SO with 2 lines of 1.0 each -> + # 2 pickings will be created since the weight is 9.0 + # the first picking will have a weight of 6.0 + sale = self._get_new_sale_order(amount=1.0) + with Form(sale) as sale_form: + self._set_line(sale_form, self.product_2, 1.0) + sale.action_confirm() + self.assertEqual(2, len(sale.picking_ids)) + + def test_no_split_if_one_move_exceed(self): + """ + If the picking contains a move that exceed the max weight, the picking + is not split + """ + self.product.weight = 6.0 + sale = self._get_new_sale_order(amount=3.0) + sale.action_confirm() + self.assertEqual(1, len(sale.picking_ids)) + + def test_multi_split_at_creation(self): + self.product.weight = 6.0 + self.product_2.weight = 3.0 + self.product_3.weight = 3.0 + sale = self._get_new_sale_order(amount=1.0) + with Form(sale) as sale_form: + self._set_line(sale_form, self.product_2, 2.0) + self._set_line(sale_form, self.product_3, 2.0) + + sale.action_confirm() + self.assertEqual(3, len(sale.picking_ids))