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()