diff --git a/addons/calendar/__manifest__.py b/addons/calendar/__manifest__.py index 2b1340d36e2..20466920cd4 100644 --- a/addons/calendar/__manifest__.py +++ b/addons/calendar/__manifest__.py @@ -26,9 +26,9 @@ If you need to manage your meetings, you should install the CRM module. 'security/ir.model.access.csv', 'security/calendar_security.xml', 'data/calendar_cron.xml', + 'data/mail_template_data.xml', 'data/calendar_data.xml', 'data/mail_data_various.xml', - 'data/mail_template_data.xml', 'views/mail_activity_views.xml', 'views/calendar_templates.xml', 'views/calendar_views.xml', diff --git a/addons/calendar/data/calendar_data.xml b/addons/calendar/data/calendar_data.xml index 124cd1abfb3..037117e6852 100644 --- a/addons/calendar/data/calendar_data.xml +++ b/addons/calendar/data/calendar_data.xml @@ -36,12 +36,14 @@ hours email + Email - 6 Hours hours email + - \ No newline at end of file + diff --git a/addons/calendar/models/calendar_alarm.py b/addons/calendar/models/calendar_alarm.py index 7fcb5ba0aad..2ea15fd017b 100644 --- a/addons/calendar/models/calendar_alarm.py +++ b/addons/calendar/models/calendar_alarm.py @@ -21,6 +21,12 @@ class Alarm(models.Model): 'Duration in minutes', store=True, search='_search_duration_minutes', compute='_compute_duration_minutes', help="Duration in minutes") + mail_template_id = fields.Many2one( + 'mail.template', string="Email Template", + domain=[('model', 'in', ['calendar.attendee'])], + compute='_compute_mail_template_id', readonly=False, store=True, + help="Template used to render mail reminder content.") + body = fields.Text("Additional Message", help="Additional message that would be sent with the notification for the reminder") @api.depends('interval', 'duration') def _compute_duration_minutes(self): @@ -34,6 +40,14 @@ class Alarm(models.Model): else: alarm.duration_minutes = 0 + @api.depends('alarm_type', 'mail_template_id') + def _compute_mail_template_id(self): + for alarm in self: + if alarm.alarm_type == 'email' and not alarm.mail_template_id: + alarm.mail_template_id = self.env['ir.model.data'].xmlid_to_res_id('calendar.calendar_template_meeting_reminder') + elif alarm.alarm_type != 'email' or not alarm.mail_template_id: + alarm.mail_template_id = False + def _search_duration_minutes(self, operator, value): return [ '|', '|', diff --git a/addons/calendar/models/calendar_alarm_manager.py b/addons/calendar/models/calendar_alarm_manager.py index 45af762a7a4..7babd72d105 100644 --- a/addons/calendar/models/calendar_alarm_manager.py +++ b/addons/calendar/models/calendar_alarm_manager.py @@ -5,6 +5,7 @@ import logging from datetime import timedelta from odoo import api, fields, models +from odoo.tools import plaintext2html _logger = logging.getLogger(__name__) @@ -135,7 +136,7 @@ class AlarmManager(models.AbstractModel): }) return result - def _get_events_to_notify(self, ttype): + def _get_events_by_alarm_to_notify(self, alarm_type): """ Get the events with an alarm of the given type between the cron last call and now. @@ -146,7 +147,7 @@ class AlarmManager(models.AbstractModel): already. """ self.env.cr.execute(''' - SELECT "event"."id" + SELECT "alarm"."id", "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" @@ -157,20 +158,33 @@ class AlarmManager(models.AbstractModel): 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' - )''', [ttype, self.env.context['lastcall']]) + )''', [alarm_type, self.env.context['lastcall']]) - ids = [row[0] for row in self.env.cr.fetchall()] - return self.env['calendar.event'].browse(ids) + events_by_alarm = {} + for alarm_id, event_id in self.env.cr.fetchall(): + events_by_alarm.setdefault(alarm_id, list()).append(event_id) + return events_by_alarm @api.model def _send_reminder(self): # Executed via cron - events = self._get_events_to_notify('email') + events_by_alarm = self._get_events_by_alarm_to_notify('email') + if not events_by_alarm: + return + + event_ids = list(set(event_id for event_ids in events_by_alarm.values() for event_id in event_ids)) + events = self.env['calendar.event'].browse(event_ids) attendees = events.attendee_ids.filtered(lambda a: a.state != 'declined') - attendees.with_context(calendar_template_ignore_recurrence=True)._send_mail_to_attendees( - self.env.ref('calendar.calendar_template_meeting_reminder', raise_if_not_found=False), - force_send=True, - ) + alarms = self.env['calendar.alarm'].browse(events_by_alarm.keys()) + for alarm in alarms: + alarm_attendees = attendees.filtered(lambda attendee: attendee.event_id.id in events_by_alarm[alarm.id]) + alarm_attendees.with_context( + mail_notify_force_send=True, + calendar_template_ignore_recurrence=True + )._send_mail_to_attendees( + alarm.mail_template_id, + force_send=True + ) @api.model def get_next_notif(self): @@ -198,6 +212,8 @@ class AlarmManager(models.AbstractModel): if alarm.alarm_type == 'notification': message = meeting.display_time + if alarm.body: + message += '

%s

' % plaintext2html(alarm.body) delta = alert['notify_at'] - fields.Datetime.now() delta = delta.seconds + delta.days * 3600 * 24 diff --git a/addons/calendar/views/calendar_views.xml b/addons/calendar/views/calendar_views.xml index 545dd732253..0ec4a0bac11 100644 --- a/addons/calendar/views/calendar_views.xml +++ b/addons/calendar/views/calendar_views.xml @@ -23,7 +23,7 @@ calendar.alarm.tree calendar.alarm - + @@ -33,23 +33,32 @@ - calendar.alarm.form - calendar.alarm - -
- + calendar.alarm.form + calendar.alarm + + + - - + + + + + + + + + - - +
@@ -57,6 +66,7 @@ Calendar Alarm calendar.alarm + tree,form diff --git a/addons/calendar_sms/__manifest__.py b/addons/calendar_sms/__manifest__.py index efcff3de620..5799c4c2432 100644 --- a/addons/calendar_sms/__manifest__.py +++ b/addons/calendar_sms/__manifest__.py @@ -3,6 +3,7 @@ { 'name': "Calendar - SMS", + 'version': "1.1", 'summary': 'Send text messages as event reminders', 'description': "Send text messages as event reminders", 'category': 'Hidden', diff --git a/addons/calendar_sms/models/calendar_alarm.py b/addons/calendar_sms/models/calendar_alarm.py index 3c83268bba7..d69c1b17406 100644 --- a/addons/calendar_sms/models/calendar_alarm.py +++ b/addons/calendar_sms/models/calendar_alarm.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- # Part of Odoo. See LICENSE file for full copyright and licensing details. -from odoo import fields, models +from odoo import api, fields, models class CalendarAlarm(models.Model): @@ -10,3 +10,16 @@ class CalendarAlarm(models.Model): alarm_type = fields.Selection(selection_add=[ ('sms', 'SMS Text Message') ], ondelete={'sms': 'set default'}) + sms_template_id = fields.Many2one( + 'sms.template', string="SMS Template", + domain=[('model', 'in', ['calendar.event'])], + compute='_compute_sms_template_id', readonly=False, store=True, + help="Template used to render SMS reminder content.") + + @api.depends('alarm_type', 'mail_template_id') + def _compute_sms_template_id(self): + for alarm in self: + if alarm.alarm_type == 'sms' and not alarm.sms_template_id: + alarm.sms_template_id = self.env['ir.model.data'].xmlid_to_res_id('calendar_sms.sms_template_data_calendar_reminder') + elif alarm.alarm_type != 'sms' or not alarm.sms_template_id: + alarm.sms_template_id = False diff --git a/addons/calendar_sms/models/calendar_alarm_manager.py b/addons/calendar_sms/models/calendar_alarm_manager.py index 8857c3c7607..bd2f4418c70 100644 --- a/addons/calendar_sms/models/calendar_alarm_manager.py +++ b/addons/calendar_sms/models/calendar_alarm_manager.py @@ -12,4 +12,13 @@ class AlarmManager(models.AbstractModel): """ Cron method, overridden here to send SMS reminders as well """ super()._send_reminder() - self._get_events_to_notify(ttype='sms')._do_sms_reminder() + alarms_by_event = self._get_events_by_alarm_to_notify(ttype='sms') + if not alarms_by_event: + return + + events = self.env['calendar.event'].browse(list(alarms_by_event.keys())) + attendees = events.attendee_ids.filtered(lambda a: a.state != 'declined') + for event_id in alarms_by_event.keys(): + event_alarms = attendees.event_id.alarm_ids.filtered(lambda alarm: alarm.id in alarms_by_event.get(event_id, [])) + for alarm in event_alarms: + events.browse(event_id)._do_sms_reminder(alarm) diff --git a/addons/calendar_sms/models/calendar_event.py b/addons/calendar_sms/models/calendar_event.py index 172c209c20d..deaca1e0dfb 100644 --- a/addons/calendar_sms/models/calendar_event.py +++ b/addons/calendar_sms/models/calendar_event.py @@ -13,11 +13,11 @@ class CalendarEvent(models.Model): """ return self.mapped('attendee_ids').filtered(lambda att: att.state != 'declined').mapped('partner_id') - def _do_sms_reminder(self): + def _do_sms_reminder(self, alarm): """ Send an SMS text reminder to attendees that haven't declined the event """ for event in self: event._message_sms_with_template( - template_xmlid='calendar_sms.sms_template_data_calendar_reminder', + template=alarm.sms_template_id, template_fallback=_("Event reminder: %(name)s, %(time)s.", name=event.name, time=event.display_time), partner_ids=self._sms_get_default_partners().ids, put_in_queue=False diff --git a/addons/calendar_sms/views/calendar_views.xml b/addons/calendar_sms/views/calendar_views.xml index ea7def35501..f5d8eda5284 100644 --- a/addons/calendar_sms/views/calendar_views.xml +++ b/addons/calendar_sms/views/calendar_views.xml @@ -1,6 +1,18 @@ + + calendar.alarm.view.form.inherit.calendar.sms + calendar.alarm + + + + + + + + Send SMS diff --git a/addons/hr_holidays/tests/test_company_leave.py b/addons/hr_holidays/tests/test_company_leave.py index 81d20fe8f06..31d3b9cf38b 100644 --- a/addons/hr_holidays/tests/test_company_leave.py +++ b/addons/hr_holidays/tests/test_company_leave.py @@ -313,7 +313,7 @@ class TestCompanyLeave(TransactionCase): }) company_leave._compute_date_from_to() - with self.assertQueryCount(__system__=737, admin=865): + with self.assertQueryCount(__system__=739, admin=865): # Original query count: 1987 # Without tracking/activity context keys: 5154 company_leave.action_validate() diff --git a/addons/hr_work_entry_holidays/tests/test_performance.py b/addons/hr_work_entry_holidays/tests/test_performance.py index cad64988199..cd823539586 100644 --- a/addons/hr_work_entry_holidays/tests/test_performance.py +++ b/addons/hr_work_entry_holidays/tests/test_performance.py @@ -31,7 +31,7 @@ class TestWorkEntryHolidaysPerformance(TestWorkEntryHolidaysBase): self.richard_emp.generate_work_entries(date(2018, 1, 1), date(2018, 1, 2)) leave = self.create_leave(datetime(2018, 1, 1, 7, 0), datetime(2018, 1, 1, 18, 0)) - with self.assertQueryCount(__system__=91, admin=99): + with self.assertQueryCount(__system__=92, admin=99): leave.action_validate() leave.action_refuse()