From 5a13075c617633fffbd9d734dba4c64432712d15 Mon Sep 17 00:00:00 2001 From: "Laurent Mignon (ACSONE)" Date: Tue, 12 Dec 2023 13:56:56 +0100 Subject: [PATCH] [IMP] stock_picking_group_by_max_weight: Split if max weight exceed 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 commit 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 commit 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. --- stock_picking_group_by_max_weight/README.rst | 2 +- .../models/stock_move.py | 4 ++ .../models/stock_picking.py | 69 ++++++++++++++++++- .../readme/newsfragments/.gitignore | 0 .../readme/newsfragments/1451.feature | 9 +++ .../static/description/index.html | 2 +- .../tests/test_group_maxweight.py | 37 ++++++++++ 7 files changed, 118 insertions(+), 5 deletions(-) create mode 100644 stock_picking_group_by_max_weight/readme/newsfragments/.gitignore create mode 100644 stock_picking_group_by_max_weight/readme/newsfragments/1451.feature 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))