diff --git a/addons/calendar/data/calendar_cron.xml b/addons/calendar/data/calendar_cron.xml
index d0bce74dd9f..6502fffde90 100644
--- a/addons/calendar/data/calendar_cron.xml
+++ b/addons/calendar/data/calendar_cron.xml
@@ -6,11 +6,11 @@
Calendar: Event Reminder
code
- model.get_next_mail()
+ model._send_reminder_email()
- 30
- minutes
+ 1
+ days
-1
diff --git a/addons/calendar/models/calendar_alarm_manager.py b/addons/calendar/models/calendar_alarm_manager.py
index af7a22789f6..2503245aede 100644
--- a/addons/calendar/models/calendar_alarm_manager.py
+++ b/addons/calendar/models/calendar_alarm_manager.py
@@ -116,55 +116,55 @@ class AlarmManager(models.AbstractModel):
"""
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)
+ past = one_date - timedelta(minutes=(missing * event_maxdelta))
+ future = fields.Datetime.now() + timedelta(seconds=in_the_next_X_seconds)
+ if future <= past:
+ return result
+ for alarm in event.alarm_ids:
+ if alarm.alarm_type != alarm_type:
+ continue
+ past = one_date - timedelta(minutes=(missing * alarm.duration_minutes))
+ if future <= past:
+ continue
+ if after and past <= fields.Datetime.from_string(after):
+ continue
+ result.append({
+ 'alarm_id': alarm.id,
+ 'event_id': event.id,
+ 'notify_at': one_date - timedelta(minutes=alarm.duration_minutes),
+ })
return result
@api.model
- def get_next_mail(self):
- return self._get_partner_next_mail(partners=None)
+ def _send_reminder_email(self, partners=None):
+ # Executed via cron
+ self.env.cr.execute('''
+ SELECT "event"."id"
+ FROM "calendar_event" AS "event"
+ JOIN "calendar_alarm_calendar_event_rel" AS "event_alarm_rel"
+ ON "event"."id" = "event_alarm_rel"."calendar_event_id"
+ JOIN "calendar_alarm" AS "alarm"
+ ON "event_alarm_rel"."calendar_alarm_id" = "alarm"."id"
+ WHERE (
+ "alarm"."alarm_type" = 'email'
+ AND "event"."active"
+ AND "event"."start" - CAST("alarm"."duration" || ' ' || "alarm"."interval" AS Interval) >= %s
+ AND "event"."start" - CAST("alarm"."duration" || ' ' || "alarm"."interval" AS Interval) < now() at time zone 'utc'
+ )''', [self.env.context['lastcall']])
- @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())
+ domain = [
+ ("event_id", "in", self.env.cr.fetchall()),
+ ("state", "!=", "declined"),
+ ]
+ if partners is not None:
+ domain.append(("partner_id", "in", partners.ids))
- 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)
+ attendees = self.env["calendar.attendee"].search(domain)
+ attendees._send_mail_to_attendees(
+ 'calendar.calendar_template_meeting_reminder',
+ force_send=True,
+ ignore_recurrence=True,
+ )
@api.model
def get_next_notif(self):
@@ -186,15 +186,6 @@ class AlarmManager(models.AbstractModel):
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'])
diff --git a/addons/calendar/models/calendar_event.py b/addons/calendar/models/calendar_event.py
index 3ed48e65847..3a48a8c314e 100644
--- a/addons/calendar/models/calendar_event.py
+++ b/addons/calendar/models/calendar_event.py
@@ -328,7 +328,7 @@ class Meeting(models.Model):
@api.depends('stop', 'start')
def _compute_duration(self):
- for event in self.with_context(dont_notify=True):
+ for event in self:
event.duration = self._get_duration(event.start, event.stop)
@api.depends('start', 'duration')
@@ -675,11 +675,7 @@ class Meeting(models.Model):
(detached_events & self).active = False
(detached_events - self).with_context(archive_on_error=True).unlink()
- # Notify attendees if there is an alarm on the modified event, or if there was an alarm
- # that has just been removed, as it might have changed their next event notification
- if not self._context.get('dont_notify'):
- if self.alarm_ids or values.get('alarm_ids'):
- self.env['calendar.alarm_manager']._notify_next_alarm(self.partner_ids.ids)
+ self._setup_alarms()
current_attendees = self.filtered('active').attendee_ids
if 'partner_ids' in values:
@@ -692,6 +688,15 @@ class Meeting(models.Model):
return True
+ def _setup_alarms(self):
+ """ Trigger the cron in the future for every email reminder """
+ cron = self.env.ref('calendar.ir_cron_scheduler_alarm')
+ for event in self:
+ for alarm in (alarm for alarm in event.alarm_ids if alarm.alarm_type == 'email'):
+ cron._trigger(at=event.start-timedelta(minutes=alarm.duration_minutes))
+ if any(alarm.alarm_type == 'notification' for alarm in event.alarm_ids):
+ self.env['calendar.alarm_manager']._notify_next_alarm(event.partner_ids.ids)
+
@api.model_create_multi
def create(self, vals_list):
vals_list = [ # Else bug with quick_create when we are filter on an other user
@@ -741,12 +746,8 @@ class Meeting(models.Model):
events.filtered(lambda event: event.start > fields.Datetime.now()).attendee_ids._send_mail_to_attendees('calendar.calendar_template_meeting_invitation')
events._sync_activities(fields={f for vals in vals_list for f in vals.keys() })
- # Notify attendees if there is an alarm on the created event, as it might have changed their
- # next event notification
- if not self._context.get('dont_notify'):
- for event in events:
- if len(event.alarm_ids) > 0:
- self.env['calendar.alarm_manager']._notify_next_alarm(event.partner_ids.ids)
+ events._setup_alarms()
+
return events
def read(self, fields=None, load='_classic_read'):
diff --git a/addons/calendar/models/calendar_recurrence.py b/addons/calendar/models/calendar_recurrence.py
index 6c5edfe839c..2c7e04a0ad4 100644
--- a/addons/calendar/models/calendar_recurrence.py
+++ b/addons/calendar/models/calendar_recurrence.py
@@ -284,7 +284,7 @@ class RecurrenceRule(models.Model):
:param dstart: if provided, only write events starting from this point in time
"""
events = self._get_events_from(dtstart) if dtstart else self.calendar_event_ids
- return events.with_context(no_mail_to_attendees=True, dont_notify=True).write(dict(values, recurrence_update='self_only'))
+ return events.with_context(no_mail_to_attendees=True).write(dict(values, recurrence_update='self_only'))
def _rrule_serialize(self):
"""
diff --git a/addons/calendar/tests/test_event_notifications.py b/addons/calendar/tests/test_event_notifications.py
index 644e08902cb..611b4b36630 100644
--- a/addons/calendar/tests/test_event_notifications.py
+++ b/addons/calendar/tests/test_event_notifications.py
@@ -142,6 +142,9 @@ class TestEventNotifications(TransactionCase, MailCase):
self.assertEqual(notif, bus_message)
def test_email_alarm(self):
+ cron_id = self.env.ref('calendar.ir_cron_scheduler_alarm').id
+ triggers_before = self.env['ir.cron.trigger'].search([('cron_id', '=', cron_id)])
+
alarm = self.env['calendar.alarm'].create({
'name': 'Alarm',
'alarm_type': 'email',
@@ -152,12 +155,18 @@ class TestEventNotifications(TransactionCase, MailCase):
self.event.write({
'start': now + relativedelta(minutes=15),
'stop': now + relativedelta(minutes=18),
- 'partner_ids': [(4, self.partner.id)],
- 'alarm_ids': [(4, alarm.id)],
+ 'partner_ids': [fields.Command.link(self.partner.id)],
+ 'alarm_ids': [fields.Command.link(alarm.id)],
})
+
+ triggers_after = self.env['ir.cron.trigger'].search([('cron_id', '=', cron_id)])
+ new_triggers = triggers_after - triggers_before
+ new_triggers.ensure_one()
+ self.assertEqual(new_triggers.call_at, now - relativedelta(minutes=5))
+
with patch.object(fields.Datetime, 'now', lambda: now):
with self.assertSinglePostNotifications([{'partner': self.partner, 'type': 'inbox'}], {
'message_type': 'user_notification',
'subtype': 'mail.mt_note',
}):
- self.env['calendar.alarm_manager'].with_context(lastcall=now - relativedelta(minutes=15))._get_partner_next_mail(self.partner)
+ self.env['calendar.alarm_manager'].with_context(lastcall=now - relativedelta(minutes=15))._send_reminder_email(self.partner)