[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:
committed by
Thibault Delavallée
parent
5d53fbc833
commit
d9fa702d5e
@@ -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',
|
||||
|
||||
@@ -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"><
|
||||
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
|
||||
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
|
||||
@@ -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'),
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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')
|
||||
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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 <= ((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 <= ((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>
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user