From 2e2fd3f8a74d30357d9079cf5cccb0d27d5bf4dd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thibault=20Delavall=C3=A9e?= Date: Thu, 18 Jul 2019 10:43:31 +0000 Subject: [PATCH 1/2] [IMP] event: improve test class and data to help reusing them PURPOSE Allow the user to schedule SMS communication on events SPECIFICATIONS In order to add tests this commit performs a quick cleaning of event common class to speedup and prepare new tests. Containing * move data creation in class and use savepoint; * move registration creation in its own method to allow calling it if necessary; * clean some unnecessary variables. LINKS Task 1922187 Part of PR #34705 --- addons/event/tests/common.py | 53 ++++++++++++++---------- addons/event/tests/test_event_flow.py | 16 +++---- addons/event/tests/test_mail_schedule.py | 12 +++--- 3 files changed, 44 insertions(+), 37 deletions(-) diff --git a/addons/event/tests/common.py b/addons/event/tests/common.py index dbd142a08be..06de8639651 100644 --- a/addons/event/tests/common.py +++ b/addons/event/tests/common.py @@ -6,47 +6,54 @@ from odoo import fields from odoo.tests import common -class TestEventCommon(common.TransactionCase): +class TestEventCommon(common.SavepointCase): - def setUp(self): - super(TestEventCommon, self).setUp() - - # Usefull models - self.Users = self.env['res.users'] - self.Event = self.env['event.event'] - self.Registration = self.env['event.registration'] - self.EventMail = self.env['event.mail'] + @classmethod + def setUpClass(cls): + super(TestEventCommon, cls).setUpClass() # User groups - self.group_employee_id = self.env['ir.model.data'].xmlid_to_res_id('base.group_user') - self.group_event_user_id = self.env['ir.model.data'].xmlid_to_res_id('event.group_event_user') - self.group_event_manager_id = self.env['ir.model.data'].xmlid_to_res_id('event.group_event_manager') - group_system = self.env.ref('base.group_system') + cls.group_employee_id = cls.env.ref('base.group_user').id + cls.group_event_user_id = cls.env.ref('event.group_event_user').id + cls.group_event_manager_id = cls.env.ref('event.group_event_manager').id + cls.group_system_id = cls.env.ref('base.group_system').id # Test users to use through the various tests - self.user_eventuser = self.Users.with_context({'no_reset_password': True}).create({ + cls.user_eventuser = cls.env['res.users'].with_context({'no_reset_password': True}).create({ 'name': 'Armande EventUser', 'login': 'Armande', 'email': 'armande.eventuser@example.com', 'tz': 'Europe/Brussels', - 'groups_id': [(6, 0, [self.group_employee_id, self.group_event_user_id])] + 'groups_id': [(6, 0, [cls.group_employee_id, cls.group_event_user_id])] }) - self.user_eventmanager = self.Users.with_context({'no_reset_password': True}).create({ + cls.user_eventmanager = cls.env['res.users'].with_context({'no_reset_password': True}).create({ 'name': 'Bastien EventManager', 'login': 'bastien', 'email': 'bastien.eventmanager@example.com', 'tz': 'Europe/Brussels', 'groups_id': [(6, 0, [ - self.group_employee_id, - self.group_event_manager_id, - group_system.id])] + cls.group_employee_id, + cls.group_event_manager_id, + cls.group_system_id])] }) - self.event_0 = self.env['event.event'].create({ + cls.event_0 = cls.env['event.event'].create({ 'name': 'TestEvent', + 'auto_confirm': True, 'date_begin': fields.Datetime.to_string(datetime.today() + timedelta(days=1)), 'date_end': fields.Datetime.to_string(datetime.today() + timedelta(days=15)), - 'registration_ids': [(0, 0, { - 'partner_id': self.user_eventuser.partner_id.id, - })] }) + + # set country in order to format belgium numbers + cls.event_0.company_id.write({'country_id': cls.env.ref('base.be').id}) + + @classmethod + def _create_registrations(cls, event, reg_count): + # create some registrations + registrations = cls.env['event.registration'].create([{ + 'event_id': event.id, + 'name': 'Test Registration %s' % x, + 'email': '_test_reg_%s@example.com' % x, + 'phone': '04560000%s%s' % (x, x), + } for x in range(0, reg_count)]) + return registrations diff --git a/addons/event/tests/test_event_flow.py b/addons/event/tests/test_event_flow.py index 08ac4654877..7a2364ac4c9 100644 --- a/addons/event/tests/test_event_flow.py +++ b/addons/event/tests/test_event_flow.py @@ -19,7 +19,7 @@ class TestEventFlow(TestEventCommon): def test_00_basic_event_auto_confirm(self): """ Basic event management with auto confirmation """ # EventUser creates a new event: ok - test_event = self.Event.with_user(self.user_eventmanager).create({ + test_event = self.env['event.event'].with_user(self.user_eventmanager).create({ 'name': 'TestEvent', 'auto_confirm': True, 'date_begin': datetime.datetime.now() + relativedelta(days=-1), @@ -30,13 +30,13 @@ class TestEventFlow(TestEventCommon): self.assertEqual(test_event.state, 'confirm', 'Event: auto_confirmation of event failed') # EventUser create registrations for this event - test_reg1 = self.Registration.with_user(self.user_eventuser).create({ + test_reg1 = self.env['event.registration'].with_user(self.user_eventuser).create({ 'name': 'TestReg1', 'event_id': test_event.id, }) self.assertEqual(test_reg1.state, 'open', 'Event: auto_confirmation of registration failed') self.assertEqual(test_event.seats_reserved, 1, 'Event: wrong number of reserved seats after confirmed registration') - test_reg2 = self.Registration.with_user(self.user_eventuser).create({ + test_reg2 = self.env['event.registration'].with_user(self.user_eventuser).create({ 'name': 'TestReg2', 'event_id': test_event.id, }) @@ -45,7 +45,7 @@ class TestEventFlow(TestEventCommon): # EventUser create registrations for this event: too much registrations with self.assertRaises(ValidationError): - self.Registration.with_user(self.user_eventuser).create({ + self.env['event.registration'].with_user(self.user_eventuser).create({ 'name': 'TestReg3', 'event_id': test_event.id, }) @@ -70,7 +70,7 @@ class TestEventFlow(TestEventCommon): """ Avanced event flow: no auto confirmation, manage minimum / maximum seats, ... """ # EventUser creates a new event: ok - test_event = self.Event.with_user(self.user_eventmanager).create({ + test_event = self.env['event.event'].with_user(self.user_eventmanager).create({ 'name': 'TestEvent', 'date_begin': datetime.datetime.now() + relativedelta(days=-1), 'date_end': datetime.datetime.now() + relativedelta(days=1), @@ -81,7 +81,7 @@ class TestEventFlow(TestEventCommon): 'Event: new event should be in draft state, no auto confirmation') # EventUser create registrations for this event -> no auto confirmation - test_reg1 = self.Registration.with_user(self.user_eventuser).create({ + test_reg1 = self.env['event.registration'].with_user(self.user_eventuser).create({ 'name': 'TestReg1', 'event_id': test_event.id, }) @@ -92,7 +92,7 @@ class TestEventFlow(TestEventCommon): def test_event_access_rights(self): # EventManager required to create or update events with self.assertRaises(AccessError): - self.Event.with_user(self.user_eventuser).create({ + self.env['event.event'].with_user(self.user_eventuser).create({ 'name': 'TestEvent', 'date_begin': datetime.datetime.now() + relativedelta(days=-1), 'date_end': datetime.datetime.now() + relativedelta(days=1), @@ -114,9 +114,11 @@ class TestEventFlow(TestEventCommon): event_config.execute() def test_event_data(self): + self.event_0.write({'registration_ids': [(0, 0, {'partner_id': self.user_eventuser.partner_id.id})]}) self.assertEqual(self.event_0.registration_ids.get_date_range_str(), u'tomorrow') def test_event_date_range(self): + self.event_0.write({'registration_ids': [(0, 0, {'partner_id': self.user_eventuser.partner_id.id})]}) self.patcher = patch('odoo.addons.event.models.event.fields.Datetime', wraps=Datetime) self.mock_datetime = self.patcher.start() diff --git a/addons/event/tests/test_mail_schedule.py b/addons/event/tests/test_mail_schedule.py index 1210c33f2ac..7dcc446dc9c 100644 --- a/addons/event/tests/test_mail_schedule.py +++ b/addons/event/tests/test_mail_schedule.py @@ -17,7 +17,7 @@ class TestMailSchedule(TestEventCommon): event_date_begin = now + relativedelta(days=1) event_date_end = now + relativedelta(days=3) - test_event = self.Event.with_user(self.user_eventmanager).create({ + test_event = self.env['event.event'].with_user(self.user_eventmanager).create({ 'name': 'TestEventMail', 'auto_confirm': True, 'date_begin': event_date_begin, @@ -37,19 +37,19 @@ class TestMailSchedule(TestEventCommon): }) # create some registrations - self.Registration.with_user(self.user_eventuser).create({ + self.env['event.registration'].with_user(self.user_eventuser).create({ 'event_id': test_event.id, 'name': 'Reg0', 'email': 'reg0@example.com', }) - self.Registration.with_user(self.user_eventuser).create({ + self.env['event.registration'].with_user(self.user_eventuser).create({ 'event_id': test_event.id, 'name': 'Reg1', 'email': 'reg1@example.com', }) # check subscription scheduler - schedulers = self.EventMail.search([('event_id', '=', test_event.id), ('interval_type', '=', 'after_sub')]) + schedulers = self.env['event.mail'].search([('event_id', '=', test_event.id), ('interval_type', '=', 'after_sub')]) self.assertEqual(len(schedulers), 1, 'event: wrong scheduler creation') self.assertEqual(schedulers[0].scheduled_date, test_event.create_date, 'event: incorrect scheduled date for checking controller') @@ -63,7 +63,7 @@ class TestMailSchedule(TestEventCommon): self.assertTrue(registration.mail_sent, 'event: wrongly confirmed mailing on registration') # check before event scheduler - schedulers = self.EventMail.search([('event_id', '=', test_event.id), ('interval_type', '=', 'before_event')]) + schedulers = self.env['event.mail'].search([('event_id', '=', test_event.id), ('interval_type', '=', 'before_event')]) self.assertEqual(len(schedulers), 1, 'event: wrong scheduler creation') self.assertEqual(schedulers[0].scheduled_date, event_date_begin + relativedelta(days=-1), 'event: incorrect scheduled date') @@ -75,5 +75,3 @@ class TestMailSchedule(TestEventCommon): mails = self.env['mail.mail'].search([('subject', 'ilike', 'TestEventMail'), ('date', '>=', now)], order='date DESC', limit=3) self.assertEqual(len(mails), 3, 'event: wrong number of reminders in outgoing mail queue') - - From 62ac23f0b93f05125f5067139898d81ede567130 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thibault=20Delavall=C3=A9e?= Date: Tue, 9 Jul 2019 14:02:17 +0000 Subject: [PATCH 2/2] [ADD] event_sms: allow to schedule SMS in event communication PURPOSE Allow the user to schedule SMS communication on events SPECIFICATION In event.event form view: * rename tab 'Email Schedule' to 'Communication' * add a selection field at the beginning of event_mail_ids with label 'Send' and values 'Email' and 'SMS Text Message' (required with default value 'Email'); * add support of SMS template and SMS sending in event communication; When sending SMS on event registrations, keep a log of what has been sent. Use mobile or phone field of event registration as both may contain valid values, like partner model. Add SMS template data * Name: Event: Registration (see data) * Name: Event: Reminder (see data) Improve registration model * add a mobile field to the attendees form view: https://nimb.ws/p0VvyS Technically this task uses the newly-introduced API methods allowing to send and schedule SMS. LINKS Task 1922187 Part of PR #34705 --- addons/event/models/event.py | 12 ++- addons/event/models/event_mail.py | 10 ++- addons/event/views/event_views.xml | 21 +++-- addons/event_sale/views/event_views.xml | 2 +- .../wizard/event_edit_registration.py | 3 + .../wizard/event_edit_registration.xml | 1 + addons/event_sms/__init__.py | 5 ++ addons/event_sms/__manifest__.py | 19 +++++ addons/event_sms/data/sms_data.xml | 18 +++++ addons/event_sms/models/__init__.py | 5 ++ addons/event_sms/models/event.py | 13 +++ addons/event_sms/models/event_mail.py | 49 ++++++++++++ addons/event_sms/tests/__init__.py | 3 + addons/event_sms/tests/test_sms_schedule.py | 79 +++++++++++++++++++ addons/event_sms/views/event_mail_views.xml | 41 ++++++++++ addons/event_sms/views/event_views.xml | 42 ++++++++++ 16 files changed, 307 insertions(+), 16 deletions(-) create mode 100644 addons/event_sms/__init__.py create mode 100644 addons/event_sms/__manifest__.py create mode 100644 addons/event_sms/data/sms_data.xml create mode 100644 addons/event_sms/models/__init__.py create mode 100644 addons/event_sms/models/event.py create mode 100644 addons/event_sms/models/event_mail.py create mode 100644 addons/event_sms/tests/__init__.py create mode 100644 addons/event_sms/tests/test_sms_schedule.py create mode 100644 addons/event_sms/views/event_mail_views.xml create mode 100644 addons/event_sms/views/event_views.xml diff --git a/addons/event/models/event.py b/addons/event/models/event.py index 1e35e4d29eb..2fa2a56fbfb 100644 --- a/addons/event/models/event.py +++ b/addons/event/models/event.py @@ -364,15 +364,22 @@ class EventRegistration(models.Model): _inherit = ['mail.thread', 'mail.activity.mixin'] _order = 'name, create_date desc' + # event origin = fields.Char( string='Source Document', readonly=True, help="Reference of the document that created the registration, for example a sales order") event_id = fields.Many2one( 'event.event', string='Event', required=True, readonly=True, states={'draft': [('readonly', False)]}) + # attendee partner_id = fields.Many2one( 'res.partner', string='Contact', states={'done': [('readonly', True)]}) + name = fields.Char(string='Attendee Name', index=True) + email = fields.Char(string='Email') + phone = fields.Char(string='Phone') + mobile = fields.Char(string='Mobile') + # organization date_open = fields.Datetime(string='Registration Date', readonly=True, default=lambda self: fields.Datetime.now()) # weird crash is directly now date_closed = fields.Datetime(string='Attended Date', readonly=True) event_begin_date = fields.Datetime(string="Event Start Date", related='event_id.date_begin', readonly=True) @@ -384,9 +391,6 @@ class EventRegistration(models.Model): ('draft', 'Unconfirmed'), ('cancel', 'Cancelled'), ('open', 'Confirmed'), ('done', 'Attended')], string='Status', default='draft', readonly=True, copy=False, tracking=True) - email = fields.Char(string='Email') - phone = fields.Char(string='Phone') - name = fields.Char(string='Attendee Name', index=True) @api.constrains('event_id', 'state') def _check_seats_limit(self): @@ -421,6 +425,7 @@ class EventRegistration(models.Model): data = { 'name': registration.get('name', partner_id.name), 'phone': registration.get('phone', partner_id.phone), + 'mobile': registration.get('mobile', partner_id.mobile), 'email': registration.get('email', partner_id.email), 'partner_id': partner_id.id, 'event_id': event_id and event_id.id or False, @@ -462,6 +467,7 @@ class EventRegistration(models.Model): self.name = contact.name or self.name self.email = contact.email or self.email self.phone = contact.phone or self.phone + self.mobile = contact.mobile or self.mobile def _message_get_suggested_recipients(self): recipients = super(EventRegistration, self)._message_get_suggested_recipients() diff --git a/addons/event/models/event_mail.py b/addons/event/models/event_mail.py index eb423012af5..da3f876c22e 100644 --- a/addons/event/models/event_mail.py +++ b/addons/event/models/event_mail.py @@ -29,6 +29,7 @@ class EventTypeMail(models.Model): event_type_id = fields.Many2one( 'event.type', string='Event Type', ondelete='cascade', required=True) + notification_type = fields.Selection([('mail', 'Mail')], string='Send', default='mail', required=True) interval_nbr = fields.Integer('Interval', default=1) interval_unit = fields.Selection([ ('now', 'Immediately'), @@ -42,7 +43,7 @@ class EventTypeMail(models.Model): string='Trigger', default="before_event", required=True) template_id = fields.Many2one( 'mail.template', string='Email Template', - domain=[('model', '=', 'event.registration')], required=True, ondelete='restrict', + domain=[('model', '=', 'event.registration')], ondelete='restrict', help='This field contains the template of the mail that will be automatically sent') @@ -56,6 +57,7 @@ class EventMailScheduler(models.Model): event_id = fields.Many2one('event.event', string='Event', required=True, ondelete='cascade') sequence = fields.Integer('Display order') + notification_type = fields.Selection([('mail', 'Mail')], string='Send', default='mail', required=True) interval_nbr = fields.Integer('Interval', default=1) interval_unit = fields.Selection([ ('now', 'Immediately'), @@ -69,7 +71,7 @@ class EventMailScheduler(models.Model): string='Trigger ', default="before_event", required=True) template_id = fields.Many2one( 'mail.template', string='Email Template', - domain=[('model', '=', 'event.registration')], required=True, ondelete='restrict', + domain=[('model', '=', 'event.registration')], ondelete='restrict', help='This field contains the template of the mail that will be automatically sent') scheduled_date = fields.Datetime('Scheduled Sent Mail', compute='_compute_scheduled_date', store=True) mail_registration_ids = fields.One2many('event.mail.registration', 'scheduler_id') @@ -113,7 +115,7 @@ class EventMailScheduler(models.Model): mail.mail_registration_ids.filtered(lambda reg: reg.scheduled_date and reg.scheduled_date <= now).execute() else: # Do not send emails if the mailing was scheduled before the event but the event is over - if not mail.mail_sent and (mail.interval_type != 'before_event' or mail.event_id.date_end > now): + if not mail.mail_sent and (mail.interval_type != 'before_event' or mail.event_id.date_end > now) and mail.notification_type == 'mail': mail.event_id.mail_attendees(mail.template_id.id) mail.write({'mail_sent': True}) return True @@ -180,7 +182,7 @@ class EventMailRegistration(models.Model): def execute(self): for mail in self: - if mail.registration_id.state in ['open', 'done'] and not mail.mail_sent: + if mail.registration_id.state in ['open', 'done'] and not mail.mail_sent and mail.scheduler_id.notification_type == 'mail': mail.scheduler_id.template_id.send_mail(mail.registration_id.id) mail.write({'mail_sent': True}) diff --git a/addons/event/views/event_views.xml b/addons/event/views/event_views.xml index a27715955ba..a0d0c684458 100644 --- a/addons/event/views/event_views.xml +++ b/addons/event/views/event_views.xml @@ -60,8 +60,9 @@
- - + + + @@ -253,11 +254,12 @@ - + - + - + + @@ -482,8 +484,9 @@ - + + @@ -596,7 +599,8 @@ - + + @@ -631,7 +635,8 @@ - + + diff --git a/addons/event_sale/views/event_views.xml b/addons/event_sale/views/event_views.xml index 097f4ede3fe..f0e4462dea5 100644 --- a/addons/event_sale/views/event_views.xml +++ b/addons/event_sale/views/event_views.xml @@ -88,7 +88,7 @@ event.event - + diff --git a/addons/event_sale/wizard/event_edit_registration.py b/addons/event_sale/wizard/event_edit_registration.py index 5b5b98ab300..85fb06ad406 100644 --- a/addons/event_sale/wizard/event_edit_registration.py +++ b/addons/event_sale/wizard/event_edit_registration.py @@ -33,6 +33,7 @@ class RegistrationEditor(models.TransientModel): 'name': reg.name, 'email': reg.email, 'phone': reg.phone, + 'mobile': reg.mobile, 'sale_order_line_id': so_line.id, }) for count in range(int(so_line.product_uom_qty) - len(existing_registrations)): @@ -71,6 +72,7 @@ class RegistrationEditorLine(models.TransientModel): event_ticket_id = fields.Many2one('event.event.ticket', string='Event Ticket') email = fields.Char(string='Email') phone = fields.Char(string='Phone') + mobile = fields.Char(string='Mobile') name = fields.Char(string='Name', index=True) def get_registration_data(self): @@ -81,6 +83,7 @@ class RegistrationEditorLine(models.TransientModel): 'partner_id': self.editor_id.sale_order_id.partner_id.id, 'name': self.name or self.editor_id.sale_order_id.partner_id.name, 'phone': self.phone or self.editor_id.sale_order_id.partner_id.phone, + 'mobile': self.mobile or self.editor_id.sale_order_id.partner_id.mobile, 'email': self.email or self.editor_id.sale_order_id.partner_id.email, 'origin': self.editor_id.sale_order_id.name, 'sale_order_id': self.editor_id.sale_order_id.id, diff --git a/addons/event_sale/wizard/event_edit_registration.xml b/addons/event_sale/wizard/event_edit_registration.xml index 23ff3101db6..4630417a1c0 100644 --- a/addons/event_sale/wizard/event_edit_registration.xml +++ b/addons/event_sale/wizard/event_edit_registration.xml @@ -15,6 +15,7 @@ + diff --git a/addons/event_sms/__init__.py b/addons/event_sms/__init__.py new file mode 100644 index 00000000000..90b17558ffc --- /dev/null +++ b/addons/event_sms/__init__.py @@ -0,0 +1,5 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +from . import models +from . import tests diff --git a/addons/event_sms/__manifest__.py b/addons/event_sms/__manifest__.py new file mode 100644 index 00000000000..697f0e71876 --- /dev/null +++ b/addons/event_sms/__manifest__.py @@ -0,0 +1,19 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +{ + 'name': 'SMS on Events', + 'version': '1.0', + 'category': 'Marketing', + 'description': """Schedule SMS in event management""", + 'depends': ['event', 'sms'], + 'data': [ + 'data/sms_data.xml', + 'views/event_views.xml', + 'views/event_mail_views.xml', + ], + 'demo': [ + ], + 'installable': True, + 'auto_install': True +} diff --git a/addons/event_sms/data/sms_data.xml b/addons/event_sms/data/sms_data.xml new file mode 100644 index 00000000000..b218f3d8900 --- /dev/null +++ b/addons/event_sms/data/sms_data.xml @@ -0,0 +1,18 @@ + + + + + Event: Registration + + ${object.event_id.organizer_id.name or object.event_id.company_id.name or user.env.company_id.name}: We are happy to confirm your registration for the ${object.event_id.name} event. + ${object.partner_id.lang} + + + + Event: Reminder + + ${object.event_id.organizer_id.name or object.event_id.company_id.name or user.env.company_id.name}: We are excited to remind you that the ${object.event_id.name} event is starting ${object.get_date_range_str()}. We confirm your registration and hope to meet you there. + ${object.partner_id.lang} + + + diff --git a/addons/event_sms/models/__init__.py b/addons/event_sms/models/__init__.py new file mode 100644 index 00000000000..b8ced32a4d7 --- /dev/null +++ b/addons/event_sms/models/__init__.py @@ -0,0 +1,5 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +from . import event +from . import event_mail diff --git a/addons/event_sms/models/event.py b/addons/event_sms/models/event.py new file mode 100644 index 00000000000..d8be5a5390a --- /dev/null +++ b/addons/event_sms/models/event.py @@ -0,0 +1,13 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +from odoo import models + + +class Registration(models.Model): + _inherit = 'event.registration' + + def _sms_get_number_fields(self): + """ This method returns the fields to use to find the number to use to + send an SMS on a record. """ + return ['mobile', 'phone'] diff --git a/addons/event_sms/models/event_mail.py b/addons/event_sms/models/event_mail.py new file mode 100644 index 00000000000..0b692bca8fd --- /dev/null +++ b/addons/event_sms/models/event_mail.py @@ -0,0 +1,49 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +from odoo import fields, models + + +class EventTypeMail(models.Model): + _inherit = 'event.type.mail' + + notification_type = fields.Selection(selection_add=[('sms', 'SMS')]) + sms_template_id = fields.Many2one( + 'sms.template', string='SMS Template', + domain=[('model', '=', 'event.registration')], ondelete='restrict', + help='This field contains the template of the SMS that will be automatically sent') + + +class EventMailScheduler(models.Model): + _inherit = 'event.mail' + + notification_type = fields.Selection(selection_add=[('sms', 'SMS')]) + sms_template_id = fields.Many2one( + 'sms.template', string='SMS Template', + domain=[('model', '=', 'event.registration')], ondelete='restrict', + help='This field contains the template of the SMS that will be automatically sent') + + def execute(self): + for mail in self: + now = fields.Datetime.now() + if mail.interval_type != 'after_sub': + # Do not send SMS if the communication was scheduled before the event but the event is over + if not mail.mail_sent and (mail.interval_type != 'before_event' or mail.event_id.date_end > now) and mail.notification_type == 'sms' and mail.sms_template_id: + self.env['event.registration']._message_sms_schedule_mass( + template=mail.sms_template_id, + active_domain=[('event_id', '=', mail.event_id.id), ('state', '!=', 'cancel')], + mass_keep_log=True + ) + mail.write({'mail_sent': True}) + return super(EventMailScheduler, self).execute() + + +class EventMailRegistration(models.Model): + _inherit = 'event.mail.registration' + + def execute(self): + for record in self: + if record.registration_id.state in ['open', 'done'] and not record.mail_sent and record.scheduler_id.notification_type == 'sms': + record.registration_id._message_sms_schedule_mass(template=record.scheduler_id.sms_template_id, mass_keep_log=True) + record.write({'mail_sent': True}) + return super(EventMailRegistration, self).execute() diff --git a/addons/event_sms/tests/__init__.py b/addons/event_sms/tests/__init__.py new file mode 100644 index 00000000000..1c217f92742 --- /dev/null +++ b/addons/event_sms/tests/__init__.py @@ -0,0 +1,3 @@ +# -*- coding: utf-8 -*- + +from . import test_sms_schedule diff --git a/addons/event_sms/tests/test_sms_schedule.py b/addons/event_sms/tests/test_sms_schedule.py new file mode 100644 index 00000000000..14ba81b1ffa --- /dev/null +++ b/addons/event_sms/tests/test_sms_schedule.py @@ -0,0 +1,79 @@ +# -*- coding: utf-8 -*- + +from dateutil.relativedelta import relativedelta + +from odoo.addons.event.tests.common import TestEventCommon +from odoo.addons.phone_validation.tools import phone_validation +from odoo.addons.sms.tests.common import MockSMS +from odoo.tools import mute_logger + + +class TestSMSSchedule(TestEventCommon, MockSMS): + + @classmethod + def setUpClass(cls): + super(TestSMSSchedule, cls).setUpClass() + + cls.sms_template_sub = cls.env['sms.template'].create({ + 'name': 'Test subscription', + 'model_id': cls.env.ref('event.model_event_registration').id, + 'body': '${object.event_id.organizer_id.name} registration confirmation.', + 'lang': '${object.partner_id.lang}' + }) + cls.sms_template_rem = cls.env['sms.template'].create({ + 'name': 'Test reminder', + 'model_id': cls.env.ref('event.model_event_registration').id, + 'body': '${object.event_id.organizer_id.name} reminder', + 'lang': '${object.partner_id.lang}' + }) + + cls.event_0.write({ + 'event_mail_ids': [ + (0, 0, { # right at subscription + 'interval_unit': 'now', + 'interval_type': 'after_sub', + 'notification_type': 'sms', + 'sms_template_id': cls.sms_template_sub.id}), + (0, 0, { # 3 days before event + 'interval_nbr': 3, + 'interval_unit': 'days', + 'interval_type': 'before_event', + 'notification_type': 'sms', + 'sms_template_id': cls.sms_template_rem.id}), + ] + }) + + def test_sms_schedule(self): + self._create_registrations(self.event_0, 3) + + # check subscription scheduler + schedulers = self.env['event.mail'].search([('event_id', '=', self.event_0.id), ('interval_type', '=', 'after_sub')]) + self.assertEqual(len(schedulers), 1) + self.assertEqual(schedulers.scheduled_date, self.event_0.create_date, 'event: incorrect scheduled date for checking controller') + + # verify that subscription scheduler was auto-executed after each registration + self.assertEqual(len(schedulers.mail_registration_ids), 3) + self.assertTrue(all(m.mail_sent is True for m in schedulers.mail_registration_ids)) + self.assertEqual(schedulers.mapped('mail_registration_ids.registration_id'), self.event_0.registration_ids) + sanitized_numbers = [] + for registration in self.event_0.registration_ids: + reg_sanitized_number = phone_validation.phone_format(registration.phone, 'BE', '32', force_format='E164') + sanitized_numbers.append(reg_sanitized_number) + self.assertSMSNotification([{'number': reg_sanitized_number, 'state': 'ready'}], '%s registration confirmation.' % self.event_0.organizer_id.name) + + # clear notification queue to avoid conflicts when checking next notifications + self.env['mail.notification'].search([('sms_number', 'in', sanitized_numbers)]).unlink() + self.env['sms.sms'].search([('number', 'in', sanitized_numbers)]).unlink() + + # check before event scheduler + schedulers = self.env['event.mail'].search([('event_id', '=', self.event_0.id), ('interval_type', '=', 'before_event')]) + self.assertEqual(len(schedulers), 1, 'event: wrong scheduler creation') + self.assertEqual(schedulers[0].scheduled_date, self.event_0.date_begin + relativedelta(days=-3)) + + # execute event reminder scheduler explicitly + schedulers.execute() + + # verify that subscription scheduler was auto-executed after each registration + for registration in self.event_0.registration_ids: + reg_sanitized_number = phone_validation.phone_format(registration.phone, 'BE', '32', force_format='E164') + self.assertSMSNotification([{'number': reg_sanitized_number, 'state': 'ready'}], '%s reminder' % self.event_0.organizer_id.name) diff --git a/addons/event_sms/views/event_mail_views.xml b/addons/event_sms/views/event_mail_views.xml new file mode 100644 index 00000000000..430eef7dfef --- /dev/null +++ b/addons/event_sms/views/event_mail_views.xml @@ -0,0 +1,41 @@ + + + + event.mail.view.form.inherit.sms + event.mail + + + + 0 + + + {'readonly': [('notification_type', '!=', 'mail')], 'required': [('notification_type', '=', 'mail')]} + + + + + + + + + event.mail.view.tree.inherit.sms + event.mail + + + + 0 + + + {'readonly': [('notification_type', '!=', 'mail')], 'required': [('notification_type', '=', 'mail')]} + + + + + + + + diff --git a/addons/event_sms/views/event_views.xml b/addons/event_sms/views/event_views.xml new file mode 100644 index 00000000000..fb49147c973 --- /dev/null +++ b/addons/event_sms/views/event_views.xml @@ -0,0 +1,42 @@ + + + + + event.type.view.form.inherit.sms + event.type + + + + 0 + + + {'readonly': [('notification_type', '!=', 'mail')], 'required': [('notification_type', '=', 'mail')]} + + + + + + + + + event.event.view.form + event.event + + + + 0 + + + {'readonly': [('notification_type', '!=', 'mail')], 'required': [('notification_type', '=', 'mail')]} + + + + + + + +