[MERGE] event(_sms): integrate SMS into event communication scheduler
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; 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. MIGRATION Some notes on migration * event.registration: new mobile field in addition to phone, may be void; * event.type.mail and event.mail: new notification_type field. Existing rows: set value to 'mail'. LINKS Task 1922187 closes odoo/odoo#34705 Signed-off-by: Thibault Delavallee (tde) <tde@openerp.com>
This commit is contained in:
@@ -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()
|
||||
|
||||
@@ -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})
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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()
|
||||
|
||||
|
||||
@@ -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')
|
||||
|
||||
|
||||
|
||||
@@ -60,8 +60,9 @@
|
||||
<div class="row mt16" attrs="{'invisible': [('use_mail_schedule', '=', False)]}">
|
||||
<div class="col-lg-9">
|
||||
<field name="event_type_mail_ids">
|
||||
<tree string="Email Schedule" editable="bottom">
|
||||
<field name="template_id"/>
|
||||
<tree string="Communication" editable="bottom">
|
||||
<field name="notification_type" invisible="1"/>
|
||||
<field name="template_id" attrs="{'required': [('notification_type', '=', 'mail')]}"/>
|
||||
<field name="interval_nbr" attrs="{'readonly':[('interval_unit', '=', 'now')]}"/>
|
||||
<field name="interval_unit"/>
|
||||
<field name="interval_type"/>
|
||||
@@ -253,11 +254,12 @@
|
||||
</group>
|
||||
</group>
|
||||
<notebook>
|
||||
<page string="Email Schedule" name="event_mail_schedule">
|
||||
<page string="Communication" name="event_communication">
|
||||
<field name="event_mail_ids">
|
||||
<tree string="Email Schedule" editable="bottom">
|
||||
<tree string="Communication" editable="bottom">
|
||||
<field name="sequence" widget="handle"/>
|
||||
<field name="template_id" />
|
||||
<field name="notification_type" invisible="1"/>
|
||||
<field name="template_id" attrs="{'required': [('notification_type', '=', 'mail')]}"/>
|
||||
<field name="interval_nbr" attrs="{'readonly':[('interval_unit','=','now')]}"/>
|
||||
<field name="interval_unit"/>
|
||||
<field name="interval_type"/>
|
||||
@@ -482,8 +484,9 @@
|
||||
<group string="Attendee" name="attendee">
|
||||
<field name="partner_id" attrs="{'readonly':[('state', '!=', 'draft')]}"/>
|
||||
<field name="name"/>
|
||||
<field name="phone"/>
|
||||
<field name="email"/>
|
||||
<field name="phone"/>
|
||||
<field name="mobile"/>
|
||||
</group>
|
||||
<group string="Event Information" name="event">
|
||||
<field name="event_id" attrs="{'readonly': [('state', '!=', 'draft')]}" options="{'no_open': True, 'no_create': True}"/>
|
||||
@@ -596,7 +599,8 @@
|
||||
<group>
|
||||
<group>
|
||||
<field name="event_id"/>
|
||||
<field name="template_id"/>
|
||||
<field name="notification_type" invisible="1"/>
|
||||
<field name="template_id" attrs="{'required': [('notification_type', '=', 'mail')]}"/>
|
||||
<field name="mail_sent"/>
|
||||
</group>
|
||||
<group>
|
||||
@@ -631,7 +635,8 @@
|
||||
<field name="arch" type="xml">
|
||||
<tree string="Event Mail Schedulers">
|
||||
<field name="event_id"/>
|
||||
<field name="template_id"/>
|
||||
<field name="notification_type" invisible="1"/>
|
||||
<field name="template_id" attrs="{'required': [('notification_type', '=', 'mail')]}"/>
|
||||
<field name="scheduled_date"/>
|
||||
<field name="mail_sent"/>
|
||||
<field name="done"/>
|
||||
|
||||
@@ -88,7 +88,7 @@
|
||||
<field name="model">event.event</field>
|
||||
<field name="inherit_id" ref="event.view_event_form"/>
|
||||
<field name="arch" type="xml">
|
||||
<page name="event_mail_schedule" position="before">
|
||||
<page name="event_communication" position="before">
|
||||
<page string="Tickets">
|
||||
<field name="event_ticket_ids" context="{'default_name': name}" mode="tree,kanban">
|
||||
<tree string="Tickets" editable="bottom">
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
<field name="event_ticket_id" domain="[('event_id', '=', event_id)]" readonly='1' force_save="1"/>
|
||||
<field name="name"/>
|
||||
<field name="email"/>
|
||||
<field name="mobile"/>
|
||||
<field name="phone"/>
|
||||
</tree>
|
||||
</field>
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
<?xml version="1.0"?>
|
||||
<odoo><data noupdate="1">
|
||||
|
||||
<record id="sms_template_data_event_registration" model="sms.template">
|
||||
<field name="name">Event: Registration</field>
|
||||
<field name="model_id" ref="event.model_event_registration"/>
|
||||
<field name="body">${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.</field>
|
||||
<field name="lang">${object.partner_id.lang}</field>
|
||||
</record>
|
||||
|
||||
<record id="sms_template_data_event_reminder" model="sms.template">
|
||||
<field name="name">Event: Reminder</field>
|
||||
<field name="model_id" ref="event.model_event_registration"/>
|
||||
<field name="body">${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.</field>
|
||||
<field name="lang">${object.partner_id.lang}</field>
|
||||
</record>
|
||||
|
||||
</data></odoo>
|
||||
@@ -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
|
||||
@@ -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']
|
||||
@@ -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()
|
||||
@@ -0,0 +1,3 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from . import test_sms_schedule
|
||||
@@ -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)
|
||||
@@ -0,0 +1,41 @@
|
||||
<?xml version="1.0"?>
|
||||
<odoo><data>
|
||||
<record id="event_mail_view_form" model="ir.ui.view">
|
||||
<field name="name">event.mail.view.form.inherit.sms</field>
|
||||
<field name="model">event.mail</field>
|
||||
<field name="inherit_id" ref="event.view_event_mail_form"/>
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//field[@name='notification_type']" position="attributes">
|
||||
<attribute name="invisible">0</attribute>
|
||||
</xpath>
|
||||
<xpath expr="//field[@name='template_id']" position="attributes">
|
||||
<attribute name="attrs">{'readonly': [('notification_type', '!=', 'mail')], 'required': [('notification_type', '=', 'mail')]}</attribute>
|
||||
</xpath>
|
||||
<xpath expr="//field[@name='template_id']" position="after">
|
||||
<field name="sms_template_id"
|
||||
attrs="{'readonly': [('notification_type', '!=', 'sms')], 'required': [('notification_type', '=', 'sms')]}"
|
||||
context="{'default_model': 'event.registration'}"/>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="event_mail_view_tree" model="ir.ui.view">
|
||||
<field name="name">event.mail.view.tree.inherit.sms</field>
|
||||
<field name="model">event.mail</field>
|
||||
<field name="inherit_id" ref="event.view_event_mail_tree"/>
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//field[@name='notification_type']" position="attributes">
|
||||
<attribute name="invisible">0</attribute>
|
||||
</xpath>
|
||||
<xpath expr="//field[@name='template_id']" position="attributes">
|
||||
<attribute name="attrs">{'readonly': [('notification_type', '!=', 'mail')], 'required': [('notification_type', '=', 'mail')]}</attribute>
|
||||
</xpath>
|
||||
<xpath expr="//field[@name='template_id']" position="after">
|
||||
<field name="sms_template_id"
|
||||
attrs="{'readonly': [('notification_type', '!=', 'sms')], 'required': [('notification_type', '=', 'sms')]}"
|
||||
context="{'default_model': 'event.registration'}"/>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
</data></odoo>
|
||||
@@ -0,0 +1,42 @@
|
||||
<?xml version="1.0"?>
|
||||
<odoo><data>
|
||||
|
||||
<record id="event_type_view_form" model="ir.ui.view">
|
||||
<field name="name">event.type.view.form.inherit.sms</field>
|
||||
<field name="model">event.type</field>
|
||||
<field name="inherit_id" ref="event.view_event_type_form"/>
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//field[@name='notification_type']" position="attributes">
|
||||
<attribute name="invisible">0</attribute>
|
||||
</xpath>
|
||||
<xpath expr="//field[@name='template_id']" position="attributes">
|
||||
<attribute name="attrs">{'readonly': [('notification_type', '!=', 'mail')], 'required': [('notification_type', '=', 'mail')]}</attribute>
|
||||
</xpath>
|
||||
<xpath expr="//field[@name='template_id']" position="after">
|
||||
<field name="sms_template_id"
|
||||
attrs="{'readonly': [('notification_type', '!=', 'sms')], 'required': [('notification_type', '=', 'sms')]}"
|
||||
context="{'default_model': 'event.registration'}"/>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="event_event_view_form_inherit_sms" model="ir.ui.view">
|
||||
<field name="name">event.event.view.form</field>
|
||||
<field name="model">event.event</field>
|
||||
<field name="inherit_id" ref="event.view_event_form"/>
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//field[@name='notification_type']" position="attributes">
|
||||
<attribute name="invisible">0</attribute>
|
||||
</xpath>
|
||||
<xpath expr="//field[@name='template_id']" position="attributes">
|
||||
<attribute name="attrs">{'readonly': [('notification_type', '!=', 'mail')], 'required': [('notification_type', '=', 'mail')]}</attribute>
|
||||
</xpath>
|
||||
<xpath expr="//field[@name='template_id']" position="after">
|
||||
<field name="sms_template_id"
|
||||
attrs="{'readonly': [('notification_type', '!=', 'sms')], 'required': [('notification_type', '=', 'sms')]}"
|
||||
context="{'default_model': 'event.registration'}"/>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
</data></odoo>
|
||||
Reference in New Issue
Block a user