Files
odoo_source/addons/project/models/project_task_recurrence.py
T
Abderraouf Ghrissi (abgh) a424cf481c [IMP] project: delete useless fields
In this commit:
- project tasks kanban view: if partner_is_company, we display partner_id
else commercial_partner_id -> this information is useless so we decided to
display partner_id in both cases.
- manager_id is not used in any view -> drop the field
- when creating a task by mail, email_from is set to the sender email then will
be changed depending on partner_id and parent_id, the field name is not significant
as it change and no more contain the original email sender, it's also a duplicated
information that can be got from partner / parent -> delete the field and create
a new partner if no one already exist with the same email.
- when project.analytic_account_id is changed, only tasks that have the same old
analytic_account_id will follow the name value -> we decided to change the behavior
and apply the new value for all tasks.
- project_analytic_account_id not used in any view and in only place in python
-> delete field
- project.project: partner_email, partner_phone are not used in any view ->
delete field
- project.task: partner_email not used -> delete field
- ancestor_id is no more used in any view or python reference as it's a complicated
field to understand by users -> remove ancestor_id from project.task and remove also
ancestor_task_id that is related it to it in timesheet context and replace it with
parent_task_id which is more simpler to understand by users.

task-3103701

closes odoo/odoo#110101

Related: odoo/enterprise#37246
Related: odoo/upgrade#4269
Signed-off-by: Xavier Bol (xbo) <xbo@odoo.com>
2023-02-23 18:28:45 +01:00

344 lines
15 KiB
Python

# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import _, api, fields, models
from odoo.exceptions import ValidationError
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
MONTHS = {
'january': 31,
'february': 28,
'march': 31,
'april': 30,
'may': 31,
'june': 30,
'july': 31,
'august': 31,
'september': 30,
'october': 31,
'november': 30,
'december': 31,
}
DAYS = {
'mon': MO,
'tue': TU,
'wed': WE,
'thu': TH,
'fri': FR,
'sat': SA,
'sun': SU,
}
WEEKS = {
'first': 1,
'second': 2,
'third': 3,
'last': 4,
}
class ProjectTaskRecurrence(models.Model):
_name = 'project.task.recurrence'
_description = 'Task Recurrence'
task_template_id = fields.Many2one('project.task')
task_ids = fields.One2many('project.task', 'recurrence_id', copy=False)
next_recurrence_date = fields.Date()
recurrence_left = fields.Integer(string="Number of Tasks Left to Create", copy=False)
repeat_interval = fields.Integer(string='Repeat Every', default=1)
repeat_unit = fields.Selection([
('day', 'Days'),
('week', 'Weeks'),
('month', 'Months'),
('year', 'Years'),
], default='week')
repeat_type = fields.Selection([
('forever', 'Forever'),
('until', 'End Date'),
('after', 'Number of Repetitions'),
], default="forever", string="Until")
repeat_until = fields.Date(string="End Date")
repeat_number = fields.Integer(string="Repetitions")
repeat_on_month = fields.Selection([
('date', 'Date of the Month'),
('day', 'Day of the Month'),
])
repeat_on_year = fields.Selection([
('date', 'Date of the Year'),
('day', 'Day of the Year'),
])
mon = fields.Boolean(string="Mon")
tue = fields.Boolean(string="Tue")
wed = fields.Boolean(string="Wed")
thu = fields.Boolean(string="Thu")
fri = fields.Boolean(string="Fri")
sat = fields.Boolean(string="Sat")
sun = fields.Boolean(string="Sun")
repeat_day = fields.Selection([
(str(i), str(i)) for i in range(1, 32)
])
repeat_week = fields.Selection([
('first', 'First'),
('second', 'Second'),
('third', 'Third'),
('last', 'Last'),
])
repeat_weekday = fields.Selection([
('mon', 'Monday'),
('tue', 'Tuesday'),
('wed', 'Wednesday'),
('thu', 'Thursday'),
('fri', 'Friday'),
('sat', 'Saturday'),
('sun', 'Sunday'),
], string='Day Of The Week', readonly=False)
repeat_month = fields.Selection([
('january', 'January'),
('february', 'February'),
('march', 'March'),
('april', 'April'),
('may', 'May'),
('june', 'June'),
('july', 'July'),
('august', 'August'),
('september', 'September'),
('october', 'October'),
('november', 'November'),
('december', 'December'),
])
@api.constrains('repeat_unit', 'mon', 'tue', 'wed', 'thu', 'fri', 'sat', 'sun')
def _check_recurrence_days(self):
for project in self.filtered(lambda p: p.repeat_unit == 'week'):
if not any([project.mon, project.tue, project.wed, project.thu, project.fri, project.sat, project.sun]):
raise ValidationError(_('You should select a least one day'))
@api.constrains('repeat_interval')
def _check_repeat_interval(self):
if self.filtered(lambda t: t.repeat_interval <= 0):
raise ValidationError(_('The interval should be greater than 0'))
@api.constrains('repeat_number', 'repeat_type')
def _check_repeat_number(self):
if self.filtered(lambda t: t.repeat_type == 'after' and t.repeat_number <= 0):
raise ValidationError(_('Should repeat at least once'))
@api.constrains('repeat_type', 'repeat_until')
def _check_repeat_until_date(self):
today = fields.Date.today()
if self.filtered(lambda t: t.repeat_type == 'until' and t.repeat_until < today):
raise ValidationError(_('The end date should be in the future'))
@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 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):
return ['message_partner_ids', 'company_id', 'description', 'displayed_image_id', 'email_cc',
'parent_id', 'partner_id', 'partner_phone', 'planned_hours',
'project_id', 'display_project_id', 'project_privacy_visibility', 'sequence', 'tag_ids', 'recurrence_id',
'name', 'recurring_task', 'analytic_account_id', 'user_ids']
def _get_weekdays(self, n=1):
self.ensure_one()
if self.repeat_unit == 'week':
return [fn(n) for day, fn in DAYS.items() if self[day]]
return [DAYS.get(self.repeat_weekday)(n)]
@api.model
def _get_next_recurring_dates(self, date_start, repeat_interval, repeat_unit, repeat_type, repeat_until, repeat_on_month, repeat_on_year, weekdays, repeat_day, repeat_week, repeat_month, **kwargs):
count = kwargs.get('count', 1)
rrule_kwargs = {'interval': repeat_interval or 1, 'dtstart': date_start}
repeat_day = int(repeat_day)
start = False
dates = []
if repeat_type == 'until':
rrule_kwargs['until'] = repeat_until if repeat_until else fields.Date.today()
else:
rrule_kwargs['count'] = count
if repeat_unit == 'week'\
or (repeat_unit == 'month' and repeat_on_month == 'day')\
or (repeat_unit == 'year' and repeat_on_year == 'day'):
rrule_kwargs['byweekday'] = weekdays
if repeat_unit == 'day':
rrule_kwargs['freq'] = DAILY
elif repeat_unit == 'month':
rrule_kwargs['freq'] = MONTHLY
if repeat_on_month == 'date':
start = date_start - relativedelta(days=1)
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]))
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
month = list(MONTHS.keys()).index(repeat_month) + 1 if repeat_month else date_start.month
repeat_month = repeat_month or list(MONTHS.keys())[month - 1]
rrule_kwargs['bymonth'] = month
if repeat_on_year == 'date':
rrule_kwargs['bymonthday'] = min(repeat_day, MONTHS.get(repeat_month))
rrule_kwargs['bymonth'] = month
else:
rrule_kwargs['freq'] = WEEKLY
rules = rrule(**rrule_kwargs)
return list(rules) if rules else []
def _new_task_values(self, task_from, to_template=False):
self.ensure_one()
fields_to_copy = self._get_recurring_fields()
task_values = task_from.read(fields_to_copy).pop()
create_values = {
field: value[0] if isinstance(value, tuple) else value for field, value in task_values.items()
}
create_values['stage_id'] = task_from.project_id.type_ids[0].id if task_from.project_id.type_ids\
else task_from.stage_id.id
create_values['date_deadline'] = self._get_postponed_date(task_from, 'date_deadline', to_template=to_template)
create_values['active'] = not to_template
create_values['recurrence_template_id'] = not to_template and task_from.id
return create_values
def _create_subtasks(self, task_from, task_to, to_template=False):
self.ensure_one()
childs_from = task_from.with_context(active_test=False).child_ids
if to_template:
childs_to = task_from.recurrence_template_id.with_context(active_test=False).child_ids
childs_from_to_create = childs_from.filtered(lambda t: not t.recurrence_template_id)
childs_to_to_delete = childs_to.filtered(lambda t: t not in childs_from.recurrence_template_id)
childs_to_to_delete.unlink_task_and_subtasks_recursively()
(childs_to - childs_to_to_delete).parent_id = task_to
else:
childs_from_to_create = childs_from
# copy the subtasks of the original task
for child_from in childs_from_to_create:
child_values = self._new_task_values(child_from, to_template=to_template)
child_values['parent_id'] = task_to.id
child_to = self.env['project.task'].sudo().create(child_values)
if to_template:
child_from.recurrence_template_id = child_to
if child_from.with_context(active_test=False).child_ids:
self._create_subtasks(child_from, child_to, to_template=to_template)
def _create_task(self, task_from=False):
self.ensure_one()
to_template = bool(task_from)
task_from = task_from or self.task_template_id
create_values = self._new_task_values(task_from, to_template=to_template)
task_to = self.env['project.task'].sudo().create(create_values)
self._create_subtasks(task_from, task_to, to_template=to_template)
if to_template:
task_from.recurrence_template_id.unlink()
task_from.recurrence_template_id = task_to
if task_from.parent_id:
task_to.parent_id = task_from.parent_id.recurrence_template_id
else:
self.task_template_id.unlink_task_and_subtasks_recursively()
self.task_template_id = task_to
def _set_next_recurrence_date(self, date_start=None):
today = fields.Date.today()
if not date_start:
date_start = today + relativedelta(days=1)
for recurrence in self.filtered(
lambda r:
r.repeat_type == 'after' and r.recurrence_left >= 0
or r.repeat_type == 'until' and r.repeat_until >= today
or r.repeat_type == 'forever'
):
if recurrence.repeat_type == 'after' and recurrence.recurrence_left == 0:
recurrence.next_recurrence_date = False
else:
next_date = self._get_next_recurring_dates(date_start, recurrence.repeat_interval, recurrence.repeat_unit, recurrence.repeat_type, recurrence.repeat_until, recurrence.repeat_on_month, recurrence.repeat_on_year, recurrence._get_weekdays(), recurrence.repeat_day, recurrence.repeat_week, recurrence.repeat_month, count=1)
recurrence.next_recurrence_date = next_date[0] if next_date else False
@api.model
def _cron_create_recurring_tasks(self):
if not self.env.user.has_group('project.group_project_recurring_tasks'):
return
today = fields.Date.today()
recurring_today = self.search([('next_recurrence_date', '<=', today)])
recurrence_ids = [] # to set the next recurrence date
for recurrence in recurring_today:
if not recurrence.task_template_id.project_id.allow_recurring_tasks:
continue
recurrence_ids.append(recurrence.id)
recurrence._create_task()
if recurrence.repeat_type == 'after':
recurrence.recurrence_left -= 1
self.browse(recurrence_ids)._set_next_recurrence_date()
@api.model_create_multi
def create(self, vals_list):
for vals in vals_list:
if vals.get('repeat_number'):
vals['recurrence_left'] = vals.get('repeat_number')
recurrences = super().create(vals_list)
recurrences._set_next_recurrence_date()
return recurrences
def write(self, vals):
if vals.get('repeat_number'):
vals['recurrence_left'] = vals.get('repeat_number')
res = super(ProjectTaskRecurrence, self).write(vals)
if 'next_recurrence_date' not in vals:
self._set_next_recurrence_date()
return res
def unlink(self):
self.task_template_id.unlink_task_and_subtasks_recursively()
return super().unlink()
def _get_postponed_date(self, task_from, field, to_template=False):
# We cannot just apply the recurrence's repeat interval to the task's deadline because,
# if the recurrence is created later than the task, the planned date won't be set as expected.
# Instead, we'll apply the delta create-deadline of the previous task to the new one's create date.
# Apply the repeat interval to the create date : # ~ the repeat interval
# p1~p2~p3 ? ? # / the expected delta create-deadline
# / / / => p2 should be set at ? # c the create dates
# c1---------c2~c3 # p the deadline
# ^ Recurrence initiation
# Apply the previous delta create-deadline to the create date :
# p1 p2 p3
# / / / => p2 is set as expected
# c1---------c2~c3
# ^ Recurrence initiation
self.ensure_one()
if not task_from[field]:
return False
field_is_datetime = task_from._fields[field].type == 'datetime'
datetime = task_from[field]
date = datetime.date() if field_is_datetime else datetime
delta = date - task_from.create_date.date()
if to_template:
next_recurrence_date = (self.task_template_id.create_date or self.create_date).date()
else:
next_recurrence_date = self.next_recurrence_date or fields.Date.today()
postponed_date = next_recurrence_date + relativedelta(days=delta.days)
if field_is_datetime:
postponed_date += relativedelta(hour=datetime.hour, minute=datetime.minute, second=datetime.second)
return postponed_date