diff --git a/modules/account/exceptions.py b/modules/account/exceptions.py index a491d34dd7..ec51d4526e 100644 --- a/modules/account/exceptions.py +++ b/modules/account/exceptions.py @@ -33,10 +33,6 @@ class FiscalYearValidationError(ValidationError): pass -class FiscalYearDatesError(FiscalYearValidationError): - pass - - class FiscalYearSequenceError(FiscalYearValidationError): pass diff --git a/modules/account/fiscalyear.py b/modules/account/fiscalyear.py index a34a0edc60..6c948540b1 100644 --- a/modules/account/fiscalyear.py +++ b/modules/account/fiscalyear.py @@ -2,21 +2,26 @@ # this repository contains the full copyright notices and license terms. from dateutil.relativedelta import relativedelta +from sql import Null +from sql.conditionals import Case +from sql.operators import Equal, NotEqual from trytond.cache import Cache from trytond.i18n import gettext -from trytond.model import ModelSQL, ModelView, Workflow, fields +from trytond.model import Exclude, ModelSQL, ModelView, Workflow, fields from trytond.model.exceptions import AccessError from trytond.pool import Pool from trytond.pyson import Eval, Id from trytond.rpc import RPC +from trytond.sql.functions import DateRange +from trytond.sql.operators import RangeOverlap from trytond.transaction import Transaction from trytond.wizard import ( Button, StateAction, StateTransition, StateView, Wizard) from .exceptions import ( - FiscalYearCloseError, FiscalYearDatesError, FiscalYearNotFoundError, - FiscalYearReOpenError, FiscalYearSequenceError) + FiscalYearCloseError, FiscalYearNotFoundError, FiscalYearReOpenError, + FiscalYearSequenceError) STATES = { 'readonly': Eval('state') != 'open', @@ -57,6 +62,24 @@ class FiscalYear(Workflow, ModelSQL, ModelView): @classmethod def __setup__(cls): super(FiscalYear, cls).__setup__() + t = cls.__table__() + cls._sql_constraints += [ + ('dates_overlap', + Exclude(t, + (t.company, Equal), + (DateRange(t.start_date, t.end_date, '[]'), RangeOverlap), + ), + 'account.msg_fiscalyear_overlap'), + ('open_earlier', + Exclude(t, + (t.company, Equal), + (DateRange( + Case( + (t.state == 'open', t.start_date), else_=Null), + t.end_date), RangeOverlap), + (Case((t.state == 'open', t.id), else_=-1), NotEqual)), + 'account.msg_open_fiscalyear_earlier'), + ] cls._order.insert(0, ('start_date', 'DESC')) cls._transitions |= set(( ('open', 'closed'), @@ -113,52 +136,9 @@ class FiscalYear(Workflow, ModelSQL, ModelView): @classmethod def validate_fields(cls, fiscalyears, field_names): super().validate_fields(fiscalyears, field_names) - cls.check_dates(fiscalyears, field_names) cls.check_post_move_sequence(fiscalyears, field_names) cls.check_period_dates(fiscalyears, field_names) - @classmethod - def check_dates(cls, fiscalyears, field_names=None): - if field_names and not (field_names & { - 'start_date', 'end_date', 'state', 'company'}): - return - transaction = Transaction() - connection = transaction.connection - cls.lock() - cursor = connection.cursor() - table = cls.__table__() - for year in fiscalyears: - cursor.execute(*table.select(table.id, - where=(((table.start_date <= year.start_date) - & (table.end_date >= year.start_date)) - | ((table.start_date <= year.end_date) - & (table.end_date >= year.end_date)) - | ((table.start_date >= year.start_date) - & (table.end_date <= year.end_date))) - & (table.company == year.company.id) - & (table.id != year.id))) - second_id = cursor.fetchone() - if second_id: - second = cls(second_id[0]) - raise FiscalYearDatesError( - gettext('account.msg_fiscalyear_overlap', - first=year.rec_name, - second=second.rec_name)) - if year.state == 'open': - fiscalyears = cls.search([ - ('start_date', '>=', year.end_date), - ('state', 'in', ['closed', 'locked']), - ('company', '=', year.company.id), - ], - limit=1, - order=[('start_date', 'ASC')]) - if fiscalyears: - fiscalyear, = fiscalyears - raise FiscalYearDatesError( - gettext('account.msg_open_fiscalyear_earlier', - open=year.rec_name, - closed=fiscalyear.rec_name)) - @classmethod def check_post_move_sequence(cls, fiscalyears, field_names=None): if field_names and 'post_move_sequence' not in field_names: diff --git a/modules/account/message.xml b/modules/account/message.xml index 4c1d162443..cf80cc7926 100644 --- a/modules/account/message.xml +++ b/modules/account/message.xml @@ -21,8 +21,8 @@ this repository contains the full copyright notices and license terms. --> To close period "%(period)s" you must post the moves "%(moves)s". - - The periods "%(first)s" and "%(second)s" overlap, you must use different dates. + + The dates of standard period of the same fiscal year can not overlap. You cannot use the same post move sequence for the periods "%(first)s" and "%(second)s" because they are from different fiscal years. @@ -64,10 +64,10 @@ this repository contains the full copyright notices and license terms. --> To continue, you must reopen the fiscal year "%(fiscalyear)s" of company "%(company)s" for the date "%(date)s". - The fiscal years "%(first)s" and "%(second)s" overlap, you must use different dates. + The dates of fiscal years of the same company can not overlap. - The open fiscal year "%(open)s" can not be before the close fiscal year "%(closed)s". + Open fiscal years can not be before closed fiscal years. The fiscal years "%(first)s" and "%(second)s" cannot have the same post move sequence, you must use different sequences. diff --git a/modules/account/period.py b/modules/account/period.py index 6bc7d0bb9f..95e10791bc 100644 --- a/modules/account/period.py +++ b/modules/account/period.py @@ -1,13 +1,17 @@ # 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.operators import Equal + from trytond.cache import Cache from trytond.const import OPERATORS from trytond.i18n import gettext -from trytond.model import Index, ModelSQL, ModelView, Workflow, fields +from trytond.model import Exclude, Index, ModelSQL, ModelView, Workflow, fields from trytond.model.exceptions import AccessError from trytond.pool import Pool from trytond.pyson import Eval, Id +from trytond.sql.functions import DateRange +from trytond.sql.operators import RangeOverlap from trytond.tools import grouped_slice from trytond.transaction import Transaction @@ -59,6 +63,14 @@ class Period(Workflow, ModelSQL, ModelView): super(Period, cls).__setup__() t = cls.__table__() cls.__access__.add('fiscalyear') + cls._sql_constraints += [ + ('standard_dates_overlap', + Exclude(t, + (t.fiscalyear, Equal), + (DateRange(t.start_date, t.end_date, '[]'), RangeOverlap), + where=t.type == 'standard'), + 'account.msg_period_standard_overlap'), + ] cls._sql_indexes.add( Index( t, @@ -121,43 +133,10 @@ class Period(Workflow, ModelSQL, ModelView): @classmethod def validate_fields(cls, periods, field_names): super().validate_fields(periods, field_names) - cls.check_dates(periods, field_names) cls.check_fiscalyear_dates(periods, field_names) cls.check_move_dates(periods, field_names) cls.check_post_move_sequence(periods, field_names) - @classmethod - def check_dates(cls, periods, field_names=None): - if field_names and not ( - field_names & { - 'start_date', 'end_date', 'fiscalyear', 'type'}): - return - transaction = Transaction() - connection = transaction.connection - cls.lock() - table = cls.__table__() - cursor = connection.cursor() - for period in periods: - if period.type != 'standard': - continue - cursor.execute(*table.select(table.id, - where=(((table.start_date <= period.start_date) - & (table.end_date >= period.start_date)) - | ((table.start_date <= period.end_date) - & (table.end_date >= period.end_date)) - | ((table.start_date >= period.start_date) - & (table.end_date <= period.end_date))) - & (table.fiscalyear == period.fiscalyear.id) - & (table.type == 'standard') - & (table.id != period.id))) - period_id = cursor.fetchone() - if period_id: - overlapping_period = cls(period_id[0]) - raise PeriodDatesError( - gettext('account.msg_period_overlap', - first=period.rec_name, - second=overlapping_period.rec_name)) - @classmethod def check_fiscalyear_dates(cls, periods, field_names=None): if field_names and not ( diff --git a/modules/account/tests/test_module.py b/modules/account/tests/test_module.py index fefeda546f..785a66bfc8 100644 --- a/modules/account/tests/test_module.py +++ b/modules/account/tests/test_module.py @@ -7,8 +7,8 @@ from decimal import Decimal from dateutil.relativedelta import relativedelta from trytond.exceptions import UserError -from trytond.modules.account.exceptions import ( - FiscalYearDatesError, PeriodDatesError) +from trytond.model.exceptions import SQLConstraintError +from trytond.modules.account.exceptions import PeriodDatesError from trytond.modules.account.tax import TaxableMixin from trytond.modules.company.tests import ( CompanyTestMixin, PartyCompanyCheckEraseMixin, create_company, set_company) @@ -309,7 +309,7 @@ class AccountTestCase( company, start_date=datetime.date(2021, 1, 1), end_date=datetime.date(2021, 12, 31)) - with self.assertRaises(FiscalYearDatesError): + with self.assertRaises(SQLConstraintError): earlier_fiscalyear.save() @with_transaction()