[IMP] event and event_* modules: onboarding improvements

event:
- added tip
- default subscription/reminder/thanks templates for registrations are now using
the reply_to defined on the event
- fixed check of available seats: use seats_availability field, unlimited events
should never trigger the constraint
- added a sequence on scheduled emails on events
- config: wording improvements
- various improvements in event, registration, event_mail and event_report views

event_sale:
- removed override of seats_max: should not depend on available seats in
tickets. Indeed you may have less available places than the sum of seats for
each ticket type, for example if you have 0-10 VIP and 0-20 Standard tickets
for a 20-seats event.
- removed a duplicated view override
- added a seats_availability field on tickets, same meaning as on event.
- improved event form view: registration tab is now replaced by the tickets tab
when tickets are used

website_event:
- better management of sold-out event in the front-end, when doing registrations

website_event_sale:
- removed register link on the event kanban view. There is no need to to from
the back-end to the front-end from the kanban view. The published button on the
event form view already allows to go to the front-end view on the event
- better management of sold-out event in the front-end, when doing registrations
- fixed a template that was in customize and should not

website_event_track:
- wording improvements
- small improvements in the front-end of tracks
This commit is contained in:
Mahendra Barad
2015-01-29 17:39:41 +01:00
committed by Thibault Delavallée
parent 5d53fbc833
commit d9fa702d5e
20 changed files with 85 additions and 118 deletions
+1
View File
@@ -26,6 +26,7 @@ Key Features
'report/report_event_registration_view.xml',
'event_view.xml',
'event_data.xml',
'event_tip_data.xml',
'res_config_view.xml',
'email_template.xml',
'views/event.xml',
+3
View File
@@ -7,6 +7,7 @@
<field name="model_id" ref="event.model_event_registration"/>
<field name="email_from">${(object.event_id.organizer_id.email or object.event_id.user_id.email or '')|safe}</field>
<field name="email_to" >${object.email|safe}</field>
<field name="reply_to">${object.event_id.reply_to|safe}</field>
<field name="subject">Your subscription at ${object.event_id.name}</field>
<field name="body_html"><![CDATA[<p>Hello ${object.name},</p>
<p>We confirm that your subscription to the event ${object.event_id.name} has been recorded.
@@ -20,6 +21,7 @@ You will automatically receive an email providing you more practical information
<field name="model_id" ref="event.model_event_registration"/>
<field name="email_from">${(object.event_id.organizer_id.email or object.event_id.user_id.email or '')|safe}</field>
<field name="email_to" >${object.partner_id.email or object.email|safe}</field>
<field name="reply_to">${object.event_id.reply_to|safe}</field>
<field name="subject">Reminder of event ${object.event_id.name}</field>
<field name="body_html"><![CDATA[<p>Hello ${object.name},</p>
<p>This is a reminder for ${object.event_id.name} that you registered for is confirmed and will be held from ${object.event_id.date_begin_located} to ${object.event_id.date_end_located}.
@@ -33,6 +35,7 @@ For any further information please contact our event department.</p>
<field name="model_id" ref="event.model_event_registration"/>
<field name="email_from">${(object.event_id.organizer_id.email or object.event_id.user_id.email or '')|safe}</field>
<field name="email_to" >${object.partner_id.email or object.email|safe}</field>
<field name="reply_to">${object.event_id.reply_to|safe}</field>
<field name="subject">Thanks for attending ${object.event_id.name}</field>
<field name="body_html"><![CDATA[<p>Hello ${object.name},</p>
<p>We would like to thank you for your presence at our event ${object.event_id.name}.
+3 -3
View File
@@ -183,7 +183,7 @@ class event_event(models.Model):
@api.one
@api.constrains('seats_max', 'seats_available')
def _check_seats_limit(self):
if self.seats_max and self.seats_available < 0:
if self.seats_availability == 'limited' and self.seats_max and self.seats_available < 0:
raise UserError(_('No more available seats.'))
@api.one
@@ -283,7 +283,7 @@ class event_registration(models.Model):
@api.one
@api.constrains('event_id', 'state')
def _check_seats_limit(self):
if self.event_id.seats_max and self.event_id.seats_available < (1 if self.state == 'draft' else 0):
if self.event_id.seats_availability == 'limited' and self.event_id.seats_max and self.event_id.seats_available < (1 if self.state == 'draft' else 0):
raise UserError(_('No more seats available for this event.'))
@api.multi
@@ -292,7 +292,7 @@ class event_registration(models.Model):
return False
if any(registration.event_id.state != 'confirm' or
not registration.event_id.auto_confirm or
not registration.event_id.seats_available for registration in self):
(not registration.event_id.seats_available and registration.event_id.seats_availability == 'limited') for registration in self):
return False
return True
+7 -3
View File
@@ -21,8 +21,9 @@ class EventMailScheduler(models.Model):
that periodically checks for mailing to run. """
_name = 'event.mail'
event_id = fields.Many2one('event.event', string='Event', required=True, readonly=True, ondelete='cascade')
interval_nbr = fields.Integer('Timing', default=1)
event_id = fields.Many2one('event.event', string='Event', required=True, ondelete='cascade')
sequence = fields.Integer('Display order')
interval_nbr = fields.Integer('Interval', default=1)
interval_unit = fields.Selection([
('now', 'Immediately'),
('hours', 'Hour(s)'), ('days', 'Day(s)'),
@@ -108,4 +109,7 @@ class EventMailRegistration(models.Model):
@api.one
@api.depends('registration_id', 'scheduler_id.interval_unit', 'scheduler_id.interval_type')
def _compute_scheduled_date(self):
self.scheduled_date = datetime.strptime(self.registration_id.date_open, tools.DEFAULT_SERVER_DATETIME_FORMAT) + _INTERVALS[self.scheduler_id.interval_unit](self.scheduler_id.interval_nbr)
if self.registration_id:
self.scheduled_date = datetime.strptime(self.registration_id.date_open, tools.DEFAULT_SERVER_DATETIME_FORMAT) + _INTERVALS[self.scheduler_id.interval_unit](self.scheduler_id.interval_nbr)
else:
self.scheduled_date = False
+13
View File
@@ -0,0 +1,13 @@
<?xml version="1.0" encoding="UTF-8"?>
<odoo>
<record model="web.tip" id="event_tip_1">
<field name="title"></field>
<field name="description">By clicking here, you can create and manage registration(s) for this event, send emails to attendee(s) and print identification badges.</field>
<field name="action_id" ref="event.action_event_view"/>
<field name="model">event.event</field>
<field name="mode">form</field>
<field name="trigger_selector">.oe_form_buttons_view:visible,div.oe_right.oe_button_box > button</field>
<field name="highlight_selector">div.oe_right.oe_button_box:visible > button:nth-child(1)</field>
<field name="placement">bottom</field>
</record>
</odoo>
+7 -4
View File
@@ -102,11 +102,14 @@
</group>
</page>
<page string="Email Schedule">
<field name="reply_to"/>
<group>
<field name="reply_to"/>
</group>
<field name="event_mail_ids">
<tree string="Email Schedule" editable="bottom">
<field name="sequence" widget="handle"/>
<field name="template_id" />
<field name="interval_nbr"/>
<field name="interval_nbr" attrs="{'readonly':[('interval_unit','=','now')]}"/>
<field name="interval_unit"/>
<field name="interval_type"/>
<field name="done"/>
@@ -312,7 +315,7 @@
<field name="partner_id" attrs="{'readonly':[('state', '!=', 'draft')]}"/>
</group>
<group string="Event Information" name="event">
<field name="event_id" readonly="1"/>
<field name="event_id" attrs="{'readonly': [('state', '!=', 'draft')]}"/>
<field name="date_open" groups="base.group_no_one"/>
<field name="date_closed" groups="base.group_no_one"/>
</group>
@@ -407,7 +410,7 @@
<field name="mail_sent"/>
</group>
<group>
<field name="interval_nbr"/>
<field name="interval_nbr" attrs="{'readonly':[('interval_unit', '=', 'now')]}"/>
<field name="interval_unit"/>
<field name="interval_type"/>
<field name="scheduled_date"/>
@@ -33,6 +33,7 @@ class report_event_registration(models.Model):
event_date = fields.Datetime('Event Date', readonly=True)
event_id = fields.Many2one('event.event', 'Event', required=True)
draft_state = fields.Integer(' # No of Draft Registrations')
cancel_state = fields.Integer(' # No of Cancelled Registrations')
confirm_state = fields.Integer(' # No of Confirmed Registrations')
seats_max = fields.Integer('Max Seats')
nbevent = fields.Integer('Number of Events')
@@ -62,6 +63,7 @@ class report_event_registration(models.Model):
count(r.event_id) AS nbregistration,
CASE WHEN r.state IN ('draft') THEN count(r.event_id) ELSE 0 END AS draft_state,
CASE WHEN r.state IN ('open','done') THEN count(r.event_id) ELSE 0 END AS confirm_state,
CASE WHEN r.state IN ('cancel') THEN count(r.event_id) ELSE 0 END AS cancel_state,
e.type AS event_type,
e.seats_max AS seats_max,
e.state AS event_state,
@@ -7,7 +7,7 @@
<field name="model">report.event.registration</field>
<field name="arch" type="xml">
<pivot string="Event on Registration" disable_linking="True">
<field name="event_type" type="row"/>
<field name="event_id" type="row"/>
<field name="event_date" interval="month" type="col"/>
<field name="confirm_state" type="measure"/>
</pivot>
@@ -19,7 +19,7 @@
<field name="model">report.event.registration</field>
<field name="arch" type="xml">
<graph string="Event on Registration">
<field name="event_type"/>
<field name="event_id"/>
<field name="event_date" interval="month"/>
<field name="confirm_state" type="measure"/>
</graph>
+2 -2
View File
@@ -7,10 +7,10 @@ class event_config_settings(osv.TransientModel):
_inherit='marketing.config.settings'
_columns = {
'module_event_sale': fields.boolean(
'Sale different type of ticket',
'Sell events tickets',
help='Install the event_sale module'),
'module_website_event_track': fields.boolean(
'Organize few days event with track,full agenda,own menu in website'),
'Organize few days event with track, full agenda, own menu in website'),
'auto_confirmation': fields.boolean(
'Automate events and subscription confirmation', help='Unselect this option to manually manage draft event and draft subscription'),
}
-3
View File
@@ -5,9 +5,6 @@
/* End of customize */
cursor: default;
}
.openerp .oe_kanban_view .oe_event_kanban_view .oe_event_fontend_link {
/*padding-top: 2px;*/
}
.openerp .oe_kanban_view .oe_event_kanban_view .oe_event_date_thumb {
border-top-left-radius:3px;
border-top-right-radius:3px;
+1
View File
@@ -21,6 +21,7 @@ class TestEventFlow(TestEventCommon):
'date_begin': datetime.datetime.now() + relativedelta(days=-1),
'date_end': datetime.datetime.now() + relativedelta(days=1),
'seats_max': 2,
'seats_availability': 'limited',
})
self.assertEqual(test_event.state, 'confirm', 'Event: auto_confirmation of event failed')
+13 -14
View File
@@ -12,11 +12,6 @@ class event_event(models.Model):
event_ticket_ids = fields.One2many(
'event.event.ticket', 'event_id', string='Event Ticket',
default=lambda rec: rec._default_tickets(), copy=True)
seats_max = fields.Integer(
string='Maximum Available Seats',
help="The maximum registration level is equal to the sum of the maximum registration of event ticket. " +
"If you have too much registrations you are not able to confirm your event. (0 to ignore this rule )",
store=True, readonly=True, compute='_compute_seats_max')
badge_back = fields.Html('Badge Back', translate=True, states={'done': [('readonly', True)]})
badge_innerleft = fields.Html('Badge Innner Left', translate=True, states={'done': [('readonly', True)]})
@@ -34,11 +29,6 @@ class event_event(models.Model):
except ValueError:
return self.env['event.event.ticket']
@api.one
@api.depends('event_ticket_ids.seats_max')
def _compute_seats_max(self):
self.seats_max = sum(ticket.seats_max for ticket in self.event_ticket_ids)
class event_ticket(models.Model):
_name = 'event.event.ticket'
@@ -70,8 +60,11 @@ class event_ticket(models.Model):
# be considered in the timezone of the event, not the timezone of the user!
# Until we add a TZ on the event we'll use the context's current date, more accurate
# than using UTC all the time.
current_date = fields.Date.context_today(self.with_context({'tz': self.event_id.date_tz}))
self.is_expired = self.deadline < current_date
if self.deadline:
current_date = fields.Date.context_today(self.with_context({'tz': self.event_id.date_tz}))
self.is_expired = self.deadline < current_date
else:
self.is_expired = False
# FIXME non-stored fields wont ends up in _columns (and thus _all_columns), which forbid them
# to be used in qweb views. Waiting a fix, we create an old function field directly.
@@ -99,7 +92,12 @@ class event_ticket(models.Model):
}
# seats fields
seats_max = fields.Integer('Maximum Available Seats', help="You can for each event define a maximum registration level. If you have too much registrations you are not able to confirm your event. (put 0 to ignore this rule )")
seats_availability = fields.Selection(
[('limited', 'Limited'), ('unlimited', 'Unlimited')],
'Available Seat', required=True, store=True, compute='_compute_seats', default="limited")
seats_max = fields.Integer('Maximum Available Seats',
help="Define the number of available tickets. If you have too much registrations you will"
"not BE able to sell tickets anymore. Set 0 to ignore this rule set as unlimited.")
seats_reserved = fields.Integer(string='Reserved Seats', compute='_compute_seats', store=True)
seats_available = fields.Integer(string='Available Seats', compute='_compute_seats', store=True)
seats_unconfirmed = fields.Integer(string='Unconfirmed Seat Reservations', compute='_compute_seats', store=True)
@@ -109,8 +107,9 @@ class event_ticket(models.Model):
@api.depends('seats_max', 'registration_ids.state')
def _compute_seats(self):
""" Determine reserved, available, reserved but unconfirmed and used seats. """
# initialize fields to 0
# initialize fields to 0 + compute seats availability
for ticket in self:
ticket.seats_availability = 'unlimited' if ticket.seats_max == 0 else 'limited'
ticket.seats_unconfirmed = ticket.seats_reserved = ticket.seats_used = ticket.seats_available = 0
# aggregate registrations by ticket and by state
if self.ids:
+15 -33
View File
@@ -43,49 +43,31 @@
</field>
</record>
<record id="view_event_form" model="ir.ui.view">
<field name="name">view_event_form</field>
<record model="ir.ui.view" id="view_event_form_inherit_ticket">
<field name="name">view_event_form_inherit_tichet</field>
<field name="model">event.event</field>
<field name="inherit_id" ref="event.view_event_form"/>
<field name="arch" type="xml">
<data>
<xpath expr="//page[@string='Registrations']" position="after">
<page string="Ticket Types">
<xpath expr="//page[@string='Registrations']" position="replace">
<page string="Tickets">
<group>
<group>
<field name="auto_confirm" invisible="1"/>
<field name="seats_min" attrs="{'invisible':[('auto_confirm','=', True)]}"/>
<field name="seats_availability" widget='radio'/>
<field name="seats_max" attrs="{'invisible':[('seats_availability','=','unlimited')]}"/>
</group>
</group>
<field name="event_ticket_ids" context="{'default_name': name}">
<tree string="Payments" editable="bottom">
<tree string="Tickets" editable="bottom">
<field name="name"/>
<field name="product_id"/>
<field name="deadline"/>
<field name="price"/>
<field name="seats_max"/>
<field name="seats_reserved"/>
<field name="seats_unconfirmed"/>
</tree>
</field>
</page>
</xpath>
</data>
</field>
</record>
<!-- Edit badge report form -->
<record id="view_event_form" model="ir.ui.view">
<field name="name">view_event_form</field>
<field name="model">event.event</field>
<field name="inherit_id" ref="event.view_event_form"/>
<field name="arch" type="xml">
<data>
<xpath expr="//notebook" position="inside">
<page string="Ticket Types">
<field name="event_ticket_ids" context="{'default_name': name}">
<tree string="Payments" editable="bottom">
<field name="name"/>
<field name="product_id"/>
<field name="deadline"/>
<field name="price"/>
<field name="seats_max"/>
<field name="seats_reserved"/>
<field name="seats_unconfirmed"/>
<field name="seats_reserved" readonly="1"/>
<field name="seats_unconfirmed" readonly="1"/>
</tree>
</field>
</page>
+5 -5
View File
@@ -398,18 +398,18 @@
<td>Free</td>
<td></td>
<td>
<select t-if="event.seats_available" name="nb_register-0" class="form-control">
<t t-foreach="range(0, event.seats_available > 9 and 10 or event.seats_available+1)" t-as="nb">
<select t-if="event.seats_available or event.seats_availability == 'unlimited'" name="nb_register-0" class="form-control">
<t t-foreach="range(0, (event.seats_availability == 'unlimited' or event.seats_available > 9) and 10 or event.seats_available+1)" t-as="nb">
<option t-esc="nb"/>
</t>
</select>
<span t-if="not event.seats_available">Sold Out</span>
<span t-if="event.seats_availability == 'limited' and not event.seats_available">Sold Out</span>
</td>
</tr>
</tbody>
</table>
<t t-if="event.seats_available">
<button type="submit" t-if="event.seats_available" class="btn btn-primary btn-lg pull-right a-submit" t-attf-id="#{event.id}">Register Now</button>
<t t-if="event.seats_availability == 'unlimited' or event.seats_available">
<button type="submit" class="btn btn-primary btn-lg pull-right a-submit" t-attf-id="#{event.id}">Register Now</button>
</t>
</form>
</template>
@@ -21,18 +21,5 @@
</field>
</record>
<record model="ir.ui.view" id="view_event_kanban">
<field name="name">event.event.kanban</field>
<field name="model">event.event</field>
<field name="inherit_id" ref="event.view_event_kanban"/>
<field name="arch" type="xml">
<data>
<xpath expr="//t[@t-name='kanban-box']/div" position="attributes">
<attribute name="data-routing">event</attribute>
</xpath>
</data>
</field>
</record>
</data>
</openerp>
-1
View File
@@ -14,7 +14,6 @@ Online Event's Tickets
'author': 'OpenERP SA',
'depends': ['website_event', 'event_sale', 'website_sale'],
'data': [
'views/website_event_sale_backend.xml',
'views/website_event_sale.xml',
'security/ir.model.access.csv',
'security/website_event_sale.xml',
@@ -11,15 +11,15 @@
<template id="index" inherit_id="website_event.index" name="Event's Ticket">
<xpath expr="//li[@t-foreach='event_ids']/div/h4" position="before">
<t t-if="event.state in ['draft', 'confirm'] and event.event_ticket_ids">
<span t-if="not event.seats_available" class="label label-danger pull-right">Sold Out</span>
<span t-if="event.seats_available and event.seats_available &lt;= ((event.seats_max or 0) / 4)" class="label pull-right label-info">
<span t-if="event.seats_availability == 'limited' and not event.seats_available" class="label label-danger pull-right">Sold Out</span>
<span t-if="event.seats_availability == 'limited' and event.seats_available and event.seats_available &lt;= ((event.seats_max or 0) / 4)" class="label pull-right label-info">
Only <t t-esc="event.seats_available"/> Remaining
</span>
</t>
</xpath>
</template>
<template id="registration_template" inherit_id="website_event.registration_template" customize_show="True" name="Event's Ticket form">
<template id="registration_template" inherit_id="website_event.registration_template">
<xpath expr="//tbody" position="replace">
<tbody>
<t t-foreach="event.event_ticket_ids" t-as="ticket">
@@ -62,19 +62,19 @@
</span>
</td>
<td>
<select t-if="ticket.seats_available" t-attf-name="nb_register-#{ticket.id}" class="form-control">
<t t-foreach="range(0, ticket.seats_available > 9 and 10 or ticket.seats_available+1 )" t-as="nb">
<select t-if="ticket.seats_available or ticket.seats_availability == 'unlimited'" t-attf-name="nb_register-#{ticket.id}" class="form-control">
<t t-foreach="range(0, (ticket.seats_availability == 'unlimited' or ticket.seats_available > 9) and 10 or ticket.seats_available + 1)" t-as="nb">
<option t-esc="nb"/>
</t>
</select>
<span t-if="not ticket.seats_available">Sold Out</span>
<span t-if="ticket.seats_availability == 'limited' and not ticket.seats_available">Sold Out</span>
</td>
</tr>
</t>
</tbody>
</xpath>
<xpath expr="//button[@type='submit']" position="replace">
<button type="submit" t-if="event.seats_available" class="btn btn-primary btn-lg pull-right a-submit" t-attf-id="#{event.id}">Order Now</button>
<button type="submit" t-if="event.seats_available or event.seats_availability == 'unlimited'" class="btn btn-primary btn-lg pull-right a-submit" t-attf-id="#{event.id}">Order Now</button>
<form t-if="not event.event_ticket_ids">
<div class="alert alert-info">
Event registration not yet started.
@@ -1,24 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<openerp>
<data>
<record model="ir.ui.view" id="view_event_kanban">
<field name="name">event.event.kanban</field>
<field name="model">event.event</field>
<field name="inherit_id" ref="event.view_event_kanban"/>
<field name="arch" type="xml">
<data>
<xpath expr="//templates" position="before">
<field name="website_url"/>
</xpath>
<xpath expr="//h4[last()]" position="after">
<div class="oe_event_fontend_link">
<a t-attf-href="#{record.website_url.raw_value}/register" type="url">Register</a>
</div>
</xpath>
</data>
</field>
</record>
</data>
</openerp>
+2 -2
View File
@@ -23,7 +23,7 @@ class event_track_location(models.Model):
class event_track(models.Model):
_name = "event.track"
_description = 'Event Tracks'
_description = 'Event Track'
_order = 'priority, date'
_inherit = ['mail.thread', 'ir.needaction_mixin', 'website.seo.metadata', 'website.published.mixin']
@@ -38,7 +38,7 @@ class event_track(models.Model):
description = fields.Html('Track Description', translate=True)
date = fields.Datetime('Track Date')
duration = fields.Float('Duration', digits=(16, 2), default=1.5)
location_id = fields.Many2one('event.track.location', 'Location')
location_id = fields.Many2one('event.track.location', 'Room')
event_id = fields.Many2one('event.event', 'Event', required=True)
color = fields.Integer('Color Index')
priority = fields.Selection([
@@ -133,7 +133,7 @@
<li t-if="track.speaker_ids" class="text-muted fa fa-user">
<t t-esc="', '.join([speaker.name for speaker in track.speaker_ids])"></t>
</li>
<li class="text-muted fa fa-calendar"> <span t-field="track.date" t-field-options='{"hide_seconds":"True"}'/></li>
<li t-if="track.date" class="text-muted fa fa-calendar"> <span t-field="track.date" t-field-options='{"hide_seconds":"True"}'/></li>
<li class="text-muted fa fa-map-marker" t-if="track.location_id">
<span t-field="track.location_id"/>
</li>
@@ -145,7 +145,7 @@
</a>
</li>
</ul>
<p class="mt8"><t t-esc="html2text(track.description or '')[0:500]"/>...</p>
<p class="mt8"><t t-esc="html2text(track.description or '')[0:500]"/><t t-if="track.description">...</t></p>
</div>
</div>
</div>