# Copyright 2020 Brainbean Apps (https://brainbeanapps.com) # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). from datetime import datetime, time from dateutil.relativedelta import relativedelta from pytz import UTC, timezone from odoo import _, api, fields, models class MailActivity(models.Model): _inherit = "mail.activity" next_reminder = fields.Datetime( string="Next reminder", compute="_compute_next_reminder", compute_sudo=True, store=True, ) last_reminder_local = fields.Datetime(string="Last reminder (local)",) deadline = fields.Datetime( string="Deadline", compute="_compute_deadline", compute_sudo=True, store=True, ) @api.model def _get_activities_to_remind_domain(self): """Hook for extensions""" return [ ("next_reminder", "<=", fields.Datetime.now()), ("deadline", ">=", fields.Datetime.now()), ] @api.model def _get_activities_to_remind(self): return self.search(self._get_activities_to_remind_domain()) @api.model def _process_reminders(self): activities = self._get_activities_to_remind() activities.action_remind() return activities @api.multi @api.depends( "user_id.tz", "activity_type_id.reminders", "deadline", "last_reminder_local", ) def _compute_next_reminder(self): now = fields.Datetime.now() for activity in self: if activity.deadline < now: activity.next_reminder = None continue reminders = activity.activity_type_id._get_reminder_offsets() if not reminders: activity.next_reminder = None continue reminders.sort(reverse=True) tz = timezone(activity.user_id.sudo().tz or "UTC") last_reminder_local = ( tz.localize(activity.last_reminder_local) if activity.last_reminder_local else None ) local_deadline = tz.localize( datetime.combine( activity.date_deadline, time.min, # Schedule reminder based of beginning of day ) ) for reminder in reminders: next_reminder_local = local_deadline - relativedelta(days=reminder,) if not last_reminder_local or next_reminder_local > last_reminder_local: break if last_reminder_local and next_reminder_local <= last_reminder_local: activity.next_reminder = None continue activity.next_reminder = next_reminder_local.astimezone(UTC).replace( tzinfo=None ) @api.multi @api.depends("user_id.tz", "date_deadline") def _compute_deadline(self): for activity in self: tz = timezone(activity.user_id.sudo().tz or "UTC") activity.deadline = ( tz.localize(datetime.combine(activity.date_deadline, time.max)) .astimezone(UTC) .replace(tzinfo=None) ) @api.multi def action_notify(self): super().action_notify() utc_now = fields.Datetime.now().replace(tzinfo=UTC) for activity in self: if activity.last_reminder_local: continue tz = timezone(activity.user_id.sudo().tz or "UTC") activity.last_reminder_local = utc_now.astimezone(tz).replace(tzinfo=None) @api.multi def action_remind(self): IrModel = self.env["ir.model"] MailThread = self.env["mail.thread"] message_activity_assigned = self.env.ref("mail.message_activity_assigned") utc_now = fields.Datetime.now().replace(tzinfo=UTC) for activity in self: tz = timezone(activity.user_id.sudo().tz or "UTC") local_now = utc_now.astimezone(tz) model_description = IrModel._get(activity.res_model).display_name subject = _("%s: %s assigned to you, %d day(s) remaining") % ( activity.res_name, activity.summary or activity.activity_type_id.name, (activity.date_deadline - local_now.date()).days, ) body = message_activity_assigned.render( dict(activity=activity, model_description=model_description), engine="ir.qweb", minimal_qcontext=True, ) MailThread.message_notify( partner_ids=activity.user_id.partner_id.ids, body=body, subject=subject, record_name=activity.res_name, model_description=model_description, notif_layout="mail.mail_notification_light", ) activity.last_reminder_local = local_now.replace(tzinfo=None)