[IMP] event: format mobile / phone number of attendees when possible
ISSUE SMS reminders are not always correctly sent, as people fill form using local numbers. When scheduler run the number is not always recognized to be formatted using E164 format. We should try to normalize numbers when possible. SPECIFICATIONS Try a sanitize: format number based on event country (or partner if set). If formatting it works, use the given number, otherwise use the original number. Do it at registration creation, as classic flow is the frontend where customers give numbers used to create registrations. When the number is coming from the partner through the compute, do not do anything and keep current behavior which copies partner number if not given directly on the registration. Add an onchange to try to format the number in the form view, like what is done on leads. Do not automatically do it at write as it is not really required currently from business point of view, it is more an helper for frontend registration. Task-3342820 closes odoo/odoo#128118 X-original-commit: 94fa8d962535a754c9fb26997a38cc8b51b8974e Signed-off-by: Warnon Aurélien (awa) <awa@odoo.com> Signed-off-by: Thibault Delavallee (tde) <tde@openerp.com>
This commit is contained in:
@@ -7,6 +7,15 @@ from odoo import _, api, fields, models, SUPERUSER_ID
|
||||
from odoo.tools import format_date
|
||||
from odoo.exceptions import AccessError, ValidationError
|
||||
|
||||
# phone_validation is not officially in the depends of event, but we would like
|
||||
# to have the formatting available in event, not in event_sms -> do a conditional
|
||||
# import just to be sure
|
||||
try:
|
||||
from odoo.addons.phone_validation.tools.phone_validation import phone_format
|
||||
except ImportError:
|
||||
def phone_format(number, country_code, country_phone_code, force_format='INTERNATIONAL', raise_exception=True):
|
||||
return number
|
||||
|
||||
|
||||
class EventRegistration(models.Model):
|
||||
_name = 'event.registration'
|
||||
@@ -121,12 +130,43 @@ class EventRegistration(models.Model):
|
||||
return dict((fname, contact[fname]) for fname in fnames if contact[fname])
|
||||
return {}
|
||||
|
||||
@api.onchange('phone', 'event_id', 'partner_id')
|
||||
def _onchange_phone_validation(self):
|
||||
if self.phone:
|
||||
country = self.partner_id.country_id or self.event_id.country_id or self.env.company.country_id
|
||||
self.phone = self._phone_format(self.phone, country)
|
||||
|
||||
@api.onchange('mobile', 'event_id', 'partner_id')
|
||||
def _onchange_mobile_validation(self):
|
||||
if self.mobile:
|
||||
country = self.partner_id.country_id or self.event_id.country_id or self.env.company.country_id
|
||||
self.mobile = self._phone_format(self.mobile, country)
|
||||
|
||||
# ------------------------------------------------------------
|
||||
# CRUD
|
||||
# ------------------------------------------------------------
|
||||
|
||||
@api.model_create_multi
|
||||
def create(self, vals_list):
|
||||
# format numbers: prefetch side records, then try to format according to country
|
||||
all_partner_ids = set(values['partner_id'] for values in vals_list if values.get('partner_id'))
|
||||
all_event_ids = set(values['event_id'] for values in vals_list if values.get('event_id'))
|
||||
for values in vals_list:
|
||||
if not values.get('phone') and not values.get('mobile'):
|
||||
continue
|
||||
|
||||
related_country = self.env['res.country']
|
||||
if values.get('partner_id'):
|
||||
related_country = self.env['res.partner'].with_prefetch(all_partner_ids).browse(values['partner_id']).country_id
|
||||
if not related_country and values.get('event_id'):
|
||||
related_country = self.env['event.event'].with_prefetch(all_event_ids).browse(values['event_id']).country_id
|
||||
if not related_country:
|
||||
related_country = self.env.company.country_id
|
||||
|
||||
for fname in {'mobile', 'phone'}:
|
||||
if values.get(fname):
|
||||
values[fname] = self._phone_format(values[fname], related_country)
|
||||
|
||||
registrations = super(EventRegistration, self).create(vals_list)
|
||||
|
||||
# auto_confirm if possible; if not automatically confirmed, call mail schedulers in case
|
||||
@@ -181,6 +221,20 @@ class EventRegistration(models.Model):
|
||||
""" Checks that all registrations are for `auto-confirm` events. """
|
||||
return all(event.auto_confirm for event in self.event_id)
|
||||
|
||||
def _phone_format(self, number, country):
|
||||
""" Call phone_validation formatting tool function. Returns original
|
||||
number in case formatting cannot be done (no country, wrong info, ...) """
|
||||
if not number or not country:
|
||||
return number
|
||||
new_number = phone_format(
|
||||
number,
|
||||
country.code,
|
||||
country.phone_code,
|
||||
force_format='E164',
|
||||
raise_exception=False,
|
||||
)
|
||||
return new_number if new_number else number
|
||||
|
||||
# ------------------------------------------------------------
|
||||
# ACTIONS / BUSINESS
|
||||
# ------------------------------------------------------------
|
||||
|
||||
@@ -8,7 +8,7 @@ from odoo import Command
|
||||
from odoo.addons.event.tests.common import EventCase
|
||||
from odoo import exceptions
|
||||
from odoo.fields import Datetime as FieldsDatetime
|
||||
from odoo.tests.common import users, Form
|
||||
from odoo.tests.common import users, Form, tagged
|
||||
from odoo.tools import mute_logger
|
||||
|
||||
|
||||
@@ -55,6 +55,7 @@ class TestEventInternalsCommon(EventCase):
|
||||
})
|
||||
|
||||
|
||||
@tagged('event_event')
|
||||
class TestEventData(TestEventInternalsCommon):
|
||||
|
||||
@users('user_eventmanager')
|
||||
@@ -573,6 +574,7 @@ class TestEventData(TestEventInternalsCommon):
|
||||
self.env['event.registration'].create(new_draft_to_autoconfirm)
|
||||
|
||||
|
||||
@tagged('event_registration')
|
||||
class TestEventRegistrationData(TestEventInternalsCommon):
|
||||
|
||||
@users('user_eventmanager')
|
||||
@@ -580,6 +582,7 @@ class TestEventRegistrationData(TestEventInternalsCommon):
|
||||
""" Test registration computed fields about partner """
|
||||
test_email = '"Nibbler In Space" <nibbler@futurama.example.com>'
|
||||
test_phone = '0456001122'
|
||||
test_phone_fmt = '+32456001122'
|
||||
|
||||
event = self.env['event.event'].browse(self.event_0.ids)
|
||||
customer = self.env['res.partner'].browse(self.event_customer.id)
|
||||
@@ -626,12 +629,12 @@ class TestEventRegistrationData(TestEventInternalsCommon):
|
||||
new_reg = event.registration_ids.sorted()[0]
|
||||
self.assertEqual(new_reg.name, 'Nibbler In Space')
|
||||
self.assertEqual(new_reg.email, False)
|
||||
self.assertEqual(new_reg.phone, test_phone)
|
||||
self.assertEqual(new_reg.phone, test_phone_fmt)
|
||||
new_reg.write({'partner_id': customer.id})
|
||||
self.assertEqual(new_reg.partner_id, customer)
|
||||
self.assertEqual(new_reg.name, 'Nibbler In Space')
|
||||
self.assertEqual(new_reg.email, customer.email)
|
||||
self.assertEqual(new_reg.phone, test_phone)
|
||||
self.assertEqual(new_reg.phone, test_phone_fmt)
|
||||
|
||||
@users('user_eventmanager')
|
||||
def test_registration_partner_sync_company(self):
|
||||
@@ -667,6 +670,123 @@ class TestEventRegistrationData(TestEventInternalsCommon):
|
||||
self.assertEqual(new_reg.phone, contact.phone)
|
||||
|
||||
|
||||
@tagged('event_registration', 'phone_number')
|
||||
class TestEventRegistrationPhone(EventCase):
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super().setUpClass()
|
||||
|
||||
cls.test_event_address = cls.env['res.partner'].create({
|
||||
'city': 'Gandhinagar',
|
||||
'country_id': cls.env.ref("base.in").id,
|
||||
'name': 'Odoo In',
|
||||
'zip': '382007',
|
||||
})
|
||||
cls.test_event = cls.env['event.event'].create({
|
||||
'address_id': cls.test_event_address.id,
|
||||
'company_id': cls.company_admin.id,
|
||||
'date_begin': datetime(2023, 6, 5, 8, 0, 0),
|
||||
'date_end': datetime(2023, 6, 8, 18, 0, 0),
|
||||
'name': 'Test Phone Format',
|
||||
})
|
||||
|
||||
@users('user_eventregistrationdesk')
|
||||
def test_assert_initial_values(self):
|
||||
customer = self.event_customer.with_env(self.env)
|
||||
customer2 = self.event_customer2.with_env(self.env)
|
||||
event = self.test_event.with_env(self.env)
|
||||
|
||||
self.assertFalse(customer.mobile)
|
||||
self.assertEqual(customer.phone, '0485112233')
|
||||
self.assertEqual(customer2.mobile, '0456654321')
|
||||
self.assertEqual(customer2.phone, '0456987654')
|
||||
|
||||
self.assertEqual(event.company_id.country_id, self.env.ref("base.be"))
|
||||
self.assertEqual(event.country_id, self.env.ref("base.in"))
|
||||
|
||||
@users('user_eventregistrationdesk')
|
||||
def test_registration_form_phone(self):
|
||||
""" Test onchange on phone / mobile, should try to format number """
|
||||
event = self.test_event.with_user(self.env.user)
|
||||
|
||||
lead_form = Form(self.env['event.registration'])
|
||||
lead_form.event_id = event
|
||||
lead_form.mobile = '7200000011'
|
||||
lead_form.phone = '7200000000'
|
||||
self.assertEqual(lead_form.mobile, '+917200000011')
|
||||
self.assertEqual(lead_form.phone, '+917200000000')
|
||||
|
||||
@users('user_eventregistrationdesk')
|
||||
def test_registration_phone_format(self):
|
||||
""" Test phone formatting: based on partner (BE numbers) or event
|
||||
(IN numbers) or company (BE numbers). """
|
||||
event = self.test_event.with_user(self.env.user)
|
||||
|
||||
# customer_id, mobile, phone -> based on partner or event country
|
||||
sources = [
|
||||
(self.event_customer.id, None, None), # BE local on partner
|
||||
(self.event_customer2.id, None, None), # BE local on partner
|
||||
(self.event_customer2.id, '0456001122', None), # BE local + on partner
|
||||
(False, '0456778899', '+32456778899'), # BE local + BE global
|
||||
(False, '7200000000', False), # IN local
|
||||
(False, False, '7200000011'), # IN local
|
||||
(False, '7200000000', '7200000011'), # IN local
|
||||
(False, '+917200000088', '+917200000099'), # IN global
|
||||
]
|
||||
# mobile, phone
|
||||
expected = [
|
||||
(False, '0485112233'), # partner values, no format
|
||||
('0456654321', '0456987654'), # partner values, no format
|
||||
('+32456001122', '0456987654'), # BE on partner / partner value, no format
|
||||
('0456778899', '+32456778899'), # IN on event -> cannot format BE
|
||||
('+917200000000', False), # IN on event
|
||||
(False, '+917200000011'), # IN on event
|
||||
('+917200000000', '+917200000011'), # IN on event
|
||||
('+917200000088', '+917200000099'), # already formatted
|
||||
]
|
||||
for (partner_id, mobile, phone), (exp_mobile, exp_phone) in zip(sources, expected):
|
||||
with self.subTest(partner_id=partner_id, mobile=mobile, phone=phone):
|
||||
create_vals = {
|
||||
'event_id': event.id,
|
||||
'partner_id': partner_id,
|
||||
}
|
||||
if mobile is not None:
|
||||
create_vals['mobile'] = mobile
|
||||
if phone is not None:
|
||||
create_vals['phone'] = phone
|
||||
reg = self.env['event.registration'].create(create_vals)
|
||||
self.assertEqual(reg.mobile, exp_mobile)
|
||||
self.assertEqual(reg.phone, exp_phone)
|
||||
|
||||
# no country on event -> based on partner or event company country
|
||||
self.test_event.write({'address_id': False})
|
||||
expected = [
|
||||
(False, '0485112233'), # partner values, no format
|
||||
('0456654321', '0456987654'), # partner values, no format
|
||||
('+32456001122', '0456987654'), # BE on partner / partner value, no format
|
||||
('+32456778899', '+32456778899'), # BE on company
|
||||
('7200000000', False), # BE on company -> cannot format IN
|
||||
(False, '7200000011'), # BE on company -> cannot format IN
|
||||
('7200000000', '7200000011'), # BE on company -> cannot format IN
|
||||
('+917200000088', '+917200000099'), # already formatted
|
||||
]
|
||||
for (partner_id, mobile, phone), (exp_mobile, exp_phone) in zip(sources, expected):
|
||||
with self.subTest(partner_id=partner_id, mobile=mobile, phone=phone):
|
||||
create_vals = {
|
||||
'event_id': event.id,
|
||||
'partner_id': partner_id,
|
||||
}
|
||||
if mobile is not None:
|
||||
create_vals['mobile'] = mobile
|
||||
if phone is not None:
|
||||
create_vals['phone'] = phone
|
||||
reg = self.env['event.registration'].create(create_vals)
|
||||
self.assertEqual(reg.mobile, exp_mobile)
|
||||
self.assertEqual(reg.phone, exp_phone)
|
||||
|
||||
|
||||
@tagged('event_ticket')
|
||||
class TestEventTicketData(TestEventInternalsCommon):
|
||||
|
||||
@freeze_time('2020-1-31 10:00:00')
|
||||
@@ -829,6 +949,7 @@ class TestEventTicketData(TestEventInternalsCommon):
|
||||
reg_draft.write({'state': 'open'})
|
||||
|
||||
|
||||
@tagged('event_event')
|
||||
class TestEventTypeData(TestEventInternalsCommon):
|
||||
|
||||
@users('user_eventmanager')
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
from collections import defaultdict
|
||||
|
||||
from odoo import api, fields, models, _
|
||||
from odoo.addons.phone_validation.tools import phone_validation
|
||||
|
||||
|
||||
class EventRegistration(models.Model):
|
||||
@@ -188,16 +189,30 @@ class EventRegistration(models.Model):
|
||||
|
||||
:return dict: values used for create / write on a lead
|
||||
"""
|
||||
valid_partner = related_partner = next(
|
||||
valid_partner = next(
|
||||
(reg.partner_id for reg in self if reg.partner_id != self.env.ref('base.public_partner')),
|
||||
self.env['res.partner']
|
||||
) # CHECKME: broader than just public partner
|
||||
|
||||
# mono registration mode: keep partner only if email and phone matches, otherwise registration > partner
|
||||
if len(self) == 1:
|
||||
if (related_partner.phone and self.phone and related_partner.phone != self.phone) or \
|
||||
(related_partner.email and self.email and related_partner.email != self.email):
|
||||
# mono registration mode: keep partner only if email and phone matches;
|
||||
# otherwise registration > partner. Note that _phone_format has to be
|
||||
# taken into account as it may have side effects on phone formatting
|
||||
if len(self) == 1 and valid_partner:
|
||||
if self.email and valid_partner.email and valid_partner.email != self.email:
|
||||
valid_partner = self.env['res.partner']
|
||||
if valid_partner and self.phone and valid_partner.phone:
|
||||
phone_formatted = phone_validation.phone_format(
|
||||
self.phone,
|
||||
valid_partner.country_id.code or None,
|
||||
valid_partner.country_id.phone_code or None,
|
||||
force_format='E164',
|
||||
raise_exception=False
|
||||
)
|
||||
partner_phone_formatted = valid_partner._phone_format(valid_partner.phone)
|
||||
if phone_formatted and partner_phone_formatted and phone_formatted != partner_phone_formatted:
|
||||
valid_partner = self.env['res.partner']
|
||||
if (not phone_formatted or not partner_phone_formatted) and self.phone != valid_partner.phone:
|
||||
valid_partner = self.env['res.partner']
|
||||
|
||||
if valid_partner:
|
||||
contact_vals = self.env['crm.lead']._prepare_values_from_partner(valid_partner)
|
||||
|
||||
@@ -168,14 +168,16 @@ class TestEventSale(TestEventSaleCommon):
|
||||
)
|
||||
self.assertEqual(
|
||||
set(ticket1_new_reg.mapped('phone')),
|
||||
set(['+32456111111', self.event_customer.phone])
|
||||
set(['+32456111111', self.event_customer._phone_format(self.event_customer.phone)])
|
||||
)
|
||||
self.assertEqual(
|
||||
set(ticket1_new_reg.mapped('mobile')),
|
||||
set(['+32456222222', self.event_customer.mobile])
|
||||
set(['+32456222222', self.event_customer._phone_format(self.event_customer.mobile)])
|
||||
)
|
||||
for field in ['name', 'email', 'phone', 'mobile']:
|
||||
for field in ['name', 'email']:
|
||||
self.assertEqual(ticket2_new_reg[field], self.event_customer[field])
|
||||
for field in ['phone', 'mobile']:
|
||||
self.assertEqual(ticket2_new_reg[field], self.event_customer._phone_format(self.event_customer[field]))
|
||||
|
||||
# ADDING MANUAL LINES ON SO
|
||||
# ------------------------------------------------------------
|
||||
|
||||
Reference in New Issue
Block a user