summaryrefslogtreecommitdiff
path: root/addons/calendar/models/calendar_alarm_manager.py
diff options
context:
space:
mode:
Diffstat (limited to 'addons/calendar/models/calendar_alarm_manager.py')
-rw-r--r--addons/calendar/models/calendar_alarm_manager.py224
1 files changed, 224 insertions, 0 deletions
diff --git a/addons/calendar/models/calendar_alarm_manager.py b/addons/calendar/models/calendar_alarm_manager.py
new file mode 100644
index 00000000..2b931b4d
--- /dev/null
+++ b/addons/calendar/models/calendar_alarm_manager.py
@@ -0,0 +1,224 @@
+# -*- coding: utf-8 -*-
+# Part of Odoo. See LICENSE file for full copyright and licensing details.
+
+import logging
+from datetime import timedelta
+
+from odoo import api, fields, models
+
+_logger = logging.getLogger(__name__)
+
+
+class AlarmManager(models.AbstractModel):
+ _name = 'calendar.alarm_manager'
+ _description = 'Event Alarm Manager'
+
+ def _get_next_potential_limit_alarm(self, alarm_type, seconds=None, partners=None):
+ result = {}
+ delta_request = """
+ SELECT
+ rel.calendar_event_id, max(alarm.duration_minutes) AS max_delta,min(alarm.duration_minutes) AS min_delta
+ FROM
+ calendar_alarm_calendar_event_rel AS rel
+ LEFT JOIN calendar_alarm AS alarm ON alarm.id = rel.calendar_alarm_id
+ WHERE alarm.alarm_type = %s
+ GROUP BY rel.calendar_event_id
+ """
+ base_request = """
+ SELECT
+ cal.id,
+ cal.start - interval '1' minute * calcul_delta.max_delta AS first_alarm,
+ CASE
+ WHEN cal.recurrency THEN rrule.until - interval '1' minute * calcul_delta.min_delta
+ ELSE cal.stop - interval '1' minute * calcul_delta.min_delta
+ END as last_alarm,
+ cal.start as first_event_date,
+ CASE
+ WHEN cal.recurrency THEN rrule.until
+ ELSE cal.stop
+ END as last_event_date,
+ calcul_delta.min_delta,
+ calcul_delta.max_delta,
+ rrule.rrule AS rule
+ FROM
+ calendar_event AS cal
+ RIGHT JOIN calcul_delta ON calcul_delta.calendar_event_id = cal.id
+ LEFT JOIN calendar_recurrence as rrule ON rrule.id = cal.recurrence_id
+ """
+
+ filter_user = """
+ RIGHT JOIN calendar_event_res_partner_rel AS part_rel ON part_rel.calendar_event_id = cal.id
+ AND part_rel.res_partner_id IN %s
+ """
+
+ # Add filter on alarm type
+ tuple_params = (alarm_type,)
+
+ # Add filter on partner_id
+ if partners:
+ base_request += filter_user
+ tuple_params += (tuple(partners.ids), )
+
+ # Upper bound on first_alarm of requested events
+ first_alarm_max_value = ""
+ if seconds is None:
+ # first alarm in the future + 3 minutes if there is one, now otherwise
+ first_alarm_max_value = """
+ COALESCE((SELECT MIN(cal.start - interval '1' minute * calcul_delta.max_delta)
+ FROM calendar_event cal
+ RIGHT JOIN calcul_delta ON calcul_delta.calendar_event_id = cal.id
+ WHERE cal.start - interval '1' minute * calcul_delta.max_delta > now() at time zone 'utc'
+ ) + interval '3' minute, now() at time zone 'utc')"""
+ else:
+ # now + given seconds
+ first_alarm_max_value = "(now() at time zone 'utc' + interval '%s' second )"
+ tuple_params += (seconds,)
+
+ self._cr.execute("""
+ WITH calcul_delta AS (%s)
+ SELECT *
+ FROM ( %s WHERE cal.active = True ) AS ALL_EVENTS
+ WHERE ALL_EVENTS.first_alarm < %s
+ AND ALL_EVENTS.last_event_date > (now() at time zone 'utc')
+ """ % (delta_request, base_request, first_alarm_max_value), tuple_params)
+
+ for event_id, first_alarm, last_alarm, first_meeting, last_meeting, min_duration, max_duration, rule in self._cr.fetchall():
+ result[event_id] = {
+ 'event_id': event_id,
+ 'first_alarm': first_alarm,
+ 'last_alarm': last_alarm,
+ 'first_meeting': first_meeting,
+ 'last_meeting': last_meeting,
+ 'min_duration': min_duration,
+ 'max_duration': max_duration,
+ 'rrule': rule
+ }
+
+ # determine accessible events
+ events = self.env['calendar.event'].browse(result)
+ result = {
+ key: result[key]
+ for key in set(events._filter_access_rules('read').ids)
+ }
+ return result
+
+ def do_check_alarm_for_one_date(self, one_date, event, event_maxdelta, in_the_next_X_seconds, alarm_type, after=False, missing=False):
+ """ Search for some alarms in the interval of time determined by some parameters (after, in_the_next_X_seconds, ...)
+ :param one_date: date of the event to check (not the same that in the event browse if recurrent)
+ :param event: Event browse record
+ :param event_maxdelta: biggest duration from alarms for this event
+ :param in_the_next_X_seconds: looking in the future (in seconds)
+ :param after: if not False: will return alert if after this date (date as string - todo: change in master)
+ :param missing: if not False: will return alert even if we are too late
+ :param notif: Looking for type notification
+ :param mail: looking for type email
+ """
+ result = []
+ # TODO: remove event_maxdelta and if using it
+ if one_date - timedelta(minutes=(missing * event_maxdelta)) < fields.Datetime.now() + timedelta(seconds=in_the_next_X_seconds): # if an alarm is possible for this date
+ for alarm in event.alarm_ids:
+ if alarm.alarm_type == alarm_type and \
+ one_date - timedelta(minutes=(missing * alarm.duration_minutes)) < fields.Datetime.now() + timedelta(seconds=in_the_next_X_seconds) and \
+ (not after or one_date - timedelta(minutes=alarm.duration_minutes) > fields.Datetime.from_string(after)):
+ alert = {
+ 'alarm_id': alarm.id,
+ 'event_id': event.id,
+ 'notify_at': one_date - timedelta(minutes=alarm.duration_minutes),
+ }
+ result.append(alert)
+ return result
+
+ @api.model
+ def get_next_mail(self):
+ return self._get_partner_next_mail(partners=None)
+
+ @api.model
+ def _get_partner_next_mail(self, partners=None):
+ self = self.with_context(mail_notify_force_send=True)
+ last_notif_mail = fields.Datetime.to_string(self.env.context.get('lastcall') or fields.Datetime.now())
+
+ cron = self.env.ref('calendar.ir_cron_scheduler_alarm', raise_if_not_found=False)
+ if not cron:
+ _logger.error("Cron for " + self._name + " can not be identified !")
+ return False
+
+ interval_to_second = {
+ "weeks": 7 * 24 * 60 * 60,
+ "days": 24 * 60 * 60,
+ "hours": 60 * 60,
+ "minutes": 60,
+ "seconds": 1
+ }
+
+ if cron.interval_type not in interval_to_second:
+ _logger.error("Cron delay can not be computed !")
+ return False
+
+ cron_interval = cron.interval_number * interval_to_second[cron.interval_type]
+
+ all_meetings = self._get_next_potential_limit_alarm('email', seconds=cron_interval, partners=partners)
+
+ for meeting in self.env['calendar.event'].browse(all_meetings):
+ max_delta = all_meetings[meeting.id]['max_duration']
+ in_date_format = meeting.start
+ last_found = self.do_check_alarm_for_one_date(in_date_format, meeting, max_delta, 0, 'email', after=last_notif_mail, missing=True)
+ for alert in last_found:
+ self.do_mail_reminder(alert)
+
+ @api.model
+ def get_next_notif(self):
+ partner = self.env.user.partner_id
+ all_notif = []
+
+ if not partner:
+ return []
+
+ all_meetings = self._get_next_potential_limit_alarm('notification', partners=partner)
+ time_limit = 3600 * 24 # return alarms of the next 24 hours
+ for event_id in all_meetings:
+ max_delta = all_meetings[event_id]['max_duration']
+ meeting = self.env['calendar.event'].browse(event_id)
+ in_date_format = fields.Datetime.from_string(meeting.start)
+ last_found = self.do_check_alarm_for_one_date(in_date_format, meeting, max_delta, time_limit, 'notification', after=partner.calendar_last_notif_ack)
+ if last_found:
+ for alert in last_found:
+ all_notif.append(self.do_notif_reminder(alert))
+ return all_notif
+
+ def do_mail_reminder(self, alert):
+ meeting = self.env['calendar.event'].browse(alert['event_id'])
+ alarm = self.env['calendar.alarm'].browse(alert['alarm_id'])
+
+ result = False
+ if alarm.alarm_type == 'email':
+ result = meeting.attendee_ids.filtered(lambda r: r.state != 'declined')._send_mail_to_attendees('calendar.calendar_template_meeting_reminder', force_send=True, ignore_recurrence=True)
+ return result
+
+ def do_notif_reminder(self, alert):
+ alarm = self.env['calendar.alarm'].browse(alert['alarm_id'])
+ meeting = self.env['calendar.event'].browse(alert['event_id'])
+
+ if alarm.alarm_type == 'notification':
+ message = meeting.display_time
+
+ delta = alert['notify_at'] - fields.Datetime.now()
+ delta = delta.seconds + delta.days * 3600 * 24
+
+ return {
+ 'alarm_id': alarm.id,
+ 'event_id': meeting.id,
+ 'title': meeting.name,
+ 'message': message,
+ 'timer': delta,
+ 'notify_at': fields.Datetime.to_string(alert['notify_at']),
+ }
+
+ def _notify_next_alarm(self, partner_ids):
+ """ Sends through the bus the next alarm of given partners """
+ notifications = []
+ users = self.env['res.users'].search([('partner_id', 'in', tuple(partner_ids))])
+ for user in users:
+ notif = self.with_user(user).with_context(allowed_company_ids=user.company_ids.ids).get_next_notif()
+ notifications.append([(self._cr.dbname, 'calendar.alarm', user.partner_id.id), notif])
+ if len(notifications) > 0:
+ self.env['bus.bus'].sendmany(notifications)