[IMP] project: add recurring tasks

Some interventions are done on a regular basis (e.g. maintenance of
fire alarms, safety inspections). Having tasks auto-generate would
facilitate the process and would ensure that the next intervention
isn't missed/forgotten.

closes odoo/odoo#55517

Taskid: 2172156
Related: odoo/enterprise#12246
Related: odoo/upgrade#1604
Signed-off-by: Yannick Tivisse (yti) <yti@odoo.com>
This commit is contained in:
Kevin Baptiste
2020-08-12 08:39:32 +00:00
parent 0dd4876762
commit 1b62bd9508
17 changed files with 1020 additions and 4 deletions
+1
View File
@@ -40,6 +40,7 @@
'wizard/project_task_type_delete_views.xml',
],
'demo': ['data/project_demo.xml'],
'qweb': ['static/src/xml/project_templates.xml'],
'test': [
],
'installable': True,
+10
View File
@@ -10,6 +10,16 @@
<field name="numbercall">-1</field>
</record>
<record id="ir_cron_recurring_tasks" model="ir.cron">
<field name="name">Project: Create Recurring Tasks</field>
<field name="model_id" ref="project.model_project_task_recurrence"/>
<field name="state">code</field>
<field name="code">model._cron_create_recurring_tasks()</field>
<field name="interval_type">days</field>
<field name="numbercall">-1</field>
<field name="nextcall" eval="(DateTime.now().replace(hour=3, minute=0) + timedelta(days=1)).strftime('%Y-%m-%d %H:%M:%S')" />
</record>
<!-- Task-related subtypes for messaging / Chatter -->
<record id="mt_task_new" model="mail.message.subtype">
<field name="name">Task Created</field>
+1
View File
@@ -2,6 +2,7 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from . import analytic_account
from . import project_task_recurrence
from . import project
from . import res_config_settings
from . import res_partner
+247 -3
View File
@@ -2,13 +2,15 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
import ast
from datetime import timedelta
from datetime import timedelta, datetime
from random import randint
from odoo import api, fields, models, tools, SUPERUSER_ID, _
from odoo.exceptions import UserError, AccessError, ValidationError, RedirectWarning
from odoo.tools.misc import format_date
from odoo.tools.misc import format_date, get_lang
from odoo.osv.expression import OR
from .project_task_recurrence import DAYS, WEEKS
class ProjectTaskType(models.Model):
_name = 'project.task.type'
@@ -210,6 +212,7 @@ class Project(models.Model):
subtask_project_id = fields.Many2one('project.project', string='Sub-task Project', ondelete="restrict",
help="Project in which sub-tasks of the current project will be created. It can be the current project itself.")
allow_subtasks = fields.Boolean('Sub-tasks', default=lambda self: self.env.user.has_group('project.group_subtask_project'))
allow_recurring_tasks = fields.Boolean('Recurring Tasks', default=lambda self: self.env.user.has_group('project.group_project_recurring_tasks'))
# rating fields
rating_request_deadline = fields.Datetime(compute='_compute_rating_request_deadline', store=True)
@@ -339,6 +342,9 @@ class Project(models.Model):
for task in project.task_ids:
task.allowed_user_ids -= permission_removed
if 'allow_recurring_tasks' in vals and not vals.get('allow_recurring_tasks'):
self.env['project.task'].search([('project_id', 'in', self.ids), ('recurring_task', '=', True)]).write({'recurring_task': False})
if 'active' in vals:
# archiving/unarchiving a project does it on its tasks, too
self.with_context(active_test=False).mapped('tasks').write({'active': vals['active']})
@@ -596,6 +602,172 @@ class Task(models.Model):
# customer portal: include comment and incoming emails in communication history
website_message_ids = fields.One2many(domain=lambda self: [('model', '=', self._name), ('message_type', 'in', ['email', 'comment'])])
# recurrence fields
allow_recurring_tasks = fields.Boolean(related='project_id.allow_recurring_tasks')
recurring_task = fields.Boolean(string="Recurrent")
recurring_count = fields.Integer(string="Tasks in Recurrence", compute='_compute_recurring_count')
recurrence_id = fields.Many2one('project.task.recurrence', copy=False)
recurrence_update = fields.Selection([
('this', 'This task'),
('subsequent', 'This and following tasks'),
('all', 'All tasks'),
], default='this', store=False)
recurrence_message = fields.Char(string='Next Recurrencies', compute='_compute_recurrence_message')
repeat_interval = fields.Integer(string='Repeat Every', default=1, compute='_compute_repeat', readonly=False)
repeat_unit = fields.Selection([
('day', 'Days'),
('week', 'Weeks'),
('month', 'Months'),
('year', 'Years'),
], default='week', compute='_compute_repeat', readonly=False)
repeat_type = fields.Selection([
('forever', 'Forever'),
('until', 'End Date'),
('after', 'Number of Repetitions'),
], default="forever", string="Until", compute='_compute_repeat', readonly=False)
repeat_until = fields.Date(string="End Date", compute='_compute_repeat', readonly=False)
repeat_number = fields.Integer(string="Repetitions", default=1, compute='_compute_repeat', readonly=False)
repeat_on_month = fields.Selection([
('date', 'Date of the Month'),
('day', 'Day of the Month'),
], default='date', compute='_compute_repeat', readonly=False)
repeat_on_year = fields.Selection([
('date', 'Date of the Year'),
('day', 'Day of the Year'),
], default='date', compute='_compute_repeat', readonly=False)
mon = fields.Boolean(string="Mon", compute='_compute_repeat', readonly=False)
tue = fields.Boolean(string="Tue", compute='_compute_repeat', readonly=False)
wed = fields.Boolean(string="Wed", compute='_compute_repeat', readonly=False)
thu = fields.Boolean(string="Thu", compute='_compute_repeat', readonly=False)
fri = fields.Boolean(string="Fri", compute='_compute_repeat', readonly=False)
sat = fields.Boolean(string="Sat", compute='_compute_repeat', readonly=False)
sun = fields.Boolean(string="Sun", compute='_compute_repeat', readonly=False)
repeat_day = fields.Selection([
(str(i), str(i)) for i in range(1, 32)
], compute='_compute_repeat', readonly=False)
repeat_week = fields.Selection([
('first', 'First'),
('second', 'Second'),
('third', 'Third'),
('last', 'Last'),
], default='first', compute='_compute_repeat', readonly=False)
repeat_weekday = fields.Selection([
('mon', 'Monday'),
('tue', 'Tuesday'),
('wed', 'Wednesday'),
('thu', 'Thursday'),
('fri', 'Friday'),
('sat', 'Saturday'),
('sun', 'Sunday'),
], string='Day Of The Week', compute='_compute_repeat', 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'),
], compute='_compute_repeat', readonly=False)
repeat_show_dow = fields.Boolean(compute='_compute_repeat_visibility')
repeat_show_day = fields.Boolean(compute='_compute_repeat_visibility')
repeat_show_week = fields.Boolean(compute='_compute_repeat_visibility')
repeat_show_month = fields.Boolean(compute='_compute_repeat_visibility')
@api.model
def _get_recurrence_fields(self):
return ['repeat_interval', 'repeat_unit', 'repeat_type', 'repeat_until', 'repeat_number',
'repeat_on_month', 'repeat_on_year', 'mon', 'tue', 'wed', 'thu', 'fri', 'sat',
'sun', 'repeat_day', 'repeat_week', 'repeat_month', 'repeat_weekday']
@api.depends('recurring_task', 'repeat_unit', 'repeat_on_month', 'repeat_on_year')
def _compute_repeat_visibility(self):
for task in self:
task.repeat_show_day = task.recurring_task and (task.repeat_unit == 'month' and task.repeat_on_month == 'date') or (task.repeat_unit == 'year' and task.repeat_on_year == 'date')
task.repeat_show_week = task.recurring_task and (task.repeat_unit == 'month' and task.repeat_on_month == 'day') or (task.repeat_unit == 'year' and task.repeat_on_year == 'day')
task.repeat_show_dow = task.recurring_task and task.repeat_unit == 'week'
task.repeat_show_month = task.recurring_task and task.repeat_unit == 'year'
@api.depends('recurring_task', 'repeat_unit')
def _compute_repeat(self):
rec_fields = self._get_recurrence_fields()
defaults = self.default_get(rec_fields)
for task in self:
for f in rec_fields:
if task.recurrence_id:
task[f] = task.recurrence_id[f]
else:
if task.recurring_task:
task[f] = defaults.get(f)
else:
task[f] = False
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.depends(
'recurring_task', 'repeat_interval', 'repeat_unit', 'repeat_type', 'repeat_until',
'repeat_number', 'repeat_on_month', 'repeat_on_year', 'mon', 'tue', 'wed', 'thu', 'fri',
'sat', 'sun', 'repeat_day', 'repeat_week', 'repeat_month', 'repeat_weekday')
def _compute_recurrence_message(self):
self.recurrence_message = False
for task in self.filtered(lambda t: t.recurring_task and t._is_recurrence_valid()):
date = fields.Date.today()
number_occurrences = min(5, task.repeat_number if task.repeat_type == 'after' else 5)
delta = task.repeat_interval if task.repeat_unit == 'day' else 1
recurring_dates = self.env['project.task.recurrence']._get_next_recurring_dates(
date + timedelta(days=delta),
task.repeat_interval,
task.repeat_unit,
task.repeat_type,
task.repeat_until,
task.repeat_on_month,
task.repeat_on_year,
task._get_weekdays(WEEKS.get(task.repeat_week)),
task.repeat_day,
task.repeat_week,
task.repeat_month,
count=number_occurrences)
date_format = self.env['res.lang']._lang_get(self.env.user.lang).date_format
task.recurrence_message = '<ul>'
for date in recurring_dates[:5]:
task.recurrence_message += '<li>%s</li>' % date.strftime(date_format)
if task.repeat_type == 'after' and task.repeat_number > 5 or task.repeat_type == 'forever' or len(recurring_dates) > 5:
task.recurrence_message += '<li>...</li>'
task.recurrence_message += '</ul>'
if task.repeat_type == 'until':
task.recurrence_message += _('<p><em>Number of tasks: %(tasks_count)s</em></p>') % {'tasks_count': len(recurring_dates)}
def _is_recurrence_valid(self):
self.ensure_one()
return self.repeat_interval > 0 and\
(not self.repeat_show_dow or self._get_weekdays()) and\
(self.repeat_type != 'after' or self.repeat_number) and\
(self.repeat_type != 'until' or self.repeat_until and self.repeat_until > fields.Date.today())
@api.depends('recurrence_id')
def _compute_recurring_count(self):
self.recurring_count = 0
recurring_tasks = self.filtered(lambda l: l.recurrence_id)
count = self.env['project.task'].read_group([('recurrence_id', 'in', recurring_tasks.recurrence_id.ids)], ['id'], 'recurrence_id')
tasks_count = {c.get('recurrence_id')[0]: c.get('recurrence_id_count') for c in count}
for task in recurring_tasks:
task.recurring_count = tasks_count.get(task.recurrence_id.id, 0)
@api.depends('partner_id.email')
def _compute_partner_email(self):
for task in self:
@@ -759,6 +931,8 @@ class Task(models.Model):
default = {}
if not default.get('name'):
default['name'] = _("%s (copy)", self.name)
if self.recurrence_id:
default['recurrence_id'] = self.recurrence_id.copy().id
return super(Task, self).copy(default)
@api.constrains('parent_id')
@@ -821,6 +995,25 @@ class Task(models.Model):
# ------------------------------------------------
# CRUD overrides
# ------------------------------------------------
@api.model
def default_get(self, default_fields):
vals = super(Task, self).default_get(default_fields)
days = list(DAYS.keys())
week_start = fields.Datetime.today().weekday()
if all([d in default_fields for d in days]):
vals[days[week_start]] = True
if 'repeat_day' in default_fields:
vals['repeat_day'] = str(fields.Datetime.today().day)
if 'repeat_month' in default_fields:
vals['repeat_month'] = self._fields.get('repeat_month').selection[fields.Datetime.today().month - 1][0]
if 'repeat_until' in default_fields:
vals['repeat_until'] = fields.Date.today() + timedelta(days=7)
if 'repeat_weekday' in default_fields:
vals['repeat_weekday'] = self._fields.get('repeat_weekday').selection[week_start][0]
return vals
@api.model_create_multi
def create(self, vals_list):
@@ -847,12 +1040,22 @@ class Task(models.Model):
if vals.get('stage_id'):
vals.update(self.update_date_end(vals['stage_id']))
vals['date_last_stage_update'] = fields.Datetime.now()
# recurrence
rec_fields = vals.keys() & self._get_recurrence_fields()
if rec_fields and vals.get('recurring_task') is True:
rec_values = {rec_field: vals[rec_field] for rec_field in rec_fields}
rec_values['next_recurrence_date'] = fields.Datetime.today()
recurrence = self.env['project.task.recurrence'].create(rec_values)
vals['recurrence_id'] = recurrence.id
return super().create(vals_list)
def write(self, vals):
now = fields.Datetime.now()
if 'parent_id' in vals and vals['parent_id'] in self.ids:
raise UserError(_("Sorry. You can't set a task as its parent task."))
if 'active' in vals and not vals.get('active') and any(self.mapped('recurrence_id')):
# TODO: show a dialog to stop the recurrence
raise UserError(_('You cannot archive recurring tasks. Please, disable the recurrence first.'))
# stage change: update date_last_stage_update
if 'stage_id' in vals:
vals.update(self.update_date_end(vals['stage_id']))
@@ -864,7 +1067,33 @@ class Task(models.Model):
if vals.get('user_id') and 'date_assign' not in vals:
vals['date_assign'] = now
result = super(Task, self).write(vals)
# recurrence fields
rec_fields = vals.keys() & self._get_recurrence_fields()
if rec_fields:
rec_values = {rec_field: vals[rec_field] for rec_field in rec_fields}
for task in self:
if task.recurrence_id:
task.recurrence_id.write(rec_values)
elif vals.get('recurring_task'):
rec_values['next_recurrence_date'] = fields.Datetime.today()
recurrence = self.env['project.task.recurrence'].create(rec_values)
task.recurrence_id = recurrence.id
if 'recurring_task' in vals and not vals.get('recurring_task'):
self.recurrence_id.unlink()
tasks = self
recurrence_update = vals.pop('recurrence_update', 'this')
if recurrence_update != 'this':
recurrence_domain = []
if recurrence_update == 'subsequent':
for task in self:
recurrence_domain = OR([recurrence_domain, ['&', ('recurrence_id', '=', task.recurrence_id.id), ('create_date', '>=', task.create_date)]])
else:
recurrence_domain = [('recurrence_id', 'in', self.recurrence_id.ids)]
tasks |= self.env['project.task'].search(recurrence_domain)
result = super(Task, tasks).write(vals)
# rating on stage
if 'stage_id' in vals and vals.get('stage_id'):
self.filtered(lambda x: x.project_id.rating_status == 'stage')._send_task_rating_mail(force_send=True)
@@ -876,6 +1105,12 @@ class Task(models.Model):
return {'date_end': fields.Datetime.now()}
return {'date_end': False}
def unlink(self):
if any(self.mapped('recurrence_id')):
# TODO: show a dialog to stop the recurrence
raise UserError(_('You cannot delete recurring tasks. Please, disable the recurrence first.'))
return super().unlink()
# ---------------------------------------------------
# Subtasks
# ---------------------------------------------------
@@ -1106,6 +1341,15 @@ class Task(models.Model):
return action
def action_recurring_tasks(self):
return {
'name': 'Tasks in Recurrence',
'type': 'ir.actions.act_window',
'res_model': 'project.task',
'view_mode': 'tree,form',
'domain': [('recurrence_id', 'in', self.recurrence_id.ids)],
}
# ---------------------------------------------------
# Rating business
# ---------------------------------------------------
@@ -0,0 +1,258 @@
# -*- 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_ids = fields.One2many('project.task', 'recurrence_id')
next_recurrence_date = fields.Date()
recurrence_left = fields.Integer(string="Number of tasks left to create")
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):
raise ValidationError('The end date should be after the day of the month')
@api.model
def _get_recurring_fields(self):
return ['allowed_user_ids', 'company_id', 'description', 'displayed_image_id', 'email_cc',
'parent_id', 'partner_email', 'partner_id', 'partner_phone', 'planned_hours',
'project_id', 'project_privacy_visibility', 'sequence', 'tag_ids', 'recurrence_id',
'name', 'recurring_task']
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)
if repeat_type == 'until' and repeat_until > date_start:
delta = relativedelta(repeat_until, date_start)
count = delta.years * 12 + delta.months
for i in range(count):
start = start.replace(day=min(repeat_day, monthrange(start.year, start.month)[1]))
dates.append(start)
start += relativedelta(months=repeat_interval)
return dates
elif repeat_unit == 'year':
rrule_kwargs['freq'] = YEARLY
month = list(MONTHS.keys()).index(repeat_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):
self.ensure_one()
fields_to_copy = self._get_recurring_fields()
task_values = task.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.project_id.type_ids[0].id if task.project_id.type_ids else task.stage_id
create_values['user_id'] = False
return create_values
def _create_next_task(self):
for recurrence in self:
task = self.sudo().task_ids[-1]
create_values = recurrence._new_task_values(task)
new_task = self.env['project.task'].sudo().create(create_values)
if not new_task.parent_id and task.child_ids:
children = []
# copy the subtasks of the original task
for child in task.child_ids:
child_values = self._new_task_values(child)
child_values['parent_id'] = new_task.id
children.append(child_values)
self.env['project.task'].create(children)
def _set_next_recurrence_date(self):
today = fields.Date.today()
tomorrow = 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'):
next_date = self._get_next_recurring_dates(tomorrow, 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
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)])
recurring_today._create_next_task()
for recurrence in recurring_today.filtered(lambda r: r.repeat_type == 'after'):
recurrence.recurrence_left -= 1
recurring_today._set_next_recurrence_date()
def create(self, vals):
if vals.get('repeat_number'):
vals['recurrence_left'] = vals.get('repeat_number')
res = super(ProjectTaskRecurrence, self).create(vals)
res._set_next_recurrence_date()
return res
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
@@ -11,3 +11,9 @@ class ResConfigSettings(models.TransientModel):
module_hr_timesheet = fields.Boolean(string="Task Logs")
group_subtask_project = fields.Boolean("Sub-tasks", implied_group="project.group_subtask_project")
group_project_rating = fields.Boolean("Customer Ratings", implied_group='project.group_project_rating')
group_project_recurring_tasks = fields.Boolean("Recurring Tasks", implied_group="project.group_project_recurring_tasks")
def set_values(self):
if self.user_has_groups('project.group_project_recurring_tasks') != self.group_project_recurring_tasks:
self.env['project.project'].sudo().search([]).write({'allow_recurring_tasks': self.group_project_recurring_tasks})
super(ResConfigSettings, self).set_values()
@@ -24,3 +24,4 @@ access_account_analytic_account_manager,account.analytic.account,analytic.model_
access_account_analytic_line_project,account.analytic.line project,analytic.model_account_analytic_line,project.group_project_manager,1,1,1,1
access_project_delete_wizard,access_project_delete_wizard,model_project_delete_wizard,project.group_project_manager,1,1,1,1
access_project_task_type_delete_wizard,project.task.type.delete.wizard,model_project_task_type_delete_wizard,project.group_project_manager,1,1,1,1
access_project_task_recurrence,project.task.recurrence,model_project_task_recurrence,project.group_project_user,1,1,1,1
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
24 access_account_analytic_line_project account.analytic.line project analytic.model_account_analytic_line project.group_project_manager 1 1 1 1
25 access_project_delete_wizard access_project_delete_wizard model_project_delete_wizard project.group_project_manager 1 1 1 1
26 access_project_task_type_delete_wizard project.task.type.delete.wizard model_project_task_type_delete_wizard project.group_project_manager 1 1 1 1
27 access_project_task_recurrence project.task.recurrence model_project_task_recurrence project.group_project_user 1 1 1 1
@@ -29,6 +29,11 @@
<field name="category_id" ref="base.module_category_hidden"/>
</record>
<record id="group_project_recurring_tasks" model="res.groups">
<field name="name">Use Recurring Tasks</field>
<field name="category_id" ref="base.module_category_hidden"/>
</record>
<data noupdate="1">
<record id="base.default_user" model="res.users">
<field name="groups_id" eval="[(4,ref('project.group_project_manager'))]"/>
@@ -70,3 +70,7 @@
margin-right: 150px;
padding-left: 1rem;
}
.o_form_project_recurrence_message *:last-child {
margin-bottom: 0;
}
@@ -37,3 +37,15 @@
}
}
.o_dow_widget {
th {
padding: 10px 10px 0 10px;
}
.o_dow_days {
td {
text-align: center;
vertical-align: middle;
}
}
}
@@ -0,0 +1,17 @@
<?xml version="1.0" encoding="utf-8"?>
<templates id="template" xml:space="preserve">
<t t-name="day_of_week_widget">
<table class="o_dow_widget">
<thead>
<tr>
<th t-foreach="widget.labels" t-as="label">
<t t-esc="label"/>
</th>
</tr>
</thead>
<tbody>
<tr class="o_dow_days" />
</tbody>
</table>
</t>
</templates>
+1 -1
View File
@@ -1,4 +1,4 @@
# -*- coding: utf-8 -*-
from . import test_project_base, test_project_flow, test_access_rights, test_project_ui
from . import test_project_base, test_project_flow, test_access_rights, test_project_recurrence, test_project_ui
from . import test_multicompany
@@ -0,0 +1,351 @@
# -*- coding: utf-8 -*-
from odoo.tests.common import SavepointCase, Form
from odoo.exceptions import ValidationError
from odoo import fields
from datetime import date, datetime
from dateutil.rrule import MO, TU, WE, TH, FR, SA, SU
from unittest.mock import patch
class TestProjectrecurrence(SavepointCase):
@classmethod
def setUpClass(cls):
super(TestProjectrecurrence, cls).setUpClass()
cls.env.user.groups_id += cls.env.ref('project.group_project_recurring_tasks')
cls.stage_a = cls.env['project.task.type'].create({'name': 'a'})
cls.stage_b = cls.env['project.task.type'].create({'name': 'b'})
cls.project_recurring = cls.env['project.project'].with_context({'mail_create_nolog': True}).create({
'name': 'Recurring',
'allow_recurring_tasks': True,
'type_ids': [
(4, cls.stage_a.id),
(4, cls.stage_b.id),
]
})
def set_task_create_date(self, task_id, create_date):
self.env.cr.execute("UPDATE project_task SET create_date=%s WHERE id=%s", (create_date, task_id))
def test_recurrence_simple(self):
with patch.object(fields.Date, 'today', lambda: datetime(2020, 2, 1)):
with Form(self.env['project.task']) as form:
form.name = 'test recurring task'
form.project_id = self.project_recurring
form.recurring_task = True
form.repeat_interval = 5
form.repeat_unit = 'month'
form.repeat_type = 'after'
form.repeat_number = 10
form.repeat_on_month = 'date'
form.repeat_day = '31'
task = form.save()
self.assertTrue(bool(task.recurrence_id), 'should create a recurrence')
task.write(dict(repeat_interval=2, repeat_number=11))
self.assertEqual(task.recurrence_id.repeat_interval, 2, 'recurrence should be updated')
self.assertEqual(task.recurrence_id.repeat_number, 11, 'recurrence should be updated')
self.assertEqual(task.recurrence_id.recurrence_left, 11)
self.assertEqual(task.recurrence_id.next_recurrence_date, date(2020, 2, 29))
task.recurring_task = False
self.assertFalse(bool(task.recurrence_id), 'the recurrence should be deleted')
def test_recurrence_cron(self):
with patch.object(fields.Date, 'today', lambda: datetime(2020, 1, 1)):
form = Form(self.env['project.task'])
form.name = 'test recurring task'
form.description = 'my super recurring task bla bla bla'
form.project_id = self.project_recurring
form.date_deadline = datetime(2020, 2, 1)
form.recurring_task = True
form.repeat_interval = 1
form.repeat_unit = 'month'
form.repeat_type = 'after'
form.repeat_number = 2
form.repeat_on_month = 'date'
form.repeat_day = '15'
task = form.save()
task.planned_hours = 2
self.assertEqual(task.recurrence_id.next_recurrence_date, date(2020, 1, 15))
self.assertEqual(self.env['project.task'].search_count([('project_id', '=', self.project_recurring.id)]), 1)
self.env['project.task.recurrence']._cron_create_recurring_tasks()
self.assertEqual(self.env['project.task'].search_count([('project_id', '=', self.project_recurring.id)]), 1, 'no extra task should be created')
self.assertEqual(task.recurrence_id.recurrence_left, 2)
with patch.object(fields.Date, 'today', lambda: datetime(2020, 1, 15)):
self.assertEqual(self.env['project.task'].search_count([('project_id', '=', self.project_recurring.id)]), 1)
self.env['project.task.recurrence']._cron_create_recurring_tasks()
self.assertEqual(self.env['project.task'].search_count([('project_id', '=', self.project_recurring.id)]), 2)
self.assertEqual(task.recurrence_id.recurrence_left, 1)
with patch.object(fields.Date, 'today', lambda: datetime(2020, 2, 15)):
self.env['project.task.recurrence']._cron_create_recurring_tasks()
self.assertEqual(task.recurrence_id.recurrence_left, 0)
tasks = self.env['project.task'].search([('project_id', '=', self.project_recurring.id)])
self.assertEqual(len(tasks), 3)
self.assertTrue(bool(tasks[2].date_deadline))
self.assertFalse(tasks[1].date_deadline, "Deadline should not be copied")
for f in self.env['project.task.recurrence']._get_recurring_fields():
self.assertTrue(tasks[0][f] == tasks[1][f] == tasks[2][f], "Field %s should have been copied" % f)
def test_recurrence_update_task(self):
with patch.object(fields.Date, 'today', lambda: datetime(2020, 1, 1)):
task = self.env['project.task'].create({
'name': 'test recurring task',
'project_id': self.project_recurring.id,
'recurring_task': True,
'repeat_interval': 1,
'repeat_unit': 'week',
'repeat_type': 'after',
'repeat_number': 2,
'mon': True,
})
with patch.object(fields.Date, 'today', lambda: datetime(2020, 1, 6)):
self.env['project.task.recurrence']._cron_create_recurring_tasks()
with patch.object(fields.Date, 'today', lambda: datetime(2020, 1, 13)):
self.env['project.task.recurrence']._cron_create_recurring_tasks()
task_c, task_b, task_a = self.env['project.task'].search([('project_id', '=', self.project_recurring.id)])
self.set_task_create_date(task_a.id, datetime(2020, 1, 1))
self.set_task_create_date(task_b.id, datetime(2020, 1, 6))
self.set_task_create_date(task_c.id, datetime(2020, 1, 13))
(task_a+task_b+task_c).invalidate_cache()
task_c.write({
'name': 'my super updated task',
'recurrence_update': 'all',
})
self.assertEqual(task_a.name, 'my super updated task')
self.assertEqual(task_b.name, 'my super updated task')
self.assertEqual(task_c.name, 'my super updated task')
task_a.write({
'name': 'don\'t you dare change my title',
'recurrence_update': 'this',
})
self.assertEqual(task_a.name, 'don\'t you dare change my title')
self.assertEqual(task_b.name, 'my super updated task')
self.assertEqual(task_c.name, 'my super updated task')
task_b.write({
'description': 'hello!',
'recurrence_update': 'subsequent',
})
self.assertEqual(task_a.description, False)
self.assertEqual(task_b.description, '<p>hello!</p>')
self.assertEqual(task_c.description, '<p>hello!</p>')
def test_recurrence_fields_visibility(self):
form = Form(self.env['project.task'])
form.name = 'test recurring task'
form.project_id = self.project_recurring
form.recurring_task = True
form.repeat_unit = 'week'
self.assertTrue(form.repeat_show_dow)
self.assertFalse(form.repeat_show_day)
self.assertFalse(form.repeat_show_week)
self.assertFalse(form.repeat_show_month)
form.repeat_unit = 'month'
form.repeat_on_month = 'date'
self.assertFalse(form.repeat_show_dow)
self.assertTrue(form.repeat_show_day)
self.assertFalse(form.repeat_show_week)
self.assertFalse(form.repeat_show_month)
form.repeat_unit = 'month'
form.repeat_on_month = 'day'
self.assertFalse(form.repeat_show_dow)
self.assertFalse(form.repeat_show_day)
self.assertTrue(form.repeat_show_week)
self.assertFalse(form.repeat_show_month)
form.repeat_unit = 'year'
form.repeat_on_year = 'date'
self.assertFalse(form.repeat_show_dow)
self.assertTrue(form.repeat_show_day)
self.assertFalse(form.repeat_show_week)
self.assertTrue(form.repeat_show_month)
form.repeat_unit = 'year'
form.repeat_on_year = 'day'
self.assertFalse(form.repeat_show_dow)
self.assertFalse(form.repeat_show_day)
self.assertTrue(form.repeat_show_week)
self.assertTrue(form.repeat_show_month)
form.recurring_task = False
self.assertFalse(form.repeat_show_dow)
self.assertFalse(form.repeat_show_day)
self.assertFalse(form.repeat_show_week)
self.assertFalse(form.repeat_show_month)
def test_recurrence_week_day(self):
form = Form(self.env['project.task'])
form.name = 'test recurring task'
form.project_id = self.project_recurring
form.recurring_task = True
form.repeat_unit = 'week'
form.mon = False
form.tue = False
form.wed = False
form.thu = False
form.fri = False
form.sat = False
form.sun = False
with self.assertRaises(ValidationError), self.cr.savepoint():
form.save()
def test_recurrence_next_dates_week(self):
dates = self.env['project.task.recurrence']._get_next_recurring_dates(
date_start=date(2020, 1, 1),
repeat_interval=1,
repeat_unit='week',
repeat_type=False,
repeat_until=False,
repeat_on_month=False,
repeat_on_year=False,
weekdays=False,
repeat_day=False,
repeat_week=False,
repeat_month=False,
count=5)
self.assertEqual(dates[0], datetime(2020, 1, 6, 0, 0))
self.assertEqual(dates[1], datetime(2020, 1, 13, 0, 0))
self.assertEqual(dates[2], datetime(2020, 1, 20, 0, 0))
self.assertEqual(dates[3], datetime(2020, 1, 27, 0, 0))
self.assertEqual(dates[4], datetime(2020, 2, 3, 0, 0))
dates = self.env['project.task.recurrence']._get_next_recurring_dates(
date_start=date(2020, 1, 1),
repeat_interval=3,
repeat_unit='week',
repeat_type='until',
repeat_until=date(2020, 2, 1),
repeat_on_month=False,
repeat_on_year=False,
weekdays=[MO, FR],
repeat_day=False,
repeat_week=False,
repeat_month=False,
count=100)
self.assertEqual(len(dates), 3)
self.assertEqual(dates[0], datetime(2020, 1, 3, 0, 0))
self.assertEqual(dates[1], datetime(2020, 1, 20, 0, 0))
self.assertEqual(dates[2], datetime(2020, 1, 24, 0, 0))
def test_recurrence_next_dates_month(self):
dates = self.env['project.task.recurrence']._get_next_recurring_dates(
date_start=date(2020, 1, 15),
repeat_interval=1,
repeat_unit='month',
repeat_type=False, # Forever
repeat_until=False,
repeat_on_month='date',
repeat_on_year=False,
weekdays=False,
repeat_day=31,
repeat_week=False,
repeat_month=False,
count=12)
# should take the last day of each month
self.assertEqual(dates[0], date(2020, 1, 31))
self.assertEqual(dates[1], date(2020, 2, 29))
self.assertEqual(dates[2], date(2020, 3, 31))
self.assertEqual(dates[3], date(2020, 4, 30))
self.assertEqual(dates[4], date(2020, 5, 31))
self.assertEqual(dates[5], date(2020, 6, 30))
self.assertEqual(dates[6], date(2020, 7, 31))
self.assertEqual(dates[7], date(2020, 8, 31))
self.assertEqual(dates[8], date(2020, 9, 30))
self.assertEqual(dates[9], date(2020, 10, 31))
self.assertEqual(dates[10], date(2020, 11, 30))
self.assertEqual(dates[11], date(2020, 12, 31))
dates = self.env['project.task.recurrence']._get_next_recurring_dates(
date_start=date(2020, 2, 20),
repeat_interval=3,
repeat_unit='month',
repeat_type=False, # Forever
repeat_until=False,
repeat_on_month='date',
repeat_on_year=False,
weekdays=False,
repeat_day=29,
repeat_week=False,
repeat_month=False,
count=5)
self.assertEqual(dates[0], date(2020, 2, 29))
self.assertEqual(dates[1], date(2020, 5, 29))
self.assertEqual(dates[2], date(2020, 8, 29))
self.assertEqual(dates[3], date(2020, 11, 29))
self.assertEqual(dates[4], date(2021, 2, 28))
dates = self.env['project.task.recurrence']._get_next_recurring_dates(
date_start=date(2020, 1, 10),
repeat_interval=1,
repeat_unit='month',
repeat_type='until',
repeat_until=datetime(2020, 5, 31),
repeat_on_month='day',
repeat_on_year=False,
weekdays=[SA(4), ], # 4th Saturday
repeat_day=29,
repeat_week=False,
repeat_month=False,
count=6)
self.assertEqual(len(dates), 5)
self.assertEqual(dates[0], datetime(2020, 1, 25))
self.assertEqual(dates[1], datetime(2020, 2, 22))
self.assertEqual(dates[2], datetime(2020, 3, 28))
self.assertEqual(dates[3], datetime(2020, 4, 25))
self.assertEqual(dates[4], datetime(2020, 5, 23))
def test_recurrence_next_dates_year(self):
dates = self.env['project.task.recurrence']._get_next_recurring_dates(
date_start=date(2020, 12, 1),
repeat_interval=1,
repeat_unit='year',
repeat_type='until',
repeat_until=datetime(2026, 1, 1),
repeat_on_month=False,
repeat_on_year='date',
weekdays=False,
repeat_day=31,
repeat_week=False,
repeat_month='november',
count=10)
self.assertEqual(len(dates), 5)
self.assertEqual(dates[0], datetime(2021, 11, 30))
self.assertEqual(dates[1], datetime(2022, 11, 30))
self.assertEqual(dates[2], datetime(2023, 11, 30))
self.assertEqual(dates[3], datetime(2024, 11, 30))
self.assertEqual(dates[4], datetime(2025, 11, 30))
+78
View File
@@ -351,9 +351,21 @@
</div>
</div>
</div>
<div class="col-lg-6 o_setting_box" id="recurring_tasks_setting" groups="project.group_project_recurring_tasks">
<div class="o_setting_left_pane">
<field name="allow_recurring_tasks" />
</div>
<div class="o_setting_right_pane">
<label for="allow_recurring_tasks" />
<div class="text-muted">
Auto-generate tasks for regular activities
</div>
</div>
</div>
</div>
</page>
</notebook>
<div class="oe_chatter">
<field name="message_follower_ids" options="{'post_refresh':True}" help="Follow this project to automatically track the events associated to tasks and issues of this project." groups="base.group_user"/>
</div>
@@ -444,6 +456,7 @@
<field name="name" class="o_project_name oe_inline"
string="Project Name" placeholder="e.g. Office Party"/>
<field name="user_id" invisible="1"/>
<field name="allow_recurring_tasks" groups="project.group_project_recurring_tasks" />
</group>
<div name="alias_def" colspan="2" attrs="{'invisible': [('alias_domain', '=', False)]}">
<label for="alias_enabled" class="oe_inline" string="Create tasks by sending an email to"/>
@@ -607,6 +620,12 @@
<form string="Task" class="o_form_project_tasks">
<field name="allow_subtasks" invisible="1" />
<field name="is_closed" invisible="1" />
<field name="allow_recurring_tasks" invisible="1" />
<field name="repeat_show_dow" invisible="1" />
<field name="repeat_show_day" invisible="1" />
<field name="repeat_show_week" invisible="1" />
<field name="repeat_show_month" invisible="1" />
<field name="recurrence_id" invisible="1" />
<header>
<button name="action_assign_to_me" string="Assign to Me" type="object" class="oe_highlight"
attrs="{'invisible' : [('user_id', '!=', False)]}"/>
@@ -615,6 +634,10 @@
<div class="text-center alert alert-primary" role="alert" attrs="{'invisible': ['|', ('ribbon_message', '=', False), ('ribbon_message', '=', '')]}">
<field name="ribbon_message"/>
</div>
<div class="alert alert-info oe_edit_only" role="status" attrs="{'invisible': ['|', ('recurring_task', '=', False), ('recurrence_id', '=', False)]}">
<p>Edit recurring task</p>
<field name="recurrence_update" widget="radio"/>
</div>
<sheet string="Task">
<div class="oe_button_box" name="button_box">
<button name="action_subtask" type="object" class="oe_stat_button" icon="fa-tasks"
@@ -624,6 +647,15 @@
<button name="%(rating_rating_action_task)d" type="action" attrs="{'invisible': [('rating_count', '=', 0)]}" class="oe_stat_button" icon="fa-smile-o" groups="project.group_project_rating">
<field name="rating_count" string="Rating" widget="statinfo"/>
</button>
<button name="action_recurring_tasks" type="object" attrs="{'invisible': [('recurrence_id', '=', False)]}" class="oe_stat_button" icon="fa-repeat" groups="project.group_project_recurring_tasks">
<div class="o_field_widget o_stat_info">
<span class="o_stat_value">
<field name="recurring_count" widget="statinfo" nolabel="1" />
Tasks
</span>
<span class="o_stat_text">in Recurrence</span>
</div>
</button>
</div>
<widget name="web_ribbon" title="Archived" bg_color="bg-danger" attrs="{'invisible': [('active', '=', True)]}"/>
<div class="oe_title pr-0">
@@ -654,6 +686,7 @@
attrs="{'invisible' : [('allow_subtasks', '=', False)]}"
/>
<field name="date_deadline" widget="remaining_days" attrs="{'invisible': [('is_closed', '=', True)]}"/>
<field name="recurring_task" attrs="{'invisible': [('allow_recurring_tasks', '=', False)]}" />
<field name="tag_ids" widget="many2many_tags" options="{'color_field': 'color', 'no_create_edit': True}"/>
</group>
</group>
@@ -662,6 +695,51 @@
<field name="description" type="html"/>
<div class="oe_clear"/>
</page>
<page name="recurrence" string="Recurrence" attrs="{'invisible': [('recurring_task', '=', False)]}">
<group>
<group>
<label for="repeat_interval" />
<div class="o_row">
<field name="repeat_interval" attrs="{'required': [('recurring_task', '=', True)]}" />
<field name="repeat_unit" attrs="{'required': [('recurring_task', '=', True)]}" />
</div>
<label for="repeat_on_month" string="Repeat On" attrs="{'invisible': [('repeat_unit', 'not in', ('month', 'year'))]}" />
<div class="o_row">
<field name="repeat_on_month" attrs="{'invisible': [('repeat_unit', '!=', 'month')], 'required': [('repeat_unit', '=', 'month')]}" />
<field name="repeat_on_year" attrs="{'invisible': [('repeat_unit', '!=', 'year')], 'required': [('repeat_unit', '=', 'year')]}" />
<field name="repeat_day" attrs="{'invisible': [('repeat_show_day', '=', False)], 'required': [('repeat_show_day', '=', True)]}" />
<field name="repeat_week" attrs="{'invisible': [('repeat_show_week', '=', False)], 'required': [('repeat_show_week', '=', True)]}" />
<field name="repeat_weekday" attrs="{'invisible': [('repeat_show_week', '=', False)], 'required': [('repeat_show_week', '=', True)]}" />
<span attrs="{'invisible': ['|', ('repeat_show_week', '=', False), ('repeat_show_month', '=', False)]}">of</span>
<field name="repeat_month" attrs="{'invisible': [('repeat_show_month', '=', False)], 'required': [('repeat_show_month', '=', True)]}" />
</div>
<field name="mon" attrs="{'invisible': [('repeat_show_dow', '=', False)]}" />
<field name="tue" attrs="{'invisible': [('repeat_show_dow', '=', False)]}" />
<field name="wed" attrs="{'invisible': [('repeat_show_dow', '=', False)]}" />
<field name="thu" attrs="{'invisible': [('repeat_show_dow', '=', False)]}" />
<field name="fri" attrs="{'invisible': [('repeat_show_dow', '=', False)]}" />
<field name="sat" attrs="{'invisible': [('repeat_show_dow', '=', False)]}" />
<field name="sun" attrs="{'invisible': [('repeat_show_dow', '=', False)]}" />
<label for="repeat_type" />
<div class="o_row">
<field name="repeat_type" attrs="{'required': [('recurring_task', '=', True)]}" />
<field name="repeat_until" attrs="{'invisible': [('repeat_type', '!=', 'until')], 'required': [('repeat_type', '=', 'until')]}" />
<field name="repeat_number" attrs="{'invisible': [('repeat_type', '!=', 'after')], 'required': [('repeat_type', '=', 'after')]}" />
</div>
</group>
</group>
<group attrs="{'invisible': ['|', ('recurring_task', '=', False), ('recurrence_message', '=', False)]}">
<div class="alert alert-success o_form_project_recurrence_message" role="status">
<p><span class="fa fa-check-circle"></span> Next Occurrences:</p>
<field name="recurrence_message" widget="html" class="mb-0" />
</div>
</group>
</page>
<page name="extra_info" string="Extra Info" groups="base.group_no_one">
<group>
<group>
@@ -49,6 +49,17 @@
</div>
</div>
</div>
<div class="col-12 col-lg-6 o_setting_box" id="recurring_tasks_setting">
<div class="o_setting_left_pane">
<field name="group_project_recurring_tasks"/>
</div>
<div class="o_setting_right_pane">
<label for="group_project_recurring_tasks"/>
<div class="text-muted">
Auto-generate tasks for regular activities
</div>
</div>
</div>
</div>
<h2>Time Management</h2>
<div class="row mt16 o_settings_container" name="project_time">
+10
View File
@@ -143,3 +143,13 @@ class ProjectTask(models.Model):
})
action['context'] = context
return action
class ProjectTaskRecurrence(models.Model):
_inherit = 'project.task.recurrence'
def _new_task_values(self, task):
values = super(ProjectTaskRecurrence, self)._new_task_values(task)
task = self.sudo().task_ids[0]
if not task.is_fsm:
values['sale_line_id'] = task.sale_line_id.id
return values
+7
View File
@@ -309,3 +309,10 @@ class ProjectTask(models.Model):
def _get_action_view_so_ids(self):
return list(set((self.sale_order_id + self.timesheet_ids.so_line.order_id).ids))
class ProjectTaskRecurrence(models.Model):
_inherit = 'project.task.recurrence'
@api.model
def _get_recurring_fields(self):
return ['analytic_account_id'] + super(ProjectTaskRecurrence, self)._get_recurring_fields()