1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
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)
|