From 972ff712ef4d67455fb907f87bff99789226bde2 Mon Sep 17 00:00:00 2001 From: Bruno Zanotti Date: Wed, 30 Nov 2022 15:09:49 -0300 Subject: [PATCH] [IMP] stock_split_picking: new option to keep sml --- stock_split_picking/models/__init__.py | 1 + stock_split_picking/models/stock_move_line.py | 106 ++++++++++++++++++ stock_split_picking/models/stock_picking.py | 47 ++++---- .../wizards/stock_split_picking.py | 7 +- 4 files changed, 140 insertions(+), 21 deletions(-) create mode 100644 stock_split_picking/models/stock_move_line.py diff --git a/stock_split_picking/models/__init__.py b/stock_split_picking/models/__init__.py index bcd13c36825a..80f943386a34 100644 --- a/stock_split_picking/models/__init__.py +++ b/stock_split_picking/models/__init__.py @@ -2,3 +2,4 @@ # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). from . import stock_picking +from . import stock_move_line diff --git a/stock_split_picking/models/stock_move_line.py b/stock_split_picking/models/stock_move_line.py new file mode 100644 index 000000000000..6559c14a828d --- /dev/null +++ b/stock_split_picking/models/stock_move_line.py @@ -0,0 +1,106 @@ +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from odoo import _, models +from odoo.exceptions import UserError +from odoo.tools.float_utils import float_compare, float_is_zero, float_round + + +class StockMoveLine(models.Model): + + _inherit = "stock.move.line" + + def _prepare_move_split_vals(self, qty, move_id): + """ Values to be set in the new move line. + We set the qty_done so the new line keeps the done quantities. + """ + vals = { + "product_uom_qty": qty, + "qty_done": self.qty_done, + "move_id": move_id, + } + if self.env.context.get("force_split_uom_id"): + vals["product_uom_id"] = self.env.context["force_split_uom_id"] + return vals + + def _get_original_move_vals(self, qty): + decimal_precision = self.env["decimal.precision"].precision_get( + "Product Unit of Measure" + ) + new_product_qty = self.product_id.uom_id._compute_quantity( + self.product_qty - qty, self.product_uom_id, round=False + ) + new_product_qty = float_round( + new_product_qty, precision_digits=decimal_precision + ) + return { + "product_uom_qty": new_product_qty, + "qty_done": 0.0, + } + + def _split(self, qty, move_id): + """ Splits a stock move line with a defined qty and move to a new stock move + + :param qty: float. quantity to split (given in product UoM) + :returns: id of the backorder move line created """ + self.ensure_one() + self = self.with_prefetch() + # This makes the ORM only look for one record and not 300 at a time, + # which improves performance + if self.state in ("done", "cancel"): + raise UserError( + _("You cannot split a stock move line that has been set to 'Done'.") + ) + elif self.state == "draft": + # we restrict the split of a draft line because if not confirmed yet. + raise UserError( + _("You cannot split a draft line. It needs to be confirmed first.") + ) + if ( + float_is_zero(qty, precision_rounding=self.product_id.uom_id.rounding) + or self.product_qty <= qty + ): + self.move_id = move_id + return self.id + + decimal_precision = self.env["decimal.precision"].precision_get( + "Product Unit of Measure" + ) + + # `qty` passed as argument is the quantity to backorder and is always expressed in the + # quants UOM. + uom_qty = self.product_id.uom_id._compute_quantity( + qty, self.product_uom_id, rounding_method="HALF-UP" + ) + # TODO: usamos precision_digits porque le copiamos a odoo con stock move, + # pero quizás debería ser: precision_rounding=self.product_id.uom_id.rounding + if ( + float_compare( + qty, + self.product_uom_id._compute_quantity( + uom_qty, self.product_id.uom_id, rounding_method="HALF-UP" + ), + precision_digits=decimal_precision, + ) + == 0 + ): + defaults = self._prepare_move_split_vals(uom_qty, move_id) + else: + defaults = self.with_context( + force_split_uom_id=self.product_id.uom_id.id + )._prepare_move_split_vals(qty, move_id) + + ml = self.with_context(rounding_method="HALF-UP").copy(defaults) + self.write(self._get_original_move_vals(qty)) + + # We need to update quants reservations because it is not + # properly updated when copy a line + self.env["stock.quant"]._update_reserved_quantity( + ml.product_id, + ml.location_id, + ml.product_qty, + lot_id=ml.lot_id, + package_id=ml.package_id, + owner_id=ml.owner_id, + strict=True, + ) + return ml.id diff --git a/stock_split_picking/models/stock_picking.py b/stock_split_picking/models/stock_picking.py index d0b9952c3f10..27f8f5881e30 100644 --- a/stock_split_picking/models/stock_picking.py +++ b/stock_split_picking/models/stock_picking.py @@ -4,7 +4,6 @@ from odoo import _, models from odoo.exceptions import UserError -from odoo.tools.float_utils import float_compare class StockPicking(models.Model): @@ -12,7 +11,7 @@ class StockPicking(models.Model): _inherit = "stock.picking" - def split_process(self): + def split_process(self, keep_lines=False): """Use to trigger the wizard from button with correct context""" for picking in self: @@ -29,37 +28,45 @@ def split_process(self): # Split moves considering the qty_done on moves new_moves = self.env["stock.move"] + new_move_lines = self.env["stock.move.line"] for move in picking.move_lines: - rounding = move.product_uom.rounding qty_done = move.quantity_done - qty_initial = move.product_uom_qty - qty_diff_compare = float_compare( - qty_done, qty_initial, precision_rounding=rounding - ) - if qty_diff_compare < 0: - qty_split = qty_initial - qty_done + if qty_done: qty_uom_split = move.product_uom._compute_quantity( - qty_split, move.product_id.uom_id, rounding_method="HALF-UP" + qty_done, move.product_id.uom_id, rounding_method="HALF-UP" ) new_move_id = move._split(qty_uom_split) for move_line in move.move_line_ids: if move_line.product_qty and move_line.qty_done: - # To avoid an error - # when picking is partially available - try: - move_line.write({"product_uom_qty": move_line.qty_done}) - except UserError: - pass + if keep_lines: + new_move_line_id = move_line._split( + move_line.qty_done, new_move_id + ) + new_move_lines |= self.env["stock.move.line"].browse( + new_move_line_id + ) + else: + # To avoid an error + # when picking is partially available + try: + move_line.write( + {"product_uom_qty": move_line.qty_done} + ) + except UserError: + pass + new_moves |= self.env["stock.move"].browse(new_move_id) # If we have new moves to move, create the backorder picking if new_moves: backorder_picking = picking._create_split_backorder() new_moves.write({"picking_id": backorder_picking.id}) - new_moves.mapped("move_line_ids").write( - {"picking_id": backorder_picking.id} - ) - new_moves._action_assign() + if new_move_lines: + new_move_lines.write({"picking_id": backorder_picking.id}) + new_moves._recompute_state() + else: + new_moves._action_assign() + return backorder_picking def _create_split_backorder(self, default=None): """Copy current picking with defaults passed, post message about diff --git a/stock_split_picking/wizards/stock_split_picking.py b/stock_split_picking/wizards/stock_split_picking.py index 278b79cc6165..869aa7116a1d 100644 --- a/stock_split_picking/wizards/stock_split_picking.py +++ b/stock_split_picking/wizards/stock_split_picking.py @@ -12,7 +12,8 @@ class StockSplitPicking(models.TransientModel): [ ("done", "Done quantities"), ("move", "One picking per move"), - ("selection", "Select move lines to split off"), + ("selection", "Select stock move to split off"), + ("lines_done", "Done quantities keeping move lines"), ], required=True, default="done", @@ -32,6 +33,10 @@ def action_apply(self): def _apply_done(self): return self.mapped("picking_ids").split_process() + def _apply_lines_done(self): + """Move to a new picking the qty done keeping the stock move lines""" + return self.mapped("picking_ids").split_process(keep_lines=True) + def _apply_move(self): """Create new pickings for every move line, keep first move line in original picking