From 39b05c125967f55d6a32b9230355491ec4ec9f22 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Krier?= Date: Mon, 26 Aug 2024 19:02:39 +0200 Subject: [PATCH] Replace order point uniqueness validation by SQL constraints Closes #13476 --- modules/stock_supply/exceptions.py | 5 -- modules/stock_supply/order_point.py | 102 +++++++---------------- modules/stock_supply_production/stock.py | 9 -- 3 files changed, 28 insertions(+), 88 deletions(-) diff --git a/modules/stock_supply/exceptions.py b/modules/stock_supply/exceptions.py index 887d396e96..3ba1bb4684 100644 --- a/modules/stock_supply/exceptions.py +++ b/modules/stock_supply/exceptions.py @@ -1,12 +1,7 @@ # This file is part of Tryton. The COPYRIGHT file at the top level of # this repository contains the full copyright notices and license terms. from trytond.exceptions import UserWarning -from trytond.model.exceptions import ValidationError class SupplyWarning(UserWarning): pass - - -class OrderPointValidationError(ValidationError): - pass diff --git a/modules/stock_supply/order_point.py b/modules/stock_supply/order_point.py index f0966f2ed8..86a5663a02 100644 --- a/modules/stock_supply/order_point.py +++ b/modules/stock_supply/order_point.py @@ -1,16 +1,16 @@ # This file is part of Tryton. The COPYRIGHT file at the top level of # this repository contains the full copyright notices and license terms. -from sql import Null -from trytond.i18n import gettext -from trytond.model import ModelSQL, ModelView, fields +from sql import Null +from sql.conditionals import Greatest, Least +from sql.operators import Equal + +from trytond.model import Exclude, ModelSQL, ModelView, Unique, fields from trytond.pool import Pool from trytond.pyson import Eval, If from trytond.transaction import Transaction -from .exceptions import OrderPointValidationError - class OrderPoint(ModelSQL, ModelView): """ @@ -95,6 +95,29 @@ class OrderPoint(ModelSQL, ModelView): company = fields.Many2One('company.company', 'Company', required=True) unit = fields.Function(fields.Many2One('product.uom', 'Unit'), 'get_unit') + @classmethod + def __setup__(cls): + super().__setup__() + + t = cls.__table__() + for location_name in [ + 'provisioning_location', 'overflowing_location']: + column = getattr(t, location_name) + cls._sql_constraints.append( + ('concurrent_%s_internal' % location_name, + Exclude(t, + (t.product, Equal), + (Greatest(t.location, column), Equal), + (Least(t.location, column), Equal), + (t.company, Equal), + where=t.type == 'internal'), + 'stock_supply.msg_order_point_concurrent_%s_internal' % + location_name)) + cls._sql_constraints.append( + ('product_location_purchase_unique', + Unique(t, t.product, t.location, t.company), + 'stock_supply.msg_order_point_unique')) + @classmethod def __register__(cls, module): table = cls.__table__() @@ -145,75 +168,6 @@ class OrderPoint(ModelSQL, ModelView): def get_unit(self, name): return self.product.default_uom.id - @classmethod - def validate(cls, orderpoints): - super(OrderPoint, cls).validate(orderpoints) - cls.check_concurrent_internal(orderpoints) - cls.check_uniqueness(orderpoints) - - @classmethod - def check_concurrent_internal(cls, orders): - """ - Ensure that there is no 'concurrent' internal order - points. I.E. no two order point with opposite location for the - same product and same company. - """ - internals = cls.browse([o for o in orders if o.type == 'internal']) - if not internals: - return - - for location_name in [ - 'provisioning_location', 'overflowing_location']: - query = [] - for op in internals: - if getattr(op, location_name, None) is None: - continue - arg = ['AND', - ('product', '=', op.product.id), - (location_name, '=', op.location.id), - ('location', '=', - getattr(op, location_name).id), - ('company', '=', op.company.id), - ('type', '=', 'internal')] - query.append(arg) - if query and cls.search(['OR'] + query): - raise OrderPointValidationError( - gettext('stock_supply' - '.msg_order_point_concurrent_%s_internal' % - location_name)) - - @staticmethod - def _type2field(type=None): - t2f = { - 'purchase': 'location', - 'internal': 'location', - } - if type is None: - return t2f - else: - return t2f[type] - - @classmethod - def check_uniqueness(cls, orders): - """ - Ensure uniqueness of order points. I.E that there is no several - order point for the same location, the same product and the - same company. - """ - query = ['OR'] - for op in orders: - field = cls._type2field(op.type) - arg = ['AND', - ('product', '=', op.product.id), - (field, '=', getattr(op, field).id), - ('id', '!=', op.id), - ('company', '=', op.company.id), - ] - query.append(arg) - if cls.search(query): - raise OrderPointValidationError( - gettext('stock_supply.msg_order_point_unique')) - @property def warehouse_location(self): if self.type == 'purchase': diff --git a/modules/stock_supply_production/stock.py b/modules/stock_supply_production/stock.py index ebc8de013e..4f3296e83c 100644 --- a/modules/stock_supply_production/stock.py +++ b/modules/stock_supply_production/stock.py @@ -28,15 +28,6 @@ class OrderPoint(metaclass=PoolMeta): if option not in cls.type.selection: cls.type.selection.append(option) - @classmethod - def _type2field(cls, type=None): - if type == 'production': - return 'location' - result = super(OrderPoint, cls)._type2field(type=type) - if type is None: - result['production'] = 'location' - return result - @property def warehouse_location(self): location = super().warehouse_location