diff --git a/addons/event/data/mail_template_data.xml b/addons/event/data/mail_template_data.xml index e34855e198b..75235a18b76 100644 --- a/addons/event/data/mail_template_data.xml +++ b/addons/event/data/mail_template_data.xml @@ -11,7 +11,7 @@ Sent automatically to someone after they registered to an event
- Dear Oscar Morgan,
+ Dear ,
Thank you for your inquiry.
Here is your badge for the event OpenWood Collection Online Reveal.
If you have any questions, please let us know. @@ -50,7 +50,7 @@ Your registration
- Oscar Morgan + @@ -75,7 +75,7 @@
- Hello Oscar Morgan,
+ Hello ,
We are happy to confirm your registration to the event OpenWood Collection Online Reveal @@ -83,7 +83,7 @@ OpenWood Collection Online Reveal - for attendee Oscar Morgan. + for attendee .

@@ -282,7 +282,7 @@
Your registration
- Oscar Morgan +
- Hello Oscar Morgan,
+ Hello ,
We are excited to remind you that the event OpenWood Collection Online Reveal diff --git a/addons/event/models/event_registration.py b/addons/event/models/event_registration.py index 22043ef9c5e..15456493a58 100644 --- a/addons/event/models/event_registration.py +++ b/addons/event/models/event_registration.py @@ -34,6 +34,8 @@ class EventRegistration(models.Model): email = fields.Char(string='Email', compute='_compute_email', readonly=False, store=True, tracking=11) phone = fields.Char(string='Phone', compute='_compute_phone', readonly=False, store=True, tracking=12) mobile = fields.Char(string='Mobile', compute='_compute_mobile', readonly=False, store=True, tracking=13) + company_name = fields.Char( + string='Company Name', compute='_compute_company_name', readonly=False, store=True, tracking=14) # organization date_closed = fields.Datetime( string='Attended Date', compute='_compute_date_closed', @@ -86,6 +88,15 @@ class EventRegistration(models.Model): fnames=['mobile'] ).get('mobile') or False + @api.depends('partner_id') + def _compute_company_name(self): + for registration in self: + if not registration.company_name and registration.partner_id: + registration.company_name = registration._synchronize_partner_values( + registration.partner_id, + fnames=['company_name'] + ).get('company_name') or False + @api.depends('state') def _compute_date_closed(self): for registration in self: diff --git a/addons/event/views/event_registration_views.xml b/addons/event/views/event_registration_views.xml index e813c50e303..d5497e684a9 100644 --- a/addons/event/views/event_registration_views.xml +++ b/addons/event/views/event_registration_views.xml @@ -16,6 +16,7 @@ + @@ -64,6 +65,7 @@ + diff --git a/addons/website_event/controllers/main.py b/addons/website_event/controllers/main.py index 7ae4c40491e..781c02ca756 100644 --- a/addons/website_event/controllers/main.py +++ b/addons/website_event/controllers/main.py @@ -1,20 +1,14 @@ # -*- coding: utf-8 -*- import babel.dates -import pytz import re import werkzeug from ast import literal_eval -from collections import defaultdict -from datetime import datetime, timedelta -from dateutil.parser import parse -from dateutil.relativedelta import relativedelta from werkzeug.datastructures import OrderedMultiDict from werkzeug.exceptions import NotFound from odoo import fields, http, _ -from odoo.addons.http_routing.models.ir_http import slug from odoo.addons.website.controllers.main import QueryURL from odoo.http import request from odoo.osv import expression @@ -270,51 +264,65 @@ class WebsiteEventController(http.Controller): if int(ticket_id) not in event.event_ticket_ids.ids and len(event.event_ticket_ids.ids) > 0: raise UserError(_("This ticket is not available for sale for this event")) registrations = {} - global_values = {} general_answer_ids = [] + general_identification_answers = {} + # as we may have several questions populating the same field (e.g: the phone) + # we use this to hold the fields that have already been handled + # goal is to use the answer to the first question of every 'type' (aka name / phone / email / company name) + already_handled_fields_data = {} for key, value in form_details.items(): - if 'question_answer' in key and value: - dummy, registration_index, question_id = key.split('-') - question_sudo = request.env['event.question'].browse(int(question_id)) - answer_values = None - if question_sudo.question_type == 'simple_choice': - answer_values = { - 'question_id': int(question_id), - 'value_answer_id': int(value) - } - elif question_sudo.question_type == 'text_box': - answer_values = { - 'question_id': int(question_id), - 'value_text_box': value - } + if not value: + continue - if answer_values and not int(registration_index): - general_answer_ids.append((0, 0, answer_values)) - elif answer_values: - registrations.setdefault(registration_index, dict())\ - .setdefault('registration_answer_ids', list()).append((0, 0, answer_values)) - else: - counter, attr_name = key.split('-', 1) - field_name = attr_name.split('-')[0] + key_values = key.split('-') + # Special case for handling event_ticket_id data that holds only 2 values + if len(key_values) == 2: + registration_index, field_name = key_values if field_name not in registration_fields: continue - elif isinstance(registration_fields[field_name], (fields.Many2one, fields.Integer)): - value = int(value) or False # 0 is considered as a void many2one aka False - else: - value = value + registrations.setdefault(registration_index, dict())[field_name] = int(value) + continue - if counter == '0': - global_values[attr_name] = value + registration_index, question_type, question_id = key_values + answer_values = None + if question_type == 'simple_choice': + answer_values = { + 'question_id': int(question_id), + 'value_answer_id': int(value) + } + else: + answer_values = { + 'question_id': int(question_id), + 'value_text_box': value + } + + if answer_values and not int(registration_index): + general_answer_ids.append((0, 0, answer_values)) + elif answer_values: + registrations.setdefault(registration_index, dict())\ + .setdefault('registration_answer_ids', list()).append((0, 0, answer_values)) + + if question_type in ('name', 'email', 'phone', 'company_name')\ + and question_type not in already_handled_fields_data.get(registration_index, []): + if question_type not in registration_fields: + continue + + field_name = question_type + already_handled_fields_data.setdefault(registration_index, list()).append(field_name) + + if not int(registration_index): + general_identification_answers[field_name] = value else: - registrations.setdefault(counter, dict())[attr_name] = value - for key, value in global_values.items(): - for registration in registrations.values(): - registration[key] = value + registrations.setdefault(registration_index, dict())[field_name] = value if general_answer_ids: for registration in registrations.values(): registration.setdefault('registration_answer_ids', list()).extend(general_answer_ids) + if general_identification_answers: + for registration in registrations.values(): + registration.update(general_identification_answers) + return list(registrations.values()) def _create_attendees_from_registration_post(self, event, registration_data): diff --git a/addons/website_event/data/event_demo.xml b/addons/website_event/data/event_demo.xml index 5468167445b..0f12a338c79 100644 --- a/addons/website_event/data/event_demo.xml +++ b/addons/website_event/data/event_demo.xml @@ -1,5 +1,29 @@ + + + + + + + + + + + + + + + Get Inspired • Stay Connected • Have Fun @@ -36,6 +60,10 @@
+ @@ -57,6 +85,10 @@ + @@ -99,6 +131,10 @@ + @@ -117,12 +153,20 @@ + Discover how to grow a sustainable business with our experts. {"background-image": "url('/website_event/static/src/img/event_cover_4.jpg')", "resize_class": "o_record_has_cover o_half_screen_height", "opacity": "0.4"} + @@ -142,11 +186,19 @@ + {"background-image": "none", "background-color": "secondary", "opacity": ""} + @@ -169,6 +221,10 @@ + diff --git a/addons/website_event/models/event_event.py b/addons/website_event/models/event_event.py index b7b42da0509..15462b1c3a5 100644 --- a/addons/website_event/models/event_event.py +++ b/addons/website_event/models/event_event.py @@ -2,7 +2,6 @@ # Part of Odoo. See LICENSE file for full copyright and licensing details. from ast import literal_eval -from collections import defaultdict from dateutil.relativedelta import relativedelta import json import werkzeug.urls @@ -36,6 +35,9 @@ class Event(models.Model): }) return res + def _default_question_ids(self): + return self.env['event.type']._default_question_ids() + # description subtitle = fields.Char('Event Subtitle', translate=True) # registration @@ -219,7 +221,7 @@ class Event(models.Model): questions_tokeep_ids = [] for event in self: if not event.event_type_id and not event.question_ids: - event.question_ids = False + event.question_ids = self._default_question_ids() continue if questions_tokeep_ids: diff --git a/addons/website_event/models/event_question.py b/addons/website_event/models/event_question.py index b005731cd17..6b7d1f87264 100644 --- a/addons/website_event/models/event_question.py +++ b/addons/website_event/models/event_question.py @@ -14,7 +14,12 @@ class EventQuestion(models.Model): title = fields.Char(required=True, translate=True) question_type = fields.Selection([ ('simple_choice', 'Selection'), - ('text_box', 'Text Input')], default='simple_choice', string="Question Type", required=True) + ('text_box', 'Text Input'), + ('name', 'Name'), + ('email', 'Email'), + ('phone', 'Phone'), + ('company_name', 'Company'), + ], default='simple_choice', string="Question Type", required=True) event_type_id = fields.Many2one('event.type', 'Event Type', ondelete='cascade') event_id = fields.Many2one('event.event', 'Event', ondelete='cascade') answer_ids = fields.One2many('event.question.answer', 'question_id', "Answers", copy=True) diff --git a/addons/website_event/models/event_registration.py b/addons/website_event/models/event_registration.py index 6fea1061f26..4bf084649f8 100644 --- a/addons/website_event/models/event_registration.py +++ b/addons/website_event/models/event_registration.py @@ -12,4 +12,4 @@ class EventRegistration(models.Model): registration_answer_ids = fields.One2many('event.registration.answer', 'registration_id', string='Attendee Answers') def _get_website_registration_allowed_fields(self): - return {'name', 'phone', 'email', 'mobile', 'event_id', 'partner_id', 'event_ticket_id'} + return {'name', 'phone', 'email', 'mobile', 'company_name', 'event_id', 'partner_id', 'event_ticket_id'} diff --git a/addons/website_event/models/event_type.py b/addons/website_event/models/event_type.py index 57e963f8bff..03052a4ba1e 100644 --- a/addons/website_event/models/event_type.py +++ b/addons/website_event/models/event_type.py @@ -1,13 +1,20 @@ # -*- coding: utf-8 -*- # Part of Odoo. See LICENSE file for full copyright and licensing details. -from odoo import api, fields, models +from odoo import api, fields, models, _ class EventType(models.Model): _name = 'event.type' _inherit = ['event.type'] + def _default_question_ids(self): + return [ + (0, 0, {'title': _('Name'), 'question_type': 'name', 'is_mandatory_answer': True}), + (0, 0, {'title': _('Email'), 'question_type': 'email', 'is_mandatory_answer': True}), + (0, 0, {'title': _('Phone'), 'question_type': 'phone'}), + ] + website_menu = fields.Boolean('Display a dedicated menu on Website') community_menu = fields.Boolean( "Community Menu", compute="_compute_community_menu", @@ -17,7 +24,7 @@ class EventType(models.Model): 'Extra Register Button', compute='_compute_menu_register_cta', readonly=False, store=True) question_ids = fields.One2many( - 'event.question', 'event_type_id', + 'event.question', 'event_type_id', default=_default_question_ids, string='Questions', copy=True) @api.depends('website_menu') diff --git a/addons/website_event/static/tests/tours/tickets_questions.js b/addons/website_event/static/tests/tours/tickets_questions.js index a54b5a3409b..c6d014df220 100644 --- a/addons/website_event/static/tests/tours/tickets_questions.js +++ b/addons/website_event/static/tests/tours/tickets_questions.js @@ -25,10 +25,10 @@ registry.category("web_tour.tours").add('test_tickets_questions', { trigger: 'div:contains("Ticket #1").modal-body input[name*="phone"]', run: 'text +32499123456' }, { - trigger: 'div:contains("Ticket #1").modal-body select[name*="question_answer"]', + trigger: 'div:contains("Ticket #1").modal-body select[name*="1-simple_choice"]', run: 'text Vegetarian' }, { - trigger: 'div:contains("Ticket #1").modal-body textarea[name*="question_answer"]', + trigger: 'div:contains("Ticket #1").modal-body textarea[name*="1-text_box"]', run: 'text Fish and Nuts' }, { trigger: 'div:contains("Ticket #2").modal-body input[name*="name"]', @@ -37,13 +37,16 @@ registry.category("web_tour.tours").add('test_tickets_questions', { trigger: 'div:contains("Ticket #2").modal-body input[name*="email"]', run: 'text attendee-b@gmail.com' }, { - trigger: 'div:contains("Ticket #2").modal-body select[name*="question_answer"]', + trigger: 'div:contains("Ticket #2").modal-body input[name*="company_name"]', + run: 'text My Company' +}, { + trigger: 'div:contains("Ticket #2").modal-body select[name*="2-simple_choice"]', run: 'text Pastafarian' }, { - trigger: 'div.o_wevent_registration_question_global select[name*="question_answer"]', + trigger: 'div.o_wevent_registration_question_global select[name*="0-simple_choice"]', run: 'text A friend' }, { - trigger: 'button:contains("Continue")', + trigger: 'button[type=submit]', run: 'click' }, { // The tour stops too early and the registration fails if we don't wait the confirmation. diff --git a/addons/website_event/tests/common.py b/addons/website_event/tests/common.py index bf19adcb309..d798bee5173 100644 --- a/addons/website_event/tests/common.py +++ b/addons/website_event/tests/common.py @@ -2,12 +2,9 @@ # Part of Odoo. See LICENSE file for full copyright and licensing details. from datetime import datetime, timedelta, time -from unittest.mock import patch from odoo.addons.event.tests.common import EventCase from odoo.addons.mail.tests.common import mail_new_test_user -from odoo.fields import Datetime as FieldsDatetime, Date as FieldsDate -from odoo.tests.common import TransactionCase class OnlineEventCase(EventCase): diff --git a/addons/website_event/tests/test_event_internals.py b/addons/website_event/tests/test_event_internals.py index ca5f364b5aa..4fe636115e5 100644 --- a/addons/website_event/tests/test_event_internals.py +++ b/addons/website_event/tests/test_event_internals.py @@ -25,10 +25,15 @@ class TestEventData(TestEventQuestionCommon): self.assertEqual( event.question_ids.mapped('question_type'), - ['simple_choice', 'simple_choice', 'text_box']) - self.assertEqual(event.specific_question_ids.title, 'Question1') + ['name', 'email', 'phone', 'simple_choice', 'simple_choice', 'text_box']) + self.assertEqual(event.specific_question_ids.filtered( + lambda q: q.question_type in ['simple_choice', 'text_box']).title, 'Question1') + self.assertEqual(event.specific_question_ids.filtered( + lambda q: q.question_type in ['name', 'email', 'phone', 'company_name']) + .mapped('title'), ['Name', 'Email', 'Phone']) self.assertEqual( - set(event.specific_question_ids.mapped('answer_ids.name')), + set(event.specific_question_ids.filtered( + lambda q: q.question_type in ['simple_choice', 'text_box']).mapped('answer_ids.name')), set(['Q1-Answer1', 'Q1-Answer2'])) self.assertEqual(len(event.general_question_ids), 2) self.assertEqual(event.general_question_ids[0].title, 'Question2') @@ -55,19 +60,34 @@ class TestEventData(TestEventQuestionCommon): 'seats_max': 200, }]) + [name_question, email_question, phone_question] = event.question_ids.filtered( + lambda q: q.question_type in ('name', 'email', 'phone', 'company_name')) + + [second_phone_question, company_name_question] = self.env['event.question'].create([{ + 'title': 'Second Phone', + 'question_type': 'phone', + 'event_id': event.id, + }, { + 'title': 'Company Name', + 'question_type': 'company_name', + 'event_id': event.id, + }]) + form_details = { - '1-name': 'Pixis', - '1-email': 'pixis@gmail.com', - '1-phone': '+32444444444', + '1-name-%s' % name_question.id: 'Pixis', + '1-email-%s' % email_question.id: 'pixis@gmail.com', + '1-phone-%s' % phone_question.id: '+32444444444', + '1-phone-%s' % second_phone_question.id: '+32555555555', '1-event_ticket_id': ticket_id_1.id, - '2-name': 'Geluchat', - '2-email': 'geluchat@gmail.com', - '2-phone': '+32777777777', + '2-name-%s' % name_question.id: 'Geluchat', + '2-email-%s' % email_question.id: 'geluchat@gmail.com', + '2-phone-%s' % phone_question.id: '+32777777777', + '2-company_name-%s' % company_name_question.id: 'My Company', '2-event_ticket_id': ticket_id_2.id, - 'question_answer-1-%s' % self.event_question_1.id: '5', - 'question_answer-2-%s' % self.event_question_1.id: '9', - 'question_answer-0-%s' % self.event_question_2.id: '7', - 'question_answer-0-%s' % self.event_question_3.id: 'Free Text', + '1-simple_choice-%s' % self.event_question_1.id: '5', + '2-simple_choice-%s' % self.event_question_1.id: '9', + '0-simple_choice-%s' % self.event_question_2.id: '7', + '0-text_box-%s' % self.event_question_3.id: 'Free Text', } with MockRequest(self.env): @@ -76,11 +96,20 @@ class TestEventData(TestEventQuestionCommon): self.assertEqual(registrations, [ {'name': 'Pixis', 'email': 'pixis@gmail.com', 'phone': '+32444444444', 'event_ticket_id': ticket_id_1.id, 'registration_answer_ids': [ + (0, 0, {'question_id': name_question.id, 'value_text_box': 'Pixis'}), + (0, 0, {'question_id': email_question.id, 'value_text_box': 'pixis@gmail.com'}), + (0, 0, {'question_id': phone_question.id, 'value_text_box': '+32444444444'}), + (0, 0, {'question_id': second_phone_question.id, 'value_text_box': '+32555555555'}), (0, 0, {'question_id': self.event_question_1.id, 'value_answer_id': 5}), (0, 0, {'question_id': self.event_question_2.id, 'value_answer_id': 7}), (0, 0, {'question_id': self.event_question_3.id, 'value_text_box': 'Free Text'})]}, - {'name': 'Geluchat', 'email': 'geluchat@gmail.com', 'phone': '+32777777777', 'event_ticket_id': ticket_id_2.id, + {'name': 'Geluchat', 'email': 'geluchat@gmail.com', 'phone': '+32777777777', 'company_name': 'My Company', + 'event_ticket_id': ticket_id_2.id, 'registration_answer_ids': [ + (0, 0, {'question_id': name_question.id, 'value_text_box': 'Geluchat'}), + (0, 0, {'question_id': email_question.id, 'value_text_box': 'geluchat@gmail.com'}), + (0, 0, {'question_id': phone_question.id, 'value_text_box': '+32777777777'}), + (0, 0, {'question_id': company_name_question.id, 'value_text_box': 'My Company'}), (0, 0, {'question_id': self.event_question_1.id, 'value_answer_id': 9}), (0, 0, {'question_id': self.event_question_2.id, 'value_answer_id': 7}), (0, 0, {'question_id': self.event_question_3.id, 'value_text_box': 'Free Text'})]} diff --git a/addons/website_event/tests/test_website_event.py b/addons/website_event/tests/test_website_event.py index ab694d3cdb4..8a615845c72 100644 --- a/addons/website_event/tests/test_website_event.py +++ b/addons/website_event/tests/test_website_event.py @@ -48,6 +48,18 @@ class TestUi(HttpCaseWithUserDemo): })], 'website_published': True, 'question_ids': [(0, 0, { + 'title': 'Name', + 'question_type': 'name', + }), (0, 0, { + 'title': 'Email', + 'question_type': 'email', + }), (0, 0, { + 'title': 'Phone', + 'question_type': 'phone', + }), (0, 0, { + 'title': 'Company Name', + 'question_type': 'company_name', + }), (0, 0, { 'title': 'Meal Type', 'question_type': 'simple_choice', 'answer_ids': [ @@ -81,12 +93,13 @@ class TestUi(HttpCaseWithUserDemo): self.assertEqual(first_registration.name, 'Attendee A') self.assertEqual(first_registration.phone, '+32499123456') self.assertEqual(second_registration.name, 'Attendee B') + self.assertEqual(second_registration.company_name, 'My Company') event_questions = registrations.mapped('event_id.question_ids') - self.assertEqual(len(event_questions), 3) + self.assertEqual(len(event_questions), 7) first_registration_answers = first_registration.registration_answer_ids - self.assertEqual(len(first_registration_answers), 3) + self.assertEqual(len(first_registration_answers), 6) self.assertEqual(first_registration_answers.filtered( lambda answer: answer.question_id.title == 'Meal Type' @@ -101,7 +114,7 @@ class TestUi(HttpCaseWithUserDemo): ).value_answer_id.name, 'A friend') second_registration_answers = second_registration.registration_answer_ids - self.assertEqual(len(second_registration_answers), 2) + self.assertEqual(len(second_registration_answers), 5) self.assertEqual(second_registration_answers.filtered( lambda answer: answer.question_id.title == 'Meal Type' diff --git a/addons/website_event/views/event_event_views.xml b/addons/website_event/views/event_event_views.xml index c5547adc2d5..56fe49c5a3c 100644 --- a/addons/website_event/views/event_event_views.xml +++ b/addons/website_event/views/event_event_views.xml @@ -39,7 +39,8 @@ -