diff options
| author | stephanchrst <stephanchrst@gmail.com> | 2022-05-10 21:51:50 +0700 |
|---|---|---|
| committer | stephanchrst <stephanchrst@gmail.com> | 2022-05-10 21:51:50 +0700 |
| commit | 3751379f1e9a4c215fb6eb898b4ccc67659b9ace (patch) | |
| tree | a44932296ef4a9b71d5f010906253d8c53727726 /addons/project/models/project_task_recurrence.py | |
| parent | 0a15094050bfde69a06d6eff798e9a8ddf2b8c21 (diff) | |
initial commit 2
Diffstat (limited to 'addons/project/models/project_task_recurrence.py')
| -rw-r--r-- | addons/project/models/project_task_recurrence.py | 271 |
1 files changed, 271 insertions, 0 deletions
diff --git a/addons/project/models/project_task_recurrence.py b/addons/project/models/project_task_recurrence.py new file mode 100644 index 00000000..50d861a6 --- /dev/null +++ b/addons/project/models/project_task_recurrence.py @@ -0,0 +1,271 @@ +# -*- 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])) + if i == 0 and start < date_start: + # Ensure the next recurrence is in the future + start += relativedelta(months=repeat_interval) + 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.id + create_values['user_id'] = False + return create_values + + def _create_next_task(self): + for recurrence in self: + task = recurrence.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 = recurrence._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' + ): + if recurrence.repeat_type == 'after' and recurrence.recurrence_left == 0: + recurrence.next_recurrence_date = False + else: + 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 + + @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)]) + 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() + + @api.model + 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 |
