From e0c1369ebf427b0abf110b73641c641f91d6c2eb Mon Sep 17 00:00:00 2001 From: Virginia Date: Thu, 29 Jan 2026 16:41:19 -0300 Subject: [PATCH 1/2] [IMP] stock_ux: improve superuser handling for quantity constraints --- stock_ux/models/stock_move.py | 34 +++++------------------------- stock_ux/models/stock_move_line.py | 24 +++++++++++++++------ stock_ux/models/stock_picking.py | 21 ++++++++++++++++++ 3 files changed, 43 insertions(+), 36 deletions(-) diff --git a/stock_ux/models/stock_move.py b/stock_ux/models/stock_move.py index 09a3b9687..d211ee229 100644 --- a/stock_ux/models/stock_move.py +++ b/stock_ux/models/stock_move.py @@ -2,9 +2,8 @@ # For copyright and license notices, see __manifest__.py file in module root # directory ############################################################################## -from odoo import _, api, fields, models +from odoo import api, fields, models from odoo.exceptions import UserError, ValidationError -from odoo.tools import float_compare class StockMove(models.Model): @@ -53,35 +52,12 @@ def _compute_origin_description(self): @api.constrains("quantity") def _check_quantity(self): - precision = self.env["decimal.precision"].precision_get("Product Unit of Measure") - # Si tenemos este contexto es porque si o si viene de una compra - if "previous_product_qty" in self.env.context: - return super()._check_quantity() - if any(self.filtered(lambda x: x.scrapped)): - return super()._check_quantity() - moves = self.filtered( - lambda x: ( - x.picking_id.picking_type_id.block_additional_quantity - and float_compare(x.product_uom_qty, x.quantity, precision_digits=precision) == -1 - ) - ) - if not moves: - return super()._check_quantity() - # Si lo ejecuta el superusuario (scheduler), revertir el cambio y loguear - if self.env.is_superuser(): - for move in moves: - # Revertir el cambio de quantity - move.quantity = move.product_uom_qty - move.picking_id.message_post( - body=_( - "Se intentó transferir una cantidad mayor a la demanda inicial en el movimiento %s durante la ejecución automática (scheduler). El sistema ignoró el cambio y mantuvo la cantidad original." - ) - % move.display_name - ) + """Basic quantity validation. Main validation is done in picking.button_validate().""" + # Excepciones donde no validamos + if self.env.context.get("previous_product_qty") or self.filtered("scrapped"): return super()._check_quantity() - # Comportamiento normal: raise si corresponde - raise ValidationError(_("You can not transfer more than the initial demand!")) + return super()._check_quantity() def action_view_linked_record(self): """This function returns an action that display existing sales order diff --git a/stock_ux/models/stock_move_line.py b/stock_ux/models/stock_move_line.py index 9d1e0bd1e..c9f1027be 100644 --- a/stock_ux/models/stock_move_line.py +++ b/stock_ux/models/stock_move_line.py @@ -71,18 +71,29 @@ def _check_manual_lines(self): if not invalid_lines: return - # Si lo ejecuta el superusuario (odoobot), revertir el cambio y loguear + # Si lo ejecuta el superusuario (scheduler) if self.env.is_superuser(): + pickings_to_reassign = self.env["stock.picking"] + for line in invalid_lines: - # Revertir el cambio de quantity - line.quantity = max(0, line._check_quantity_available() + line.quantity) + # Resetear a 0 para liberar la reserva incorrecta + line.quantity = 0 + pickings_to_reassign |= line.picking_id + if line.picking_id: line.picking_id.message_post( body=_( - "Se intentó transferir una cantidad mayor al stock disponible en la línea %s durante la ejecución automática (odoobot/scheduler). El sistema ignoró el cambio y mantuvo la cantidad original." + "Se intentó transferir una cantidad mayor al stock disponible " + "en la línea %s durante la ejecución automática (scheduler). " + "El sistema reseteo la cantidad a 0 y re-reservó automáticamente." ) % line.display_name ) + + # Re-reservar automáticamente todos los pickings afectados + if pickings_to_reassign: + pickings_to_reassign.action_assign() + return raise ValidationError(_("You can't transfer more quantity than the quantity on stock!")) @@ -129,7 +140,7 @@ def _get_aggregated_product_quantities(self, **kwargs): move_line_by_move = {} for sml in self: move = sml.move_id - if move and move.origin_description and sml.picking_id.origin: + if move and move.origin_description: move_line_by_move.setdefault( move.id, {"description": move.origin_description, "product_id": sml.product_id.id} ) @@ -157,8 +168,7 @@ def _get_aggregated_properties(self, move_line=False, move=False): use_origin = ( self.env["ir.config_parameter"].sudo().get_param("stock_ux.delivery_slip_use_origin", "False") == "True" ) - picking = move_line.picking_id if move_line else (move.picking_id if move else False) - if use_origin and picking and picking.origin: + if use_origin: move = move or move_line.move_id uom = move.product_uom or move_line.product_uom_id name = move.product_id.display_name diff --git a/stock_ux/models/stock_picking.py b/stock_ux/models/stock_picking.py index e6e8442b1..4a73bcf7c 100644 --- a/stock_ux/models/stock_picking.py +++ b/stock_ux/models/stock_picking.py @@ -152,3 +152,24 @@ def write(self, vals): ) ) return super().write(vals) + + def button_validate(self): + """Valida que no se transfiera más de la demanda inicial.""" + for picking in self: + if picking.picking_type_id.block_additional_quantity: + precision = self.env["decimal.precision"].precision_get("Product Unit of Measure") + for move in picking.move_ids.filtered(lambda m: m.state not in ("draft", "cancel")): + if float_compare(move.quantity, move.product_uom_qty, precision_digits=precision) == 1: + raise UserError( + _( + "Cannot transfer more than initial demand!\n\n" + "Product: %(product)s\n" + "Initial Demand: %(demand)s\n" + "Attempted Transfer: %(quantity)s\n\n" + "Please update the source document (Purchase/Sales Order) to increase quantities.", + product=move.product_id.display_name, + demand=move.product_uom_qty, + quantity=move.quantity, + ) + ) + return super().button_validate() From 43b27104efe10bd5720bb9d35fc4f1afc9d1f140 Mon Sep 17 00:00:00 2001 From: Juan Ignacio Carreras Date: Tue, 10 Feb 2026 14:00:56 +0000 Subject: [PATCH 2/2] [FIX] stock_ux: improve quantity constraint handling --- stock_ux/models/stock_move_line.py | 24 +++++++----------------- stock_ux/models/stock_picking.py | 1 + 2 files changed, 8 insertions(+), 17 deletions(-) diff --git a/stock_ux/models/stock_move_line.py b/stock_ux/models/stock_move_line.py index c9f1027be..9d1e0bd1e 100644 --- a/stock_ux/models/stock_move_line.py +++ b/stock_ux/models/stock_move_line.py @@ -71,29 +71,18 @@ def _check_manual_lines(self): if not invalid_lines: return - # Si lo ejecuta el superusuario (scheduler) + # Si lo ejecuta el superusuario (odoobot), revertir el cambio y loguear if self.env.is_superuser(): - pickings_to_reassign = self.env["stock.picking"] - for line in invalid_lines: - # Resetear a 0 para liberar la reserva incorrecta - line.quantity = 0 - pickings_to_reassign |= line.picking_id - + # Revertir el cambio de quantity + line.quantity = max(0, line._check_quantity_available() + line.quantity) if line.picking_id: line.picking_id.message_post( body=_( - "Se intentó transferir una cantidad mayor al stock disponible " - "en la línea %s durante la ejecución automática (scheduler). " - "El sistema reseteo la cantidad a 0 y re-reservó automáticamente." + "Se intentó transferir una cantidad mayor al stock disponible en la línea %s durante la ejecución automática (odoobot/scheduler). El sistema ignoró el cambio y mantuvo la cantidad original." ) % line.display_name ) - - # Re-reservar automáticamente todos los pickings afectados - if pickings_to_reassign: - pickings_to_reassign.action_assign() - return raise ValidationError(_("You can't transfer more quantity than the quantity on stock!")) @@ -140,7 +129,7 @@ def _get_aggregated_product_quantities(self, **kwargs): move_line_by_move = {} for sml in self: move = sml.move_id - if move and move.origin_description: + if move and move.origin_description and sml.picking_id.origin: move_line_by_move.setdefault( move.id, {"description": move.origin_description, "product_id": sml.product_id.id} ) @@ -168,7 +157,8 @@ def _get_aggregated_properties(self, move_line=False, move=False): use_origin = ( self.env["ir.config_parameter"].sudo().get_param("stock_ux.delivery_slip_use_origin", "False") == "True" ) - if use_origin: + picking = move_line.picking_id if move_line else (move.picking_id if move else False) + if use_origin and picking and picking.origin: move = move or move_line.move_id uom = move.product_uom or move_line.product_uom_id name = move.product_id.display_name diff --git a/stock_ux/models/stock_picking.py b/stock_ux/models/stock_picking.py index 4a73bcf7c..d6428ca26 100644 --- a/stock_ux/models/stock_picking.py +++ b/stock_ux/models/stock_picking.py @@ -5,6 +5,7 @@ ############################################################################## from odoo import models, fields, api, _ from odoo.exceptions import ValidationError, UserError +from odoo.tools.float_utils import float_compare class StockPicking(models.Model):