[MERGE] event(_*): improve attendees views and reporting

PURPOSE

To improve Event attendees views and reporting for better usability.

SPECIFICATIONS

The goal of this commit is to improve attendees views and reporting in
Event, the changes  done are listed below.

- When opening attendees from an event (stat button or kanban button), it will display
   the list view (before the kanban one) and group by create_date (week).
- Removed the date_open field from the model as it is a duplicate of the create_date.
- On the attendees tree view, displayed the registration_answer_ids field with a
   many2many_tags widget ​(optional default hide), before the Status.
- Displayed the "X Cancel" button of the attendees tree view in red.
- Moved the partner_id field next to the ticket and kept it editable in attendees form view,
   also moved visitor field below the Mobile one so that even in debug mode, Attendee
   Name is the "primary" field.
- Added a badge widget on the `state` field.
- Added next activity widget to the field `event_ticket_id`.

While coming to Attendee reporting section we have changed the default view as graph
view instead of stern pivot view and added default groupby `state`, `event_id`, `create_date` and
created a new filter `Last 30 Days` and made it default.
Apart from that we have made `summary` field default hide in Meeting Room tree view.

LINKS
PR #79166
Task 2670765

Related: odoo/enterprise#21976
Related: odoo/upgrade#3066
Signed-off-by: Thibault Delavallee (tde) <tde@openerp.com>
This commit is contained in:
Odoo's Mergebot
2021-11-30 13:52:19 +01:00
committed by GitHub
11 changed files with 94 additions and 100 deletions
+1 -3
View File
@@ -288,8 +288,6 @@ class EventMailRegistration(models.Model):
def _compute_scheduled_date(self):
for mail in self:
if mail.registration_id:
date_open = mail.registration_id.date_open
date_open_datetime = date_open or fields.Datetime.now()
mail.scheduled_date = date_open_datetime + _INTERVALS[mail.scheduler_id.interval_unit](mail.scheduler_id.interval_nbr)
mail.scheduled_date = mail.registration_id.create_date + _INTERVALS[mail.scheduler_id.interval_unit](mail.scheduler_id.interval_nbr)
else:
mail.scheduled_date = False
+1 -24
View File
@@ -27,9 +27,7 @@ class EventRegistration(models.Model):
utm_source_id = fields.Many2one('utm.source', 'Source', index=True, ondelete='set null')
utm_medium_id = fields.Many2one('utm.medium', 'Medium', index=True, ondelete='set null')
# attendee
partner_id = fields.Many2one(
'res.partner', string='Booked by',
states={'done': [('readonly', True)]})
partner_id = fields.Many2one('res.partner', string='Booked by', tracking=1)
name = fields.Char(
string='Attendee Name', index=True,
compute='_compute_name', readonly=False, store=True, tracking=10)
@@ -37,7 +35,6 @@ class EventRegistration(models.Model):
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)
# 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', compute='_compute_date_closed',
readonly=False, store=True)
@@ -51,26 +48,6 @@ class EventRegistration(models.Model):
('open', 'Confirmed'), ('done', 'Attended')],
string='Status', default='draft', readonly=True, copy=False, tracking=True)
@api.onchange('partner_id')
def _onchange_partner_id(self):
""" Keep an explicit onchange on partner_id. Rationale : if user explicitly
changes the partner in interface, he want to update the whole customer
information. If partner_id is updated in code (e.g. updating your personal
information after having registered in website_event_sale) fields with a
value should not be reset as we don't know which one is the right one.
In other words
* computed fields based on partner_id should only update missing
information. Indeed automated code cannot decide which information
is more accurate;
* interface should allow to update all customer related information
at once. We consider event users really want to update all fields
related to the partner;
"""
for registration in self:
if registration.partner_id:
registration.update(registration._synchronize_partner_values(registration.partner_id))
@api.depends('partner_id')
def _compute_name(self):
for registration in self:
+9
View File
@@ -24,3 +24,12 @@
}
}
}
.o_event_registration_view_tree {
.o_list_button > .o_btn_cancel_registration {
color: $danger;
&:hover {
color: darken($danger, $emphasized-link-hover-darken-percentage);
}
}
}
+2
View File
@@ -102,7 +102,9 @@ class TestEventCommon(common.TransactionCase):
@classmethod
def _create_registrations(cls, event, reg_count):
# create some registrations
create_date = fields.Datetime.now()
registrations = cls.env['event.registration'].create([{
'create_date': create_date,
'event_id': event.id,
'name': 'Test Registration %s' % x,
'email': '_test_reg_%s@example.com' % x,
-53
View File
@@ -7,62 +7,9 @@ from dateutil.relativedelta import relativedelta
from odoo.addons.event.tests.common import TestEventCommon
from odoo.exceptions import ValidationError
from odoo.tests.common import Form
from odoo.tools import mute_logger
class TestEventUI(TestEventCommon):
def test_event_registration_partner_sync(self):
""" Ensure onchange on partner_id is kept for interface, not for computed
fields. """
registration_form = Form(self.env['event.registration'].with_context(
default_name='WrongName',
default_event_id=self.event_0.id
))
self.assertEqual(registration_form.event_id, self.event_0)
self.assertEqual(registration_form.name, 'WrongName')
self.assertFalse(registration_form.email)
self.assertFalse(registration_form.phone)
self.assertFalse(registration_form.mobile)
# trigger onchange
registration_form.partner_id = self.event_customer
self.assertEqual(registration_form.name, self.event_customer.name)
self.assertEqual(registration_form.email, self.event_customer.email)
self.assertEqual(registration_form.phone, self.event_customer.phone)
self.assertEqual(registration_form.mobile, self.event_customer.mobile)
# save, check record matches Form values
registration = registration_form.save()
self.assertEqual(registration.partner_id, self.event_customer)
self.assertEqual(registration.name, self.event_customer.name)
self.assertEqual(registration.email, self.event_customer.email)
self.assertEqual(registration.phone, self.event_customer.phone)
self.assertEqual(registration.mobile, self.event_customer.mobile)
# allow writing on some fields independently from customer config
registration.write({'phone': False, 'mobile': False})
self.assertFalse(registration.phone)
self.assertFalse(registration.mobile)
# reset partner should not reset other fields
registration.write({'partner_id': False})
self.assertEqual(registration.partner_id, self.env['res.partner'])
self.assertEqual(registration.name, self.event_customer.name)
self.assertEqual(registration.email, self.event_customer.email)
self.assertFalse(registration.phone)
self.assertFalse(registration.mobile)
# update to a new partner not through UI -> update only void feilds
registration.write({'partner_id': self.event_customer2.id})
self.assertEqual(registration.partner_id, self.event_customer2)
self.assertEqual(registration.name, self.event_customer.name)
self.assertEqual(registration.email, self.event_customer.email)
self.assertEqual(registration.phone, self.event_customer2.phone)
self.assertEqual(registration.mobile, self.event_customer2.mobile)
class TestEventFlow(TestEventCommon):
@mute_logger('odoo.addons.base.models.ir_model', 'odoo.models')
@@ -89,12 +89,14 @@ class TestMailSchedule(TestEventCommon, MockEmail):
# create some registrations
with freeze_time(now), self.mock_mail_gateway():
reg1 = self.env['event.registration'].with_user(self.user_eventuser).create({
reg1 = self.env['event.registration'].create({
'create_date': now,
'event_id': test_event.id,
'name': 'Reg1',
'email': 'reg1@example.com',
})
reg2 = self.env['event.registration'].with_user(self.user_eventuser).create({
reg2 = self.env['event.registration'].create({
'create_date': now,
'event_id': test_event.id,
'name': 'Reg2',
'email': 'reg2@example.com',
@@ -105,7 +107,7 @@ class TestMailSchedule(TestEventCommon, MockEmail):
# check registration state
self.assertTrue(all(reg.state == 'open' for reg in reg1 + reg2), 'Registrations: should be auto-confirmed')
self.assertTrue(all(reg.date_open == now for reg in reg1 + reg2), 'Registrations: should have open date set to confirm date')
self.assertTrue(all(reg.create_date == now for reg in reg1 + reg2), 'Registrations: should have open date set to confirm date')
# verify that subscription scheduler was auto-executed after each registration
self.assertEqual(len(after_sub_scheduler.mail_registration_ids), 2, 'event: should have 2 scheduled communication (1 / registration)')
@@ -204,7 +206,8 @@ class TestMailSchedule(TestEventCommon, MockEmail):
test_event.write({'auto_confirm': False})
with freeze_time(now_start), self.mock_mail_gateway():
reg3 = self.env['event.registration'].with_user(self.user_eventuser).create({
reg3 = self.env['event.registration'].create({
'create_date': now_start,
'event_id': test_event.id,
'name': 'Reg3',
'email': 'reg3@example.com',
@@ -220,7 +223,7 @@ class TestMailSchedule(TestEventCommon, MockEmail):
self.assertTrue(after_sub_scheduler_2.mail_done, 'event: scheduler on registration not updated next to draft registration')
# confirm registration -> should trigger registration schedulers
# NOTE: currently all schedulers are based on date_open which equals create_date
# NOTE: currently all schedulers are based on create_date
# meaning several communications may be sent in the time time
with freeze_time(now_start + relativedelta(hours=1)), self.mock_mail_gateway():
reg3.action_confirm()
+50 -12
View File
@@ -121,6 +121,23 @@
<!-- EVENT.REGISTRATION ACTIONS -->
<record id="act_event_registration_from_event" model="ir.actions.act_window">
<field name="res_model">event.registration</field>
<field name="name">Attendees</field>
<field name="view_mode">tree,kanban,form,calendar,graph</field>
<field name="domain">[('event_id', '=', active_id)]</field>
<field name="context">{'default_event_id': active_id}</field>
<field name="help" type="html">
<p class="o_view_nocontent_smiling_face">
No Attendees yet!
</p><p>
Wait until Attendees register to your Event or create their registrations manually.
</p>
</field>
</record>
<!-- We need kanban view to be displayed first while coming from registration desk, so we have created
new action and changed the view sequence. -->
<record id="event_registration_action_kanban" model="ir.actions.act_window">
<field name="res_model">event.registration</field>
<field name="name">Attendees</field>
<field name="view_mode">kanban,tree,form,calendar,graph</field>
@@ -181,7 +198,8 @@
<div class="oe_button_box" name="button_box" groups="base.group_user">
<button name="%(event.act_event_registration_from_event)d"
type="action"
context="{'search_default_expected': True}"
context="{'search_default_expected': True,
'search_default_group_by_create_date_week': True}"
class="oe_stat_button"
icon="fa-users"
help="Total Registrations for this Event">
@@ -364,7 +382,10 @@
<div t-if="record.address_id.value"><i class="fa fa-map-marker" title="Location"/> <span class="o_text_overflow o_event_kanban_location" t-esc="record.address_id.value"/></div>
</div>
<h5 class="o_event_fontsize_11 p-0">
<a name="%(act_event_registration_from_event)d" type="action" context="{'search_default_expected': True}">
<a name="%(act_event_registration_from_event)d"
type="action"
context="{'search_default_expected': True,
'search_default_group_by_create_date_week': True}">
<t t-esc="record.seats_expected.raw_value"/> Expected attendees
</a>
<t t-set="total_seats" t-value="record.seats_reserved.raw_value + record.seats_used.raw_value"/>
@@ -495,9 +516,10 @@
<field name="name">event.registration.tree</field>
<field name="model">event.registration</field>
<field name="arch" type="xml">
<tree string="Registration" multi_edit="1" sample="1">
<tree string="Registration" multi_edit="1" sample="1"
expand="1" default_order="create_date desc"
class="o_event_registration_view_tree">
<field name="create_date" optional="show" string="Registration Date"/>
<field name="date_open" optional="hide"/>
<field name="name"/>
<field name="partner_id" optional="hide"/>
<field name="email" optional="show"/>
@@ -505,12 +527,17 @@
<field name="mobile" optional="hide"/>
<field name="event_id" invisible="context.get('default_event_id')"/>
<field name="event_ticket_id" domain="[('event_id', '=', event_id)]"/>
<field name="state" readonly="0"/>
<field name="activity_ids" widget="list_activity"/>
<field name="state" decoration-info="state in ('draft', 'open')"
decoration-success="state == 'done'"
decoration-muted="state == 'cancel'" widget="badge"/>
<field name="company_id" groups="base.group_multi_company" optional="hide"/>
<field name="message_needaction" invisible="1"/>
<button name="action_confirm" string="Confirm" states="draft" type="object" icon="fa-check"/>
<button name="action_set_done" string="Mark as Attending" states="open" type="object" icon="fa-level-down"/>
<button name="action_cancel" string="Cancel" states="draft,open" type="object" icon="fa-times"/>
<button name="action_cancel" string="Cancel"
states="draft,open" type="object"
class="o_btn_cancel_registration" icon="fa-times"/>
<field name="activity_exception_decoration" widget="activity_exception"/>
</tree>
</field>
@@ -533,7 +560,6 @@
<div class="oe_button_box" name="button_box"/>
<group>
<group string="Attendee" name="attendee">
<field name="partner_id" attrs="{'readonly':[('state', '!=', 'draft')]}"/>
<field class="o_text_overflow" name="name"/>
<field name="email"/>
<field name="phone" class="o_force_ltr"/>
@@ -548,7 +574,8 @@
]"
attrs="{'invisible': [('event_id', '=', False)]}"
options="{'no_open': True, 'no_create': True}"/>
<field name="date_open" groups="base.group_no_one"/>
<field name="partner_id"/>
<field name="create_date" string="Registration Date" groups="base.group_no_one"/>
<field name="date_closed" groups="base.group_no_one"/>
</group>
<group string="Marketing" name="utm_link" groups="base.group_no_one">
@@ -686,7 +713,7 @@
<filter string="Confirmed" name="confirmed" domain="[('state', '=', 'open')]"/>
<filter string="Attended" name="attended" domain="[('state', '=', 'done')]"/>
<separator/>
<filter string="Registration Date" name="filter_date_open" date="date_open"/>
<filter string="Registration Date" name="filter_create_date" date="create_date"/>
<filter string="Event Start Date" name="filter_event_begin_date" date="event_begin_date"/>
<filter string="Attended Date" name="filter_date_closed" date="date_closed"/>
<field name="event_id"/>
@@ -699,12 +726,16 @@
domain="[('my_activity_date_deadline', '=', context_today().strftime('%Y-%m-%d'))]"/>
<filter invisible="1" string="Future Activities" name="activities_upcoming_all"
domain="[('my_activity_date_deadline', '&gt;', context_today().strftime('%Y-%m-%d'))]"/>
<filter string="Last 30 days" name="filter_last_month_creation" domain="[('create_date','&gt;', (context_today() - datetime.timedelta(days=30)).strftime('%Y-%m-%d'))]"/>
<group expand="0" string="Group By">
<filter string="Partner" name="partner" domain="[]" context="{'group_by':'partner_id'}"/>
<filter string="Event" name="group_event" domain="[]" context="{'group_by':'event_id'}"/>
<filter string="Ticket Type" name ="group_event_ticket_id" domain="[]" context="{'group_by': 'event_ticket_id'}"/>
<filter string="Status" name="status" domain="[]" context="{'group_by':'state'}"/>
<filter string="Registration Date" name="createmonth" domain="[]" context="{'group_by': 'create_date:month'}"/>
<filter string="Registration Date" name="group_by_create_date_week" domain="[]" context="{'group_by': 'create_date:week'}"
invisible="context.get('registration_view_hide_group_by_create_date_week')"/>
<filter string="Registration Date" name="group_by_create_date_day" domain="[]" context="{'group_by': 'create_date:day'}"
invisible="not context.get('registration_view_hide_group_by_create_date_week')"/>
</group>
</search>
</field>
@@ -714,8 +745,15 @@
<field name="name">Attendees</field>
<field name="res_model">event.registration</field>
<field name="domain"></field>
<field name="view_mode">pivot,graph,kanban,tree,form</field>
<field name="context">{}</field>
<field name="view_mode">graph,pivot,kanban,tree,form</field>
<field name="context">{
'search_default_filter_last_month_creation': 1,
'search_default_status': 1,
'search_default_group_by_create_date_day': 1,
'search_default_group_event': 1,
'registration_view_hide_group_by_create_date_week': 1,
}
</field>
<field name="search_view_id" ref="view_registration_search"/>
<field name="help" type="html">
<p class="o_view_nocontent_smiling_face">
@@ -17,7 +17,7 @@
<field name="model">event.registration</field>
<field name="inherit_id" ref="event.view_event_registration_form"/>
<field name="arch" type="xml">
<xpath expr="//field[@name='partner_id']" position="after">
<xpath expr="//field[@name='mobile']" position="after">
<field name="visitor_id" groups="base.group_no_one"/>
</xpath>
</field>
@@ -68,12 +68,12 @@
<field name="arch" type="xml">
<tree string="Meeting Room" multi_edit="1" sample="1">
<field name="name"/>
<field name="summary"/>
<field name="summary" optional="hide"/>
<field name="target_audience"/>
<field name="is_published"/>
<field name="is_pinned"/>
<field name="room_is_full" readonly="1"/>
<field name="room_participant_count" readonly="1"/>
<field name="room_participant_count" readonly="1" sum="Total Participant Count" />
<field name="room_max_capacity"/>
<field name="room_lang_id"/>
</tree>
@@ -28,3 +28,12 @@ class EventRegistrationAnswer(models.Model):
_sql_constraints = [
('value_check', "CHECK(value_answer_id IS NOT NULL OR COALESCE(value_text_box, '') <> '')", "There must be a suggested value or a text value.")
]
# for displaying selected answers by attendees in attendees list view
def name_get(self):
return [
(reg.id,
reg.value_answer_id.name if reg.question_type == "simple_choice" else reg.value_text_box
)
for reg in self
]
@@ -24,4 +24,15 @@
</sheet>
</field>
</record>
<record id="event_registration_view_tree" model="ir.ui.view">
<field name="name">event.registration.view.tree.inherit.website.event.questions</field>
<field name="model">event.registration</field>
<field name="inherit_id" ref="event.view_event_registration_tree"/>
<field name="arch" type="xml">
<field name="state" position="before">
<field name="registration_answer_ids" string="Selected Answers" widget="many2many_tags" optional="hide"/>
</field>
</field>
</record>
</odoo>