diff --git a/modules/stock_forecast/exceptions.py b/modules/stock_forecast/exceptions.py deleted file mode 100644 index 82c4646c45..0000000000 --- a/modules/stock_forecast/exceptions.py +++ /dev/null @@ -1,8 +0,0 @@ -# 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.model.exceptions import ValidationError - - -class ForecastValidationError(ValidationError): - pass diff --git a/modules/stock_forecast/forecast.py b/modules/stock_forecast/forecast.py index 0f4cd172be..ac56391b84 100644 --- a/modules/stock_forecast/forecast.py +++ b/modules/stock_forecast/forecast.py @@ -8,19 +8,21 @@ from dateutil.relativedelta import relativedelta from sql import Null from sql.aggregate import Sum from sql.conditionals import Coalesce +from sql.operators import Equal from trytond import backend from trytond.i18n import gettext -from trytond.model import Index, ModelSQL, ModelView, Unique, Workflow, fields +from trytond.model import ( + Exclude, Index, ModelSQL, ModelView, Unique, Workflow, fields) from trytond.model.exceptions import AccessError from trytond.pool import Pool from trytond.pyson import Bool, Eval, If +from trytond.sql.functions import DateRange +from trytond.sql.operators import RangeOverlap from trytond.tools import grouped_slice, reduce_ids from trytond.transaction import Transaction from trytond.wizard import Button, StateTransition, StateView, Wizard -from .exceptions import ForecastValidationError - class Forecast(Workflow, ModelSQL, ModelView): "Stock Forecast" @@ -66,6 +68,16 @@ class Forecast(Workflow, ModelSQL, ModelView): def __setup__(cls): super(Forecast, cls).__setup__() t = cls.__table__() + cls._sql_constraints += [ + ('dates_done_overlap', + Exclude(t, + (t.company, Equal), + (t.warehouse, Equal), + (t.destination, Equal), + (DateRange(t.from_date, t.to_date, '[]'), RangeOverlap), + where=t.state == 'done'), + 'stock_forecast.msg_forecast_done_dates_overlap'), + ] cls._sql_indexes.add( Index( t, @@ -188,46 +200,6 @@ class Forecast(Workflow, ModelSQL, ModelView): ('destination.rec_name', *clause[1:]), ] - @classmethod - def validate_fields(cls, forecasts, field_names): - super().validate_fields(forecasts, field_names) - cls.check_date_overlap(forecasts, field_names) - - @classmethod - def check_date_overlap(cls, forecasts, field_names=None): - if field_names and not (field_names & { - 'from_date', 'to_date', - 'warehouse', 'destination', - 'company', 'state'}): - return - transaction = Transaction() - connection = transaction.connection - cls.lock() - table = cls.__table__() - cursor = connection.cursor() - for forecast in forecasts: - if forecast.state != 'done': - continue - cursor.execute(*table.select(table.id, - where=(((table.from_date <= forecast.from_date) - & (table.to_date >= forecast.from_date)) - | ((table.from_date <= forecast.to_date) - & (table.to_date >= forecast.to_date)) - | ((table.from_date >= forecast.from_date) - & (table.to_date <= forecast.to_date))) - & (table.warehouse == forecast.warehouse.id) - & (table.destination == forecast.destination.id) - & (table.company == forecast.company.id) - & (table.state == 'done') - & (table.id != forecast.id))) - forecast_id = cursor.fetchone() - if forecast_id: - second = cls(forecast_id[0]) - raise ForecastValidationError( - gettext('stock_forecast.msg_forecast_date_overlap', - first=forecast.rec_name, - second=second.rec_name)) - @classmethod def delete(cls, forecasts): # Cancel before delete diff --git a/modules/stock_forecast/message.xml b/modules/stock_forecast/message.xml index e02b4f6b65..810edda170 100644 --- a/modules/stock_forecast/message.xml +++ b/modules/stock_forecast/message.xml @@ -3,8 +3,8 @@ this repository contains the full copyright notices and license terms. --> - - The forecasts "%(first)s" and "%(second)s" overlap, you must use different dates. + + The dates of done forecasts for the same company, warehouse and destination can not overlap. To delete forecast "%(forecast)s" you must cancel it.