*=hr_timesheet,project,sale_project,sale_timesheet Currently, the configuration of the recurrence is quite complete and allows a lot of flexibility, but it is costly in terms of implementation as it requires a lot of fields. The goal of this task is thus to simplify this implementation. In addition, a lot of people are complaining that tasks are only generated when the recurrence date is reached, as it doesn't allow to anticipate the planning of field service tasks. In this task, we are thus going to immediately generate a new task once the previous one is marked as done. Concretely, we: - describe a recurrence in terms of "once every n day/week/month/year for ever/until a date" and delete all fields that don't fit into it. - remove the cron. The new occurrence is created when marking the last task as done, and copied from the latter. Deleting the last task deletes the recurrence. The recurrence fields stay useful, as they form a delta t that will be added to deadline/planned dates fields to get the new values. - use an boolean icon button to activate recurrence, and place it at the end of deadline field's line. The recurrence fields appear on the next line. - delete in the form: the div explaining when the next tasks will be created and the header to choose how to save the changes in the recurrence. task-3084945 closes odoo/odoo#112764 Related: odoo/upgrade#4343 Related: odoo/enterprise#37114 Signed-off-by: Xavier Bol (xbo) <xbo@odoo.com>
112 lines
3.9 KiB
Python
112 lines
3.9 KiB
Python
# -*- coding: utf-8 -*-
|
|
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
|
|
|
from odoo import _, api, fields, models, Command
|
|
from odoo.exceptions import ValidationError
|
|
|
|
from dateutil.relativedelta import relativedelta
|
|
|
|
class ProjectTaskRecurrence(models.Model):
|
|
_name = 'project.task.recurrence'
|
|
_description = 'Task Recurrence'
|
|
|
|
task_ids = fields.One2many('project.task', 'recurrence_id', 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', 'Until'),
|
|
], default="forever", string="Until")
|
|
repeat_until = fields.Date(string="End Date")
|
|
|
|
@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_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.model
|
|
def _get_recurring_fields_to_copy(self):
|
|
return [
|
|
'analytic_account_id',
|
|
'company_id',
|
|
'description',
|
|
'displayed_image_id',
|
|
'email_cc',
|
|
'message_partner_ids',
|
|
'name',
|
|
'parent_id',
|
|
'partner_id',
|
|
'partner_phone',
|
|
'planned_hours',
|
|
'project_id',
|
|
'display_project_id',
|
|
'project_privacy_visibility',
|
|
'recurrence_id',
|
|
'recurring_task',
|
|
'sequence',
|
|
'tag_ids',
|
|
'user_ids',
|
|
]
|
|
|
|
@api.model
|
|
def _get_recurring_fields_to_postpone(self):
|
|
return [
|
|
'date_deadline',
|
|
]
|
|
|
|
def _get_last_task_id_per_recurrence_id(self):
|
|
return {} if not self else {
|
|
record['recurrence_id'][0]: record['max_id']
|
|
for record in self.env['project.task'].sudo()._read_group(
|
|
[('recurrence_id', 'in', self.ids)],
|
|
['max_id:max(id)'],
|
|
['recurrence_id'],
|
|
)
|
|
}
|
|
|
|
def _get_recurrence_delta(self):
|
|
return relativedelta(**{
|
|
f"{self.repeat_unit}s": self.repeat_interval
|
|
})
|
|
|
|
def _create_next_occurrence(self, occurrence_from):
|
|
self.ensure_one()
|
|
if occurrence_from.allow_recurring_tasks:
|
|
self.env['project.task'].sudo().create(
|
|
self._create_next_occurrence_values(occurrence_from)
|
|
)
|
|
|
|
def _create_next_occurrence_values(self, occurrence_from):
|
|
self.ensure_one()
|
|
fields_to_copy = occurrence_from.read(self._get_recurring_fields_to_copy()).pop()
|
|
create_values = {
|
|
field: value[0] if isinstance(value, tuple) else value
|
|
for field, value in fields_to_copy.items()
|
|
}
|
|
|
|
fields_to_postpone = occurrence_from.read(self._get_recurring_fields_to_postpone()).pop()
|
|
fields_to_postpone.pop('id', None)
|
|
create_values.update({
|
|
field: value and value + self._get_recurrence_delta()
|
|
for field, value in fields_to_postpone.items()
|
|
})
|
|
|
|
create_values['stage_id'] = occurrence_from.project_id.type_ids[0].id if occurrence_from.project_id.type_ids else occurrence_from.stage_id.id
|
|
if occurrence_from.allow_subtasks:
|
|
create_values['child_ids'] = [
|
|
Command.create(self._create_next_occurrence_values(child)) for child in occurrence_from.with_context(active_test=False).child_ids
|
|
]
|
|
return create_values
|