[FIX] project: recurring dates of monthly repeated task until date
Computing the recurrences of a task repeating every X months until a certain date generates too much dates Steps to reproduce: 1. Install Project 2. Go to Settings > Project > Tasks Management and enable Recurring Tasks 3. Open any project in the Project app and create a new task then edit it 4. Enable the Recurrent field of the task 5. In the Recurrence tab, edit: - Repeat Every: 6 Months - Until: End Date: one year from now 6. The recurrence message says there are 11 tasks but there should only be 2 Solution: Generate the recurrences until the `repeat_until` date is reached if the `repeat_type` is 'until', otherwise generate as much recurrences as the count Also relax the constraint on the `repeat_day` and `repeat_until` to not raise an error if `repeat_until` is the last day of the month Problem: The recurrence of a task with `repeat_unit` month and `repeat_interval` different than 1 with a `repeat_type` until creates too much tasks, exceeding the `repeat_until` opw-3076593 closes odoo/odoo#107910 X-original-commit: 236b383fd9d1ad6a5d39d8c4232b49864ec19b37 Signed-off-by: Guillaume Merlin (megu) <megu@odoo.com> Signed-off-by: Laurent Stukkens (ltu) <ltu@odoo.com>
This commit is contained in:
@@ -5137,7 +5137,7 @@ msgstr ""
|
||||
#. module: project
|
||||
#: code:addons/project/models/project_task_recurrence.py:0
|
||||
#, python-format
|
||||
msgid "The end date should be after the day of the month"
|
||||
msgid "The end date should be after the day of the month or the last day of the month"
|
||||
msgstr ""
|
||||
|
||||
#. module: project
|
||||
|
||||
@@ -8,8 +8,6 @@ from calendar import monthrange
|
||||
from dateutil.relativedelta import relativedelta
|
||||
from dateutil.rrule import rrule, rruleset, DAILY, WEEKLY, MONTHLY, YEARLY, MO, TU, WE, TH, FR, SA, SU
|
||||
|
||||
import math
|
||||
|
||||
MONTHS = {
|
||||
'january': 31,
|
||||
'february': 28,
|
||||
@@ -140,8 +138,9 @@ class ProjectTaskRecurrence(models.Model):
|
||||
|
||||
@api.constrains('repeat_unit', 'repeat_on_month', 'repeat_day', 'repeat_type', 'repeat_until')
|
||||
def _check_repeat_until_month(self):
|
||||
if self.filtered(lambda r: r.repeat_type == 'until' and r.repeat_unit == 'month' and r.repeat_until and r.repeat_on_month == 'date' and int(r.repeat_day) > r.repeat_until.day):
|
||||
raise ValidationError(_('The end date should be after the day of the month'))
|
||||
if self.filtered(lambda r: r.repeat_type == 'until' and r.repeat_unit == 'month' and r.repeat_until and r.repeat_on_month == 'date'
|
||||
and int(r.repeat_day) > r.repeat_until.day and monthrange(r.repeat_until.year, r.repeat_until.month)[1] != r.repeat_until.day):
|
||||
raise ValidationError(_('The end date should be after the day of the month or the last day of the month'))
|
||||
|
||||
@api.model
|
||||
def _get_recurring_fields(self):
|
||||
@@ -179,16 +178,16 @@ class ProjectTaskRecurrence(models.Model):
|
||||
rrule_kwargs['freq'] = MONTHLY
|
||||
if repeat_on_month == 'date':
|
||||
start = date_start - relativedelta(days=1)
|
||||
if repeat_type == 'until' and repeat_until > date_start:
|
||||
delta = relativedelta(repeat_until, date_start)
|
||||
count = math.floor((delta.years * 12 + delta.months) / repeat_interval)
|
||||
for i in range(count):
|
||||
start = start.replace(day=min(repeat_day, monthrange(start.year, start.month)[1]))
|
||||
if start < date_start:
|
||||
# Ensure the next recurrence is in the future
|
||||
start += relativedelta(months=repeat_interval)
|
||||
start = start.replace(day=min(repeat_day, monthrange(start.year, start.month)[1]))
|
||||
if i == 0 and start < date_start:
|
||||
# Ensure the next recurrence is in the future
|
||||
start += relativedelta(months=repeat_interval)
|
||||
can_generate_date = (lambda: start <= repeat_until) if repeat_type == 'until' else (lambda: len(dates) < count)
|
||||
while can_generate_date():
|
||||
dates.append(start)
|
||||
start += relativedelta(months=repeat_interval)
|
||||
start = start.replace(day=min(repeat_day, monthrange(start.year, start.month)[1]))
|
||||
return dates
|
||||
elif repeat_unit == 'year':
|
||||
rrule_kwargs['freq'] = YEARLY
|
||||
|
||||
@@ -455,6 +455,69 @@ class TestProjectrecurrence(TransactionCase):
|
||||
self.assertEqual(dates[0], datetime(2020, 7, 3))
|
||||
self.assertEqual(dates[1], datetime(2021, 1, 3))
|
||||
|
||||
# Should generate a date at the last day of the current month
|
||||
dates = self.env['project.task.recurrence']._get_next_recurring_dates(
|
||||
date_start=date(2022, 2, 26),
|
||||
repeat_interval=1,
|
||||
repeat_unit='month',
|
||||
repeat_type='until',
|
||||
repeat_until=date(2022, 2, 28),
|
||||
repeat_on_month='date',
|
||||
repeat_on_year=False,
|
||||
weekdays=False,
|
||||
repeat_day=31,
|
||||
repeat_week=False,
|
||||
repeat_month=False,
|
||||
count=5)
|
||||
|
||||
self.assertEqual(len(dates), 1)
|
||||
self.assertEqual(dates[0], date(2022, 2, 28))
|
||||
|
||||
dates = self.env['project.task.recurrence']._get_next_recurring_dates(
|
||||
date_start=date(2022, 11, 26),
|
||||
repeat_interval=3,
|
||||
repeat_unit='month',
|
||||
repeat_type='until',
|
||||
repeat_until=date(2024, 2, 29),
|
||||
repeat_on_month='date',
|
||||
repeat_on_year=False,
|
||||
weekdays=False,
|
||||
repeat_day=25,
|
||||
repeat_week=False,
|
||||
repeat_month=False,
|
||||
count=5)
|
||||
|
||||
self.assertEqual(len(dates), 5)
|
||||
self.assertEqual(dates[0], date(2023, 2, 25))
|
||||
self.assertEqual(dates[1], date(2023, 5, 25))
|
||||
self.assertEqual(dates[2], date(2023, 8, 25))
|
||||
self.assertEqual(dates[3], date(2023, 11, 25))
|
||||
self.assertEqual(dates[4], date(2024, 2, 25))
|
||||
|
||||
# Use the exact same parameters than the previous test but with a repeat_day that is not passed yet
|
||||
# So we generate an additional date in the current month
|
||||
dates = self.env['project.task.recurrence']._get_next_recurring_dates(
|
||||
date_start=date(2022, 11, 26),
|
||||
repeat_interval=3,
|
||||
repeat_unit='month',
|
||||
repeat_type='until',
|
||||
repeat_until=date(2024, 2, 29),
|
||||
repeat_on_month='date',
|
||||
repeat_on_year=False,
|
||||
weekdays=False,
|
||||
repeat_day=31,
|
||||
repeat_week=False,
|
||||
repeat_month=False,
|
||||
count=5)
|
||||
|
||||
self.assertEqual(len(dates), 6)
|
||||
self.assertEqual(dates[0], date(2022, 11, 30))
|
||||
self.assertEqual(dates[1], date(2023, 2, 28))
|
||||
self.assertEqual(dates[2], date(2023, 5, 31))
|
||||
self.assertEqual(dates[3], date(2023, 8, 31))
|
||||
self.assertEqual(dates[4], date(2023, 11, 30))
|
||||
self.assertEqual(dates[5], date(2024, 2, 29))
|
||||
|
||||
def test_recurrence_next_dates_year(self):
|
||||
dates = self.env['project.task.recurrence']._get_next_recurring_dates(
|
||||
date_start=date(2020, 12, 1),
|
||||
|
||||
Reference in New Issue
Block a user