Commit 307470e2 authored by Jérome Perrin's avatar Jérome Perrin

accounting: Fix timezone issues when checking periods are consecutive

Opening an accounting period is refused if the start date of the period
is more than one day after the stop date of the previous period, but this
check did not take into account that "next day" might be more than 24
hours, like it's the case when daylight saving happen between these dates.
Instead we check that the difference is less than 1.9 days.

Reorganise tests to group accounting period related tests in a dedicated
test class and add missing tests for period validation checks.

Also fix a few race conditions with catalog indexing that are probably not
a problem in real life but were revealed by the test.
parent a016ed04
Pipeline #13176 failed with stage
in 0 seconds
......@@ -3372,25 +3372,6 @@ class TestTransactions(AccountingTestCase):
accounting_transaction.stop()
self.assertEqual('code-2001-1', accounting_transaction.getSourceReference())
def test_generate_sub_accounting_periods(self):
accounting_period_2007 = self.section.newContent(
portal_type='Accounting Period',
start_date=DateTime('2007/01/01'),
stop_date=DateTime('2007/12/31'),)
accounting_period_2007.start()
accounting_period_2007.AccountingPeriod_createSecondaryPeriod(
frequency='monthly', open_periods=1)
sub_period_list = sorted(accounting_period_2007.contentValues(),
key=lambda x:x.getStartDate())
self.assertEqual(12, len(sub_period_list))
first_period = sub_period_list[0]
self.assertEqual(DateTime(2007, 1, 1), first_period.getStartDate())
self.assertEqual(DateTime(2007, 1, 31), first_period.getStopDate())
self.assertEqual('2007-01', first_period.getShortTitle())
self.assertEqual('January', first_period.getTitle())
def test_SearchableText(self):
accounting_transaction = self._makeOne(title='A new Transaction',
description="A description",
......@@ -6123,3 +6104,70 @@ class TestAccountingAlarms(AccountingTestCase):
'{} has wrong grouping (4.0)'.format(payment.grouped_line.getRelativeUrl()),]),
sorted([x.getProperty('detail') for x in alarm.getLastActiveProcess().getResultList()]))
self.assertTrue(alarm.sense())
class TestAccountingPeriod(AccountingTestCase):
def test_generate_sub_accounting_periods(self):
accounting_period_2007 = self.section.newContent(
portal_type='Accounting Period',
start_date=DateTime('2007/01/01'),
stop_date=DateTime('2007/12/31'),)
accounting_period_2007.start()
accounting_period_2007.AccountingPeriod_createSecondaryPeriod(
frequency='monthly', open_periods=1)
sub_period_list = sorted(accounting_period_2007.contentValues(),
key=lambda x:x.getStartDate())
self.assertEqual(12, len(sub_period_list))
first_period = sub_period_list[0]
self.assertEqual(DateTime(2007, 1, 1), first_period.getStartDate())
self.assertEqual(DateTime(2007, 1, 31), first_period.getStopDate())
self.assertEqual('2007-01', first_period.getShortTitle())
self.assertEqual('January', first_period.getTitle())
def test_accounting_period_workflow_constraint(self):
first_accounting_period = self.section.newContent(
portal_type='Accounting Period',
start_date=DateTime('2021/01/01'),
stop_date=DateTime('2020/12/31'),)
with self.assertRaisesRegexp(ValidationFailed,
'Start date is after stop date'):
self.portal.portal_workflow.doActionFor(first_accounting_period, 'start_action')
# make first accounting period valid, for the full 2021 year
first_accounting_period.setStopDate(DateTime('2021/12/31'))
self.portal.portal_workflow.doActionFor(first_accounting_period, 'start_action')
self.tic()
# check dates don't overlap
second_accounting_period = self.section.newContent(
portal_type='Accounting Period',
start_date=DateTime('2021/01/01'),
stop_date=DateTime('2022/12/31'),)
with self.assertRaisesRegexp(ValidationFailed,
'2021/01/01 00:00:00 .* is already in an open accounting period.'):
self.portal.portal_workflow.doActionFor(second_accounting_period, 'start_action')
# check there are no "holes" between dates
second_accounting_period.setStartDate('2022/01/02')
with self.assertRaisesRegexp(ValidationFailed,
'Last opened period ends on 2021/12/31.*, this period starts on 2022/01/02.*. Accounting Periods must be consecutive.'):
self.portal.portal_workflow.doActionFor(second_accounting_period, 'start_action')
# edge case, when the end date of previous period is a DST swich, this should not block
first_accounting_period.setStopDate(DateTime('2021/10/31 00:00:00 Europe/Paris'))
second_accounting_period.setStartDate(DateTime('2021/11/01 00:00:00 Europe/Paris'))
self.portal.portal_workflow.doActionFor(second_accounting_period, 'start_action')
# reset first period to 2021 and second period 2022
first_accounting_period.setStopDate(DateTime('2021/12/31'))
second_accounting_period.setStartDate(DateTime('2022/01/01'))
second_accounting_period.setStopDate(DateTime('2022/12/31'))
# check also with more than 2 periods
third_accounting_period = self.section.newContent(
portal_type='Accounting Period',
start_date=DateTime('2023/01/01'),
stop_date=DateTime('2023/12/31'),)
self.portal.portal_workflow.doActionFor(third_accounting_period, 'start_action')
......@@ -12,27 +12,36 @@ stop_date = closing_period.getStopDate()
if start_date > stop_date:
raise ValidationFailed, translateString("Start date is after stop date.")
period_list = closing_period.getParentValue().searchFolder(
simulation_state=valid_state_list,
sort_on=[('delivery.start_date', 'asc')],
portal_type='Accounting Period')
period_list = [
x
for x in closing_period.getParentValue().contentValues(
portal_type="Accounting Period",
checked_permission="Access contents information",
)
if x.getSimulationState() in valid_state_list
]
for period in period_list:
period = period.getObject()
if period.getSimulationState() in valid_state_list:
if start_date <= period.getStopDate() and not stop_date <= period.getStartDate():
raise ValidationFailed, translateString(
"${date} is already in an open accounting period.",
mapping={'date': start_date})
if len(period_list) > 1:
last_period = period_list[-1].getObject()
if last_period.getId() == closing_period.getId():
last_period = period_list[-2].getObject()
if (start_date - last_period.getStopDate()) > 1:
previous_period = next(
iter(
sorted(
[x for x in period_list if x != closing_period],
key=lambda p: p.getStartDate(),
reverse=True,
)
),
None,
)
if previous_period is not None:
if (start_date - previous_period.getStopDate()) > 1.9:
raise ValidationFailed, translateString(
"Last opened period ends on ${last_openned_date},"+
" this period starts on ${this_period_start_date}."+
" Accounting Periods must be consecutive.",
mapping = { 'last_openned_date': last_period.getStopDate(),
mapping = { 'last_openned_date': previous_period.getStopDate(),
'this_period_start_date': start_date } )
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment