diff options
Diffstat (limited to 'addons/calendar/models/calendar_alarm_manager.py')
| -rw-r--r-- | addons/calendar/models/calendar_alarm_manager.py | 224 |
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) |
