[IMP] event: Split Ticket: registration are now for one attendee only.
Registration are now for one attendee only. When buying several seats for an event you have now one registration for each attendee. event: nb_register field is removed; as well as unnecessary user_id and it subscribe / unsubscribe behavior. Also slighly cleaned some views. event_sale: do not auto confirm registrations linked to a draft sale order. Note: strange origin is a char field, not a sale_order_id. Added a small wizard to edit attendees data when confirming a sale order containing event related lines. website_event: when buying free tickets, ask for attendee details. Added template, controllers to handle that behavior. website_event_sale: when buying tickets, ask for attendee details. Events ecommerce should be better integrated with online events, using inheritance to add details instead of being very different.
This commit is contained in:
committed by
Thibault Delavallée
parent
b7eb97c39e
commit
ca862e7b85
@@ -29,6 +29,8 @@ Key Features
|
||||
'res_config_view.xml',
|
||||
'email_template.xml',
|
||||
'views/event.xml',
|
||||
'event_report.xml',
|
||||
'views/report_registrationbadge.xml',
|
||||
],
|
||||
'demo': [
|
||||
'event_demo.xml',
|
||||
|
||||
+17
-50
@@ -81,7 +81,7 @@ class event_event(models.Model):
|
||||
store=True, readonly=True, compute='_compute_seats')
|
||||
|
||||
@api.multi
|
||||
@api.depends('seats_max', 'registration_ids.state', 'registration_ids.nb_register')
|
||||
@api.depends('seats_max', 'registration_ids.state')
|
||||
def _compute_seats(self):
|
||||
""" Determine reserved, available, reserved but unconfirmed and used seats. """
|
||||
# initialize fields to 0
|
||||
@@ -94,7 +94,7 @@ class event_event(models.Model):
|
||||
'open': 'seats_reserved',
|
||||
'done': 'seats_used',
|
||||
}
|
||||
query = """ SELECT event_id, state, sum(nb_register)
|
||||
query = """ SELECT event_id, state, count(event_id)
|
||||
FROM event_registration
|
||||
WHERE event_id IN %s AND state IN ('draft', 'open', 'done')
|
||||
GROUP BY event_id, state
|
||||
@@ -110,7 +110,7 @@ class event_event(models.Model):
|
||||
|
||||
# Registration fields
|
||||
registration_ids = fields.One2many(
|
||||
'event.registration', 'event_id', string='Registrations',
|
||||
'event.registration', 'event_id', string='Attendees',
|
||||
readonly=False, states={'done': [('readonly', True)]})
|
||||
count_registrations = fields.Integer(string='Registrations', compute='_count_registrations')
|
||||
|
||||
@@ -245,36 +245,6 @@ class event_event(models.Model):
|
||||
""" Confirm Event and send confirmation email to all register peoples """
|
||||
self.confirm_event()
|
||||
|
||||
@api.one
|
||||
def subscribe_to_event(self):
|
||||
""" Subscribe the current user to a given event """
|
||||
user = self.env.user
|
||||
num_of_seats = int(self._context.get('ticket', 1))
|
||||
regs = self.registration_ids.filtered(lambda reg: reg.user_id == user)
|
||||
# the subscription is done as SUPERUSER_ID because in case we share the
|
||||
# kanban view, we want anyone to be able to subscribe
|
||||
if not regs:
|
||||
regs = regs.sudo().create({
|
||||
'event_id': self.id,
|
||||
'email': user.email,
|
||||
'name': user.name,
|
||||
'user_id': user.id,
|
||||
'nb_register': num_of_seats,
|
||||
})
|
||||
else:
|
||||
regs.write({'nb_register': num_of_seats})
|
||||
if regs._check_auto_confirmation():
|
||||
regs.sudo().confirm_registration()
|
||||
|
||||
@api.one
|
||||
def unsubscribe_to_event(self):
|
||||
""" Unsubscribe the current user from a given event """
|
||||
# the unsubscription is done as SUPERUSER_ID because in case we share
|
||||
# the kanban view, we want anyone to be able to unsubscribe
|
||||
user = self.env.user
|
||||
regs = self.sudo().registration_ids.filtered(lambda reg: reg.user_id == user)
|
||||
regs.button_reg_cancel()
|
||||
|
||||
@api.onchange('type')
|
||||
def _onchange_type(self):
|
||||
if self.type:
|
||||
@@ -289,35 +259,31 @@ class event_event(models.Model):
|
||||
res = self.env['ir.actions.act_window'].for_xml_id('event', 'action_report_event_registration')
|
||||
res['context'] = {
|
||||
"search_default_event_id": self.id,
|
||||
"group_by": ['event_date:day'],
|
||||
"group_by": ['create_date:day'],
|
||||
}
|
||||
return res
|
||||
|
||||
|
||||
class event_registration(models.Model):
|
||||
_name = 'event.registration'
|
||||
_description = 'Event Registration'
|
||||
_description = 'Attendee'
|
||||
_inherit = ['mail.thread', 'ir.needaction_mixin']
|
||||
_order = 'name, create_date desc'
|
||||
|
||||
origin = fields.Char(
|
||||
string='Source Document', readonly=True,
|
||||
help="Reference of the sales order which created the registration") # funny we refer sale orders... event is not sale related
|
||||
nb_register = fields.Integer(
|
||||
string='Number of Participants', required=True, default=1,
|
||||
readonly=True, states={'draft': [('readonly', False)]})
|
||||
help="Reference of the document that created the registration, for example a sale order")
|
||||
event_id = fields.Many2one(
|
||||
'event.event', string='Event', required=True,
|
||||
readonly=True, states={'draft': [('readonly', False)]})
|
||||
partner_id = fields.Many2one(
|
||||
'res.partner', string='Partner',
|
||||
'res.partner', string='Contact',
|
||||
states={'done': [('readonly', True)]})
|
||||
date_open = fields.Datetime(string='Registration Date', readonly=True)
|
||||
date_closed = fields.Datetime(string='Attended Date', readonly=True)
|
||||
reply_to = fields.Char(string='Reply-to Email', related='event_id.reply_to', readonly=True)
|
||||
event_begin_date = fields.Datetime(string="Event Start Date", related='event_id.date_begin', readonly=True)
|
||||
event_end_date = fields.Datetime(string="Event End Date", related='event_id.date_end', readonly=True)
|
||||
user_id = fields.Many2one('res.users', string='User', states={'done': [('readonly', True)]})
|
||||
company_id = fields.Many2one(
|
||||
'res.company', string='Company', related='event_id.company_id',
|
||||
store=True, readonly=True, states={'draft': [('readonly', False)]})
|
||||
@@ -327,17 +293,18 @@ class event_registration(models.Model):
|
||||
string='Status', default='draft', readonly=True, copy=False, track_visibility='onchange')
|
||||
email = fields.Char(string='Email')
|
||||
phone = fields.Char(string='Phone')
|
||||
name = fields.Char(string='Name', select=True)
|
||||
name = fields.Char(string='Attendee Name', select=True)
|
||||
|
||||
@api.one
|
||||
@api.constrains('event_id', 'state', 'nb_register')
|
||||
@api.constrains('event_id', 'state')
|
||||
def _check_seats_limit(self):
|
||||
if self.event_id.seats_max and \
|
||||
self.event_id.seats_available < (self.nb_register if self.state == 'draft' else 0):
|
||||
raise Warning(_('Only %s seats available for this event') % self.event_id.seats_available if self.event_id.seats_available > 0 else _('No more seats available for this event.'))
|
||||
if self.event_id.seats_max and self.event_id.seats_available < (1 if self.state == 'draft' else 0):
|
||||
raise Warning(_('No more seats available for this event.'))
|
||||
|
||||
@api.one
|
||||
def _check_auto_confirmation(self):
|
||||
if self._context.get('registration_force_draft'):
|
||||
return False
|
||||
if self.event_id and self.event_id.state == 'confirm' and self.event_id.auto_confirm and self.event_id.seats_available:
|
||||
return True
|
||||
return False
|
||||
@@ -345,7 +312,7 @@ class event_registration(models.Model):
|
||||
@api.model
|
||||
def create(self, vals):
|
||||
res = super(event_registration, self).create(vals)
|
||||
if res._check_auto_confirmation():
|
||||
if res._check_auto_confirmation()[0]:
|
||||
res.sudo().confirm_registration()
|
||||
return res
|
||||
|
||||
@@ -402,6 +369,6 @@ class event_registration(models.Model):
|
||||
contact_id = self.partner_id.address_get().get('default', False)
|
||||
if contact_id:
|
||||
contact = self.env['res.partner'].browse(contact_id)
|
||||
self.name = contact.name
|
||||
self.email = contact.email
|
||||
self.phone = contact.phone
|
||||
self.name = self.name or contact.name
|
||||
self.email = self.email or contact.email
|
||||
self.phone = self.phone or contact.phone
|
||||
|
||||
@@ -217,7 +217,6 @@
|
||||
<field name="phone">003281588558</field>
|
||||
<field name="event_id" ref="event_1"/>
|
||||
<field name="partner_id" ref="base.res_partner_2"/>
|
||||
<field name="nb_register">5</field>
|
||||
</record>
|
||||
|
||||
<record id="reg_1_2" model="event.registration">
|
||||
@@ -226,7 +225,6 @@
|
||||
<field name="phone">+ 1 64 61 04 01</field>
|
||||
<field name="partner_id" ref="base.res_partner_1"/>
|
||||
<field name="event_id" ref="event_1"/>
|
||||
<field name="nb_register">10</field>
|
||||
</record>
|
||||
|
||||
<record id="reg_0_2" model="event.registration">
|
||||
@@ -235,7 +233,6 @@
|
||||
<field name="phone">+41 21 619 10 04 </field>
|
||||
<field name="event_id" ref="event_2"/>
|
||||
<field name="partner_id" ref="base.res_partner_12"/>
|
||||
<field name="nb_register">5</field>
|
||||
</record>
|
||||
|
||||
</data>
|
||||
|
||||
@@ -17,17 +17,14 @@
|
||||
<field name="header_spacing">0</field>
|
||||
<field name="dpi">80</field>
|
||||
</record>
|
||||
<report
|
||||
id="action_report_registrationbadge"
|
||||
model="event.registration"
|
||||
string="Badge"
|
||||
report_type="qweb-html"
|
||||
name="event_sale.report_registrationbadge"
|
||||
file="event_sale.report_registrationbadge"
|
||||
/>
|
||||
<record id="action_report_registrationbadge" model="ir.actions.report.xml">
|
||||
<field name="paperformat_id" ref="event_sale.paperformat_euro_lowmargin"/>
|
||||
|
||||
<report id="action_report_registration_badge" model="event.registration"
|
||||
string="Badge" report_type="qweb-html"
|
||||
name="event.report_registration_badge"
|
||||
file="event.report_registration_badge"/>
|
||||
<record model="ir.actions.report.xml" id="event.action_report_registration_badge">
|
||||
<field name="paperformat_id" ref="event.paperformat_euro_lowmargin"/>
|
||||
</record>
|
||||
|
||||
</data>
|
||||
</openerp>
|
||||
</openerp>
|
||||
+14
-32
@@ -46,7 +46,7 @@
|
||||
<record id="act_event_registration_from_event" model="ir.actions.act_window">
|
||||
<field name="res_model">event.registration</field>
|
||||
<field name="view_type">form</field>
|
||||
<field name="name">Registrations</field>
|
||||
<field name="name">Attendees</field>
|
||||
<field name="view_mode">tree,form,calendar,graph</field>
|
||||
<field name="context">{'search_default_event_id': active_id, 'default_event_id': active_id}</field>
|
||||
</record>
|
||||
@@ -75,7 +75,7 @@
|
||||
class="oe_stat_button oe_inline"
|
||||
icon="fa-github"
|
||||
help="Register with this event">
|
||||
<field name="count_registrations" widget="statinfo" string="Registrations"/>
|
||||
<field name="count_registrations" widget="statinfo" string="Attendees"/>
|
||||
</button>
|
||||
</div>
|
||||
<group name="main_field_group">
|
||||
@@ -190,11 +190,9 @@
|
||||
<h4>
|
||||
<a name="%(act_event_registration_from_event)d" type="action">
|
||||
<t t-esc="record.seats_reserved.raw_value" > Attendees</t>
|
||||
<t t-if="(record.seats_reserved.raw_value + record.seats_unconfirmed.raw_value) > 0 ">/
|
||||
<t t-esc="record.seats_reserved.raw_value + record.seats_unconfirmed.raw_value"/> Expected
|
||||
</t>
|
||||
</a>
|
||||
<t t-if="record.seats_reserved.raw_value > 0">
|
||||
<t t-if="(record.seats_reserved.raw_value + record.seats_unconfirmed.raw_value) > 0 ">/
|
||||
<t t-esc="record.seats_reserved.raw_value + record.seats_unconfirmed.raw_value"/> Expected
|
||||
<a name="action_event_registration_report" type="object" >(Report)</a>
|
||||
</t>
|
||||
</h4>
|
||||
@@ -273,7 +271,7 @@
|
||||
|
||||
<menuitem name="Events" id="menu_event_event" action="action_event_view" parent="event.event_main_menu" />
|
||||
|
||||
<!-- EVENTS/REGISTRATIONS/EVENTS -->
|
||||
<!-- EVENT.REGISTRATION VIEWS -->
|
||||
<record model="ir.ui.view" id="view_event_registration_tree">
|
||||
<field name="name">event.registration.tree</field>
|
||||
<field name="model">event.registration</field>
|
||||
@@ -284,14 +282,8 @@
|
||||
<field name="name"/>
|
||||
<field name="email"/>
|
||||
<field name="event_id" />
|
||||
<field name="nb_register"/>
|
||||
<field name="user_id" invisible="1"/>
|
||||
<field name="origin"/>
|
||||
<field name="state"/>
|
||||
<field name="message_unread" invisible="1"/>
|
||||
<button name="registration_open" string="Confirm Registration" states="draft" type="object" icon="gtk-apply"/>
|
||||
<button name="button_reg_close" string="Attended the Event" states="open" type="object" icon="gtk-jump-to"/>
|
||||
<button name="button_reg_cancel" string="Cancel Registration" states="draft,open" type="object" icon="gtk-cancel"/>
|
||||
</tree>
|
||||
</field>
|
||||
</record>
|
||||
@@ -309,22 +301,17 @@
|
||||
<field name="state" nolabel="1" colspan="2" widget="statusbar" statusbar_visible="draft,open,done"/>
|
||||
</header>
|
||||
<sheet string="Registration">
|
||||
<label for="event_id" class="oe_edit_only"/>
|
||||
<h1>
|
||||
<field name="event_id" domain="[('state','in',('draft','confirm'))]"/>
|
||||
</h1>
|
||||
<group>
|
||||
<group>
|
||||
<field name="partner_id" attrs="{'readonly':[('state','!=', 'draft')]}"/>
|
||||
<group string="Attendee Information">
|
||||
<field name="name"/>
|
||||
<field name="phone"/>
|
||||
<field name="email"/>
|
||||
<field name="partner_id" attrs="{'readonly':[('state', '!=', 'draft')]}"/>
|
||||
</group>
|
||||
<group>
|
||||
<field name="create_date" groups="base.group_no_one"/>
|
||||
<group string="Event Information">
|
||||
<field name="event_id" readonly="1"/>
|
||||
<field name="date_open" groups="base.group_no_one"/>
|
||||
<field name="date_closed" groups="base.group_no_one"/>
|
||||
<field name="nb_register"/>
|
||||
<field name="user_id" attrs="{'readonly':[('state','!=', 'draft')]}" context="{'default_groups_ref': ['base.group_user', 'base.group_partner_manager', 'event.group_event_user']}"/>
|
||||
</group>
|
||||
</group>
|
||||
</sheet>
|
||||
@@ -354,7 +341,6 @@
|
||||
<field name="arch" type="xml">
|
||||
<graph string="Registration" type="bar">
|
||||
<field name="event_id" type="row"/>
|
||||
<field name= "nb_register" type="measure"/>
|
||||
</graph>
|
||||
</field>
|
||||
</record>
|
||||
@@ -364,15 +350,11 @@
|
||||
<field name="model">event.registration</field>
|
||||
<field name="arch" type="xml">
|
||||
<search string="Event Registration">
|
||||
<field name="name" string="Participant" filter_domain="['|','|',('name','ilike',self),('email','ilike',self),('origin','ilike',self)]"/>
|
||||
<field name="id" string="Registration ID"/>
|
||||
<field name="name" string="Participant" filter_domain="['|', '|', ('name', 'ilike', self), ('email', 'ilike', self), ('origin', 'ilike', self)]"/>
|
||||
<filter string="Unread Messages" name="message_unread" domain="[('message_unread','=',True)]"/>
|
||||
<separator/>
|
||||
<filter string="New" name="draft" domain="[('state','=','draft')]" help="Registrations in unconfirmed state"/>
|
||||
<filter string="Confirmed" domain="[('state','=','open')]" help="Confirmed registrations"/>
|
||||
<separator/>
|
||||
<filter string="My Registrations" help="My Registrations" domain="[('user_id','=',uid)]"/>
|
||||
<field name="event_id"/>
|
||||
<field name="user_id"/>
|
||||
<field name="partner_id"/>
|
||||
<group expand="0" string="Group By">
|
||||
<filter string="Responsible" domain="[]" context="{'group_by':'user_id'}"/>
|
||||
@@ -387,7 +369,7 @@
|
||||
</record>
|
||||
|
||||
<record model="ir.actions.act_window" id="action_registration">
|
||||
<field name="name">Registrations</field>
|
||||
<field name="name">Attendees</field>
|
||||
<field name="res_model">event.registration</field>
|
||||
<field name="view_type">form</field>
|
||||
<field name="domain"></field>
|
||||
@@ -396,7 +378,7 @@
|
||||
<field name="search_view_id" ref="view_registration_search"/>
|
||||
</record>
|
||||
|
||||
<menuitem name="Registrations"
|
||||
<menuitem name="Attendees"
|
||||
id="menu_action_registration" parent="event.event_main_menu"
|
||||
action="action_registration" groups="event.group_event_manager,event.group_event_user"/>
|
||||
|
||||
|
||||
@@ -29,6 +29,7 @@ class report_event_registration(models.Model):
|
||||
_order = 'event_date desc'
|
||||
_auto = False
|
||||
|
||||
create_date = fields.Datetime('Creation Date', readonly=True)
|
||||
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')
|
||||
@@ -40,7 +41,6 @@ class report_event_registration(models.Model):
|
||||
registration_state = fields.Selection([('draft', 'Draft'), ('confirm', 'Confirmed'), ('done', 'Attended'), ('cancel', 'Cancelled')], 'Registration State', readonly=True, required=True)
|
||||
event_state = fields.Selection([('draft', 'Draft'), ('confirm', 'Confirmed'), ('done', 'Done'), ('cancel', 'Cancelled')], 'Event State', readonly=True, required=True)
|
||||
user_id = fields.Many2one('res.users', 'Event Responsible', readonly=True)
|
||||
user_id_registration = fields.Many2one('res.users', 'Register', readonly=True)
|
||||
name_registration = fields.Char('Participant / Contact Name', readonly=True)
|
||||
company_id = fields.Many2one('res.company', 'Company', readonly=True)
|
||||
|
||||
@@ -54,14 +54,14 @@ class report_event_registration(models.Model):
|
||||
e.id::varchar || '/' || coalesce(r.id::varchar,'') AS id,
|
||||
e.id AS event_id,
|
||||
e.user_id AS user_id,
|
||||
r.user_id AS user_id_registration,
|
||||
r.name AS name_registration,
|
||||
r.create_date AS create_date,
|
||||
e.company_id AS company_id,
|
||||
e.date_begin AS event_date,
|
||||
count(r.id) AS nbevent,
|
||||
sum(r.nb_register) AS nbregistration,
|
||||
CASE WHEN r.state IN ('draft') THEN r.nb_register ELSE 0 END AS draft_state,
|
||||
CASE WHEN r.state IN ('open','done') THEN r.nb_register ELSE 0 END AS confirm_state,
|
||||
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,
|
||||
e.type AS event_type,
|
||||
e.seats_max AS seats_max,
|
||||
e.state AS event_state,
|
||||
@@ -72,10 +72,8 @@ class report_event_registration(models.Model):
|
||||
|
||||
GROUP BY
|
||||
event_id,
|
||||
user_id_registration,
|
||||
r.id,
|
||||
registration_state,
|
||||
r.nb_register,
|
||||
event_type,
|
||||
e.id,
|
||||
e.date_begin,
|
||||
|
||||
@@ -20,7 +20,7 @@ class TestEventFlow(TestEventCommon):
|
||||
'name': 'TestEvent',
|
||||
'date_begin': datetime.datetime.now() + relativedelta(days=-1),
|
||||
'date_end': datetime.datetime.now() + relativedelta(days=1),
|
||||
'seats_max': 10,
|
||||
'seats_max': 2,
|
||||
})
|
||||
self.assertEqual(test_event.state, 'confirm', 'Event: auto_confirmation of event failed')
|
||||
|
||||
@@ -28,33 +28,30 @@ class TestEventFlow(TestEventCommon):
|
||||
test_reg1 = self.Registration.sudo(self.user_eventuser).create({
|
||||
'name': 'TestReg1',
|
||||
'event_id': test_event.id,
|
||||
'nb_register': 7,
|
||||
})
|
||||
self.assertEqual(test_reg1.state, 'open', 'Event: auto_confirmation of registration failed')
|
||||
self.assertEqual(test_event.seats_reserved, 7, 'Event: wrong number of reserved seats after confirmed registration')
|
||||
self.assertEqual(test_event.seats_reserved, 1, 'Event: wrong number of reserved seats after confirmed registration')
|
||||
test_reg2 = self.Registration.sudo(self.user_eventuser).create({
|
||||
'name': 'TestReg2',
|
||||
'event_id': test_event.id,
|
||||
'nb_register': 3,
|
||||
})
|
||||
self.assertEqual(test_reg2.state, 'open', 'Event: auto_confirmation of registration failed')
|
||||
self.assertEqual(test_event.seats_reserved, 10, 'Event: wrong number of reserved seats after confirmed registration')
|
||||
self.assertEqual(test_event.seats_reserved, 2, 'Event: wrong number of reserved seats after confirmed registration')
|
||||
|
||||
# EventUser create registrations for this event: too much registrations
|
||||
with self.assertRaises(ValidationError):
|
||||
self.Registration.sudo(self.user_eventuser).create({
|
||||
'name': 'TestReg3',
|
||||
'event_id': test_event.id,
|
||||
'nb_register': 1,
|
||||
})
|
||||
|
||||
# EventUser validates registrations
|
||||
test_reg1.button_reg_close()
|
||||
self.assertEqual(test_reg1.state, 'done', 'Event: wrong state of attended registration')
|
||||
self.assertEqual(test_event.seats_used, 7, 'Event: incorrect number of attendees after closing registration')
|
||||
self.assertEqual(test_event.seats_used, 1, 'Event: incorrect number of attendees after closing registration')
|
||||
test_reg2.button_reg_close()
|
||||
self.assertEqual(test_reg1.state, 'done', 'Event: wrong state of attended registration')
|
||||
self.assertEqual(test_event.seats_used, 10, 'Event: incorrect number of attendees after closing registration')
|
||||
self.assertEqual(test_event.seats_used, 2, 'Event: incorrect number of attendees after closing registration')
|
||||
|
||||
# EventUser closes the event
|
||||
test_event.button_done()
|
||||
@@ -63,6 +60,7 @@ class TestEventFlow(TestEventCommon):
|
||||
with self.assertRaises(Warning):
|
||||
test_event.button_cancel()
|
||||
|
||||
|
||||
@mute_logger('openerp.addons.base.ir.ir_model', 'openerp.models')
|
||||
def test_10_advanced_event_flow(self):
|
||||
""" Avanced event flow: no auto confirmation, manage minimum / maximum
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<openerp>
|
||||
<data>
|
||||
<template id="report_registration_badge">
|
||||
<t t-call="report.html_container">
|
||||
<t t-foreach="docs" t-as="o">
|
||||
<div class="page">
|
||||
<div class="row" style="border-bottom:1px dashed black;">
|
||||
<div class="col-xs-6 text-center" style="padding-right:7mm; border-right:1px dashed black; height: 147mm; padding-top: 7mm">
|
||||
<div class="row">
|
||||
<div class="col-xs-12">
|
||||
<span t-if="o.event_id.organizer_id.company_id.logo_web and o.event_id.organizer_id.is_company">
|
||||
<img t-att-src="'data:image/png;base64,%s' % o.event_id.organizer_id.company_id.logo_web" style="max-height:1cm; max-width:4cm;"/>
|
||||
</span>
|
||||
<h4 t-field="o.event_id.name"/>
|
||||
<h5>( <i class="fa fa-clock-o"></i> <span itemprop="startDate" t-field="o.event_id.date_begin" t-field-options='{"hide_seconds":"True"}'> </span> <i>to</i> <span itemprop="endDate" t-field="o.event_id.date_end" t-field-options='{"hide_seconds":"True"}'> </span> )</h5>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-xs-12 text-center">
|
||||
<small><h3 t-if="o.partner_id.is_company == True" t-field="o.partner_id.name"/><h5 t-field="o.name"/></small>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-xs-12 mt32 text-center" style="background: lightgrey;">
|
||||
<h3>FREE TICKET</h3>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Inner right -->
|
||||
<div class="col-xs-6 text-center" style="padding-left:7mm; height: 149mm; padding-top: 2mm;">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</t>
|
||||
</t>
|
||||
</template>
|
||||
</data>
|
||||
</openerp>
|
||||
@@ -1,3 +1,4 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import models
|
||||
import wizard
|
||||
|
||||
@@ -25,9 +25,9 @@ this event.
|
||||
'views/product.xml',
|
||||
'views/sale_order.xml',
|
||||
'event_sale_data.xml',
|
||||
'event_sale_report.xml',
|
||||
'views/report_registrationbadge.xml',
|
||||
'security/ir.model.access.csv',
|
||||
'wizard/event_edit_registration.xml',
|
||||
],
|
||||
'demo': ['event_demo.xml'],
|
||||
'test': ['test/confirm.yml'],
|
||||
|
||||
@@ -78,7 +78,7 @@ class event_ticket(models.Model):
|
||||
seats_used = fields.Integer(compute='_compute_seats', store=True)
|
||||
|
||||
@api.multi
|
||||
@api.depends('seats_max', 'registration_ids.state', 'registration_ids.nb_register')
|
||||
@api.depends('seats_max', 'registration_ids.state')
|
||||
def _compute_seats(self):
|
||||
""" Determine reserved, available, reserved but unconfirmed and used seats. """
|
||||
# initialize fields to 0
|
||||
@@ -91,7 +91,7 @@ class event_ticket(models.Model):
|
||||
'open': 'seats_reserved',
|
||||
'done': 'seats_used',
|
||||
}
|
||||
query = """ SELECT event_ticket_id, state, sum(nb_register)
|
||||
query = """ SELECT event_ticket_id, state, count(event_id)
|
||||
FROM event_registration
|
||||
WHERE event_ticket_id IN %s AND state IN ('draft', 'open', 'done')
|
||||
GROUP BY event_ticket_id, state
|
||||
@@ -121,9 +121,30 @@ class event_registration(models.Model):
|
||||
_inherit = 'event.registration'
|
||||
|
||||
event_ticket_id = fields.Many2one('event.event.ticket', 'Event Ticket')
|
||||
# sale_order_line_id = fields.Many2one('sale.order.line', 'Sale Order Line', ondelete='cascade')
|
||||
|
||||
@api.one
|
||||
@api.constrains('event_ticket_id', 'nb_register', 'state')
|
||||
@api.constrains('event_ticket_id', 'state')
|
||||
def _check_ticket_seats_limit(self):
|
||||
if self.event_ticket_id.seats_max and self.event_ticket_id.seats_available < 0:
|
||||
raise Warning('No more available seats for this ticket')
|
||||
|
||||
@api.one
|
||||
def _check_auto_confirmation(self):
|
||||
res = super(event_registration, self)._check_auto_confirmation()[0]
|
||||
if res and self.origin:
|
||||
orders = self.env['sale.order'].search([('name', '=', self.origin)], limit=1)
|
||||
if orders and orders[0].state == 'draft':
|
||||
res = False
|
||||
return res
|
||||
|
||||
@api.model
|
||||
def create(self, vals):
|
||||
res = super(event_registration, self).create(vals)
|
||||
if res.origin:
|
||||
message = _("The registration has been created for event %(event_name)s%(ticket)s from sale order %(order)s") % ({
|
||||
'event_name': '<i>%s</i>' % res.event_id.name,
|
||||
'ticket': res.event_ticket_id and _(' with ticket %s') % (('<i>%s</i>') % res.event_ticket_id.name) or '',
|
||||
'order': res.origin})
|
||||
res.message_post(body=message)
|
||||
return res
|
||||
|
||||
@@ -1,7 +1,21 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from openerp import api
|
||||
from openerp.osv import fields, osv
|
||||
from openerp.tools.translate import _
|
||||
|
||||
|
||||
class sale_order(osv.osv):
|
||||
_inherit = "sale.order"
|
||||
|
||||
def action_button_confirm(self, cr, uid, ids, context=None):
|
||||
# TDE note: This method works on a list of one id (see sale/sale.py) so working on ids[0] seems safe.
|
||||
res = super(sale_order, self).action_button_confirm(cr, uid, ids, context=context)
|
||||
redirect_to_event_registration = any(line.event_id for order in self.browse(cr, uid, ids, context=context) for line in order.order_line)
|
||||
if redirect_to_event_registration:
|
||||
event_ctx = dict(context, default_sale_order_id=ids[0])
|
||||
return self.pool.get('ir.actions.act_window').for_xml_id(cr, uid, 'event_sale', 'action_sale_order_event_registration', event_ctx)
|
||||
else:
|
||||
return res
|
||||
|
||||
|
||||
class sale_order_line(osv.osv):
|
||||
@@ -35,34 +49,36 @@ class sale_order_line(osv.osv):
|
||||
event_ok=False)
|
||||
return res
|
||||
|
||||
@api.multi
|
||||
def _update_registrations(self):
|
||||
""" Create or update registrations linked to a sale order line. A sale
|
||||
order line has a product_uom_qty attribute that will be the number of
|
||||
registrations linked to this line. This method update existing registrations
|
||||
and create new one for missing one. """
|
||||
registrations = self.env['event.registration'].search([('origin', 'in', list(set([so.name for line in self for so in line.order_id if line.event_id])))])
|
||||
for so_line in [l for l in self if l.event_id]:
|
||||
existing_registrations = [r for r in registrations if r.event_id == so_line.event_id and r.origin == so_line.order_id.name]
|
||||
for registration in existing_registrations:
|
||||
registration.write({'state': 'open'})
|
||||
|
||||
for count in range(int(so_line.product_uom_qty) - len(existing_registrations)):
|
||||
self.env['event.registration'].create({
|
||||
'event_id': so_line.event_id.id,
|
||||
'event_ticket_id': so_line.event_ticket_id.id,
|
||||
'partner_id': so_line.order_id.partner_id.id,
|
||||
'origin': so_line.order_id.name,
|
||||
})
|
||||
return True
|
||||
|
||||
def button_confirm(self, cr, uid, ids, context=None):
|
||||
""" Override confirmation of the sale order line in order to create
|
||||
or update the possible event registrations linked to the sale. """
|
||||
'''
|
||||
create registration with sales order
|
||||
'''
|
||||
context = dict(context or {})
|
||||
registration_obj = self.pool.get('event.registration')
|
||||
for order_line in self.browse(cr, uid, ids, context=context):
|
||||
if order_line.event_id:
|
||||
dic = {
|
||||
'name': order_line.order_id.partner_invoice_id.name,
|
||||
'partner_id': order_line.order_id.partner_id.id,
|
||||
'nb_register': int(order_line.product_uom_qty),
|
||||
'email': order_line.order_id.partner_id.email,
|
||||
'phone': order_line.order_id.partner_id.phone,
|
||||
'origin': order_line.order_id.name,
|
||||
'event_id': order_line.event_id.id,
|
||||
'event_ticket_id': order_line.event_ticket_id and order_line.event_ticket_id.id or None,
|
||||
}
|
||||
|
||||
if order_line.event_ticket_id:
|
||||
message = _("The registration has been created for event <i>%s</i> with the ticket <i>%s</i> from the Sale Order %s. ") % (order_line.event_id.name, order_line.event_ticket_id.name, order_line.order_id.name)
|
||||
else:
|
||||
message = _("The registration has been created for event <i>%s</i> from the Sale Order %s. ") % (order_line.event_id.name, order_line.order_id.name)
|
||||
|
||||
context.update({'mail_create_nolog': True})
|
||||
registration_id = registration_obj.create(cr, uid, dic, context=context)
|
||||
registration_obj.message_post(cr, uid, [registration_id], body=message, context=context)
|
||||
return super(sale_order_line, self).button_confirm(cr, uid, ids, context=context)
|
||||
res = super(sale_order_line, self).button_confirm(cr, uid, ids, context=context)
|
||||
self._update_registrations(cr, uid, ids, context=context)
|
||||
return res
|
||||
|
||||
def onchange_event_ticket_id(self, cr, uid, ids, event_ticket_id=False, context=None):
|
||||
price = event_ticket_id and self.pool["event.event.ticket"].browse(cr, uid, event_ticket_id, context=context).price or False
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
|
||||
access_event_event_ticket_user,event.event.ticket.user,event_sale.model_event_event_ticket,event.group_event_user,1,0,0,0
|
||||
access_event_event_ticket_admin,event.event.ticket.admin,event_sale.model_event_event_ticket,event.group_event_manager,1,1,1,1
|
||||
access_event_event_ticket_admin,event.event.ticket.admin,event_sale.model_event_event_ticket,event.group_event_manager,1,1,1,1
|
||||
|
||||
|
@@ -34,21 +34,27 @@
|
||||
name: sale order line
|
||||
event_id: event
|
||||
-
|
||||
I confirm the sale order
|
||||
In the event registration I add some attendee detail lines. i choose event product
|
||||
-
|
||||
!workflow {model: sale.order, ref: order1, action: order_confirm}
|
||||
!record {model: registration.editor, id: attendee_detail}:
|
||||
sale_order_id: order1
|
||||
event_registration_ids:
|
||||
- event_id: event
|
||||
name: 'Administrator'
|
||||
email: 'abc@example.com'
|
||||
-
|
||||
I check if the sale order is confirmed
|
||||
I click apply to create attendees
|
||||
-
|
||||
!assert {model: sale.order, id: order1}:
|
||||
- state == 'manual'
|
||||
!python {model: registration.editor}: |
|
||||
self.action_make_registration(cr, uid, ref('attendee_detail'))
|
||||
-
|
||||
I check if a registration is created
|
||||
-
|
||||
!python {model: event.registration}: |
|
||||
from openerp.osv import osv
|
||||
from openerp.tools.translate import _
|
||||
order_id = ref('order1')
|
||||
order = self.pool.get('sale.order').browse(cr, uid,order_id)
|
||||
registration_ids = self.search(cr,uid,[('origin','=',order.name)])
|
||||
registration_ids = self.search(cr,uid,[('origin', '=', order.name)])
|
||||
if registration_ids == []:
|
||||
raise osv.except_osv(_('Error!'),_("The registration is not created."))
|
||||
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
<field name="arch" type="xml">
|
||||
<field name="event_id" position="after">
|
||||
<field name="event_ticket_id"/>
|
||||
<field name="origin"/>
|
||||
</field>
|
||||
</field>
|
||||
</record>
|
||||
@@ -29,8 +30,9 @@
|
||||
<field name="model">event.registration</field>
|
||||
<field name="inherit_id" ref="event.view_event_registration_form" />
|
||||
<field name="arch" type="xml">
|
||||
<field name="user_id" position="after">
|
||||
<field name="event_id" position="after">
|
||||
<field name="event_ticket_id" domain="[('event_id', '=', event_id)]"/>
|
||||
<field name="origin"/>
|
||||
</field>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
@@ -1,52 +1,39 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<openerp>
|
||||
<data>
|
||||
<template id="report_registrationbadge">
|
||||
<template id="event.report_registration_badge">
|
||||
<t t-call="report.html_container">
|
||||
<t t-foreach="docs" t-as="o">
|
||||
<div class="page">
|
||||
<div class="row">
|
||||
<!-- Inner right -->
|
||||
<div class="col-xs-6 text-center" style="padding-left:7mm; border-left:1px dashed black; height: 149mm; -webkit-transform:rotate(180deg); padding-top: 7mm">
|
||||
<div>
|
||||
<div class="row">
|
||||
<div class="col-xs-12">
|
||||
<div>
|
||||
<span t-if="o.event_id.organizer_id.company_id.logo_web and o.event_id.organizer_id.is_company">
|
||||
<img t-att-src="'data:image/png;base64,%s' % o.event_id.organizer_id.company_id.logo_web" style="max-height:1cm; max-width:4cm;"/>
|
||||
</span>
|
||||
<div>
|
||||
<h5 t-field="o.event_id.name"/>
|
||||
</div>
|
||||
<div>
|
||||
<h5>June 4th - 6th , 2014</h5>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-xs-12">
|
||||
<span t-if="o.event_id.organizer_id.company_id.logo_web and o.event_id.organizer_id.is_company">
|
||||
<img t-att-src="'data:image/png;base64,%s' % o.event_id.organizer_id.company_id.logo_web" style="max-height:1cm; max-width:4cm;"/>
|
||||
</span>
|
||||
<h5 t-field="o.event_id.name"/>
|
||||
<h5>( <i class="fa fa-clock-o"></i> <span itemprop="startDate" t-field="o.event_id.date_begin" t-field-options='{"hide_seconds":"True"}'> </span> <i>to</i> <span itemprop="endDate" t-field="o.event_id.date_end" t-field-options='{"hide_seconds":"True"}'> </span> )</h5>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-xs-12 text-center">
|
||||
<t t-if="o.event_ticket_id">
|
||||
<div>
|
||||
<strong><span t-field="o.event_ticket_id"/></strong>
|
||||
</div>
|
||||
<div class="col-xs-12 text-center">
|
||||
<t t-if="o.event_ticket_id">
|
||||
<div>
|
||||
<strong><span t-field="o.event_ticket_id"/></strong>
|
||||
</div>
|
||||
</t>
|
||||
<div>
|
||||
<t t-if="o.partner_id.is_company == True">
|
||||
<small>
|
||||
<h3 t-field="o.partner_id.name"/>
|
||||
<h5 t-field="o.name"/>
|
||||
</small>
|
||||
</t>
|
||||
<div>
|
||||
<t t-if="o.partner_id.is_company == True">
|
||||
<small>
|
||||
<h3 t-field="o.partner_id.name"/>
|
||||
<h5 t-field="o.name"/>
|
||||
</small>
|
||||
</t>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<br/>
|
||||
<div class="row">
|
||||
<div class="col-xs-12 mt32 text-center" style="background: lightgrey;">
|
||||
<h3>Status</h3>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-xs-12 mt32 text-center" style="background: lightgrey;">
|
||||
<h3>Status</h3>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
import event_edit_registration
|
||||
@@ -0,0 +1,75 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from openerp import models, fields, api
|
||||
|
||||
|
||||
class SaleOrderEventRegistration(models.TransientModel):
|
||||
_name = "registration.editor"
|
||||
|
||||
sale_order_id = fields.Many2one('sale.order', 'Sale Order', required=True)
|
||||
event_registration_ids = fields.One2many('registration.editor.line', 'editor_id', string='Registrations to Edit')
|
||||
|
||||
@api.model
|
||||
def default_get(self, fields):
|
||||
res = super(SaleOrderEventRegistration, self).default_get(fields)
|
||||
if not res.get('sale_order_id'):
|
||||
sale_order_id = res.get('sale_order_id', self._context.get('active_id'))
|
||||
res['sale_order_id'] = sale_order_id
|
||||
sale_order = self.env['sale.order'].browse(res.get('sale_order_id'))
|
||||
registrations = self.env['event.registration'].search([('origin', '=', sale_order.name)])
|
||||
|
||||
attendee_list = []
|
||||
for so_line in [l for l in sale_order.order_line if l.event_id]:
|
||||
existing_registrations = [r for r in registrations if r.event_id == so_line.event_id]
|
||||
for reg in existing_registrations:
|
||||
attendee_list.append({
|
||||
'event_id': reg.event_id.id,
|
||||
'event_ticket_id': reg.event_ticket_id.id,
|
||||
'registration_id': reg.id,
|
||||
'name': reg.name,
|
||||
'email': reg.email,
|
||||
'phone': reg.phone,
|
||||
})
|
||||
for count in range(int(so_line.product_uom_qty) - len(existing_registrations)):
|
||||
attendee_list.append({
|
||||
'event_id': so_line.event_id.id,
|
||||
'event_ticket_id': so_line.event_ticket_id.id,
|
||||
})
|
||||
res['event_registration_ids'] = attendee_list
|
||||
return res
|
||||
|
||||
@api.multi
|
||||
def action_make_registration(self):
|
||||
Registration = self.env['event.registration']
|
||||
for wizard in self:
|
||||
for wiz_registration in wizard.event_registration_ids:
|
||||
if wiz_registration.registration_id:
|
||||
wiz_registration.registration_id.write(wiz_registration.get_registration_data()[0])
|
||||
else:
|
||||
Registration.create(wiz_registration.get_registration_data()[0])
|
||||
return {'type': 'ir.actions.act_window_close'}
|
||||
|
||||
|
||||
class RegistrationEditorLine(models.TransientModel):
|
||||
"""Event Registration"""
|
||||
_name = "registration.editor.line"
|
||||
|
||||
editor_id = fields.Many2one('registration.editor')
|
||||
event_id = fields.Many2one('event.event', string='Event', required=True)
|
||||
registration_id = fields.Many2one('event.registration', 'Original Registration')
|
||||
event_ticket_id = fields.Many2one('event.event.ticket', string='Event Ticket')
|
||||
email = fields.Char(string='Email')
|
||||
phone = fields.Char(string='Phone')
|
||||
name = fields.Char(string='Name', select=True)
|
||||
|
||||
@api.one
|
||||
def get_registration_data(self):
|
||||
return {
|
||||
'event_id': self.event_id.id,
|
||||
'event_ticket_id': self.event_ticket_id.id,
|
||||
'partner_id': self.editor_id.sale_order_id.partner_id.id,
|
||||
'name': self.name or self.editor_id.sale_order_id.partner_id.name,
|
||||
'phone': self.phone or self.editor_id.sale_order_id.partner_id.phone,
|
||||
'email': self.email or self.editor_id.sale_order_id.partner_id.email,
|
||||
'origin': self.editor_id.sale_order_id.name,
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<openerp>
|
||||
<data>
|
||||
|
||||
<record id="view_event_registration_editor_form" model="ir.ui.view">
|
||||
<field name="name">registration.editor.form</field>
|
||||
<field name="model">registration.editor</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Registration">
|
||||
<p>Before confirming <field name="sale_order_id" readonly="1" class="oe_inline"/>
|
||||
please give details about the registrations</p>
|
||||
<field name="event_registration_ids">
|
||||
<tree string="Registration" editable="top" create="false" delete="false">
|
||||
<field name="event_id" readonly='1'/>
|
||||
<field name="registration_id" readonly='1'/>
|
||||
<field name="event_ticket_id" domain="[('event_id', '=', event_id)]" readonly='1'/>
|
||||
<field name="name"/>
|
||||
<field name="email"/>
|
||||
<field name="phone"/>
|
||||
</tree>
|
||||
</field>
|
||||
<footer>
|
||||
<button string="Apply" name="action_make_registration" type="object" class="oe_highlight"/>
|
||||
or
|
||||
<button string="Cancel" class="oe_link" special="cancel"/>
|
||||
</footer>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="action_sale_order_event_registration" model="ir.actions.act_window">
|
||||
<field name="name">Event Registrations</field>
|
||||
<field name="type">ir.actions.act_window</field>
|
||||
<field name="res_model">registration.editor</field>
|
||||
<field name="view_type">form</field>
|
||||
<field name="view_mode">form</field>
|
||||
<field name="view_id" ref="view_event_registration_editor_form"/>
|
||||
<field name="target">new</field>
|
||||
<field name="context">{'default_sale_order_id': context.get('default_sale_order_id')}</field>
|
||||
</record>
|
||||
|
||||
</data>
|
||||
</openerp>
|
||||
@@ -83,4 +83,4 @@ class TransferPaymentTransaction(osv.Model):
|
||||
|
||||
def _transfer_form_validate(self, cr, uid, tx, data, context=None):
|
||||
_logger.info('Validated transfer payment for tx %s: set as pending' % (tx.reference))
|
||||
return tx.write({'state': 'pending'})
|
||||
return tx.write({'state': 'done'})
|
||||
|
||||
@@ -4,9 +4,10 @@ import time
|
||||
import werkzeug.urls
|
||||
from datetime import datetime, timedelta
|
||||
from dateutil.relativedelta import relativedelta
|
||||
from collections import OrderedDict
|
||||
|
||||
from openerp import http
|
||||
from openerp import tools
|
||||
from openerp import tools, SUPERUSER_ID
|
||||
from openerp.addons.website.models.website import slug
|
||||
from openerp.http import request
|
||||
from openerp.tools.translate import _
|
||||
@@ -195,6 +196,7 @@ class website_event(http.Controller):
|
||||
'name': event_name,
|
||||
'date_begin': date_begin.strftime('%Y-%m-%d'),
|
||||
'date_end': (date_begin + timedelta(days=(1))).strftime('%Y-%m-%d'),
|
||||
'seats_available': 1000,
|
||||
}
|
||||
event_id = Event.create(request.cr, request.uid, vals, context=context)
|
||||
event = Event.browse(request.cr, request.uid, event_id, context=context)
|
||||
@@ -225,3 +227,48 @@ class website_event(http.Controller):
|
||||
"event": event,
|
||||
"url": event.website_url})
|
||||
return request.website.render("website_event.country_events_list", result)
|
||||
|
||||
def _process_tickets_details(self, data):
|
||||
nb_register = int(data.get('nb_register-0', 0))
|
||||
if nb_register:
|
||||
return [{'id': 0, 'name': 'Subscription', 'quantity': nb_register, 'price': 0}]
|
||||
return []
|
||||
|
||||
@http.route(['/event/<model("event.event"):event>/registration/new'], type='json', auth="public", methods=['POST'], website=True)
|
||||
def registration_new(self, event, **post):
|
||||
tickets = self._process_tickets_details(post)
|
||||
if not tickets:
|
||||
return request.redirect("/event/%s" % slug(event))
|
||||
return request.website._render("website_event.registration_attendee_details", {'tickets': tickets, 'event': event})
|
||||
|
||||
def _process_registration_details(self, details):
|
||||
''' Process data posted from the attendee details form. '''
|
||||
registrations = {}
|
||||
for key, value in details.iteritems():
|
||||
counter, field_name = key.split('-', 1)
|
||||
registrations.setdefault(counter, dict())[field_name] = value
|
||||
return registrations.values()
|
||||
|
||||
@http.route(['/event/<model("event.event"):event>/registration/confirm'], type='http', auth="public", methods=['POST'], website=True)
|
||||
def registration_confirm(self, event, **post):
|
||||
cr, uid, context = request.cr, request.uid, request.context
|
||||
Registration = request.registry['event.registration']
|
||||
registrations = self._process_registration_details(post)
|
||||
|
||||
registration_ids = []
|
||||
user = request.registry.get('res.users').browse(cr, uid, uid, context=context)
|
||||
for registration in registrations:
|
||||
registration_ids.append(
|
||||
Registration.create(cr, SUPERUSER_ID, {
|
||||
'name': registration.get('name', user.name),
|
||||
'phone': registration.get('phone', user.phone),
|
||||
'email': registration.get('email', user.email),
|
||||
'partner_id': user.partner_id.id,
|
||||
'event_id': event.id,
|
||||
}, context=context))
|
||||
|
||||
attendees = Registration.browse(cr, uid, registration_ids, context=context)
|
||||
return request.website.render("website_event.registration_complete", {
|
||||
'attendees': attendees,
|
||||
'event': event,
|
||||
})
|
||||
|
||||
@@ -4,6 +4,7 @@ from openerp import models, fields, api, _
|
||||
|
||||
# from openerp.osv import osv, fields
|
||||
from openerp import SUPERUSER_ID
|
||||
from openerp.models import NewId
|
||||
|
||||
# from openerp.tools.translate import _
|
||||
import re
|
||||
@@ -31,11 +32,13 @@ class event(models.Model):
|
||||
@api.one
|
||||
@api.depends('name')
|
||||
def _website_url(self):
|
||||
self.website_url = "/event/" + slug(self)
|
||||
if isinstance(self.id, NewId):
|
||||
self.website_url = ''
|
||||
else:
|
||||
self.website_url = "/event/" + slug(self)
|
||||
|
||||
@api.one
|
||||
def _default_hashtag(self):
|
||||
self.twitter_hashtag = re.sub("[- \\.\\(\\)\\@\\#\\&]+", "", self.env.user.company_id.name).lower()
|
||||
return re.sub("[- \\.\\(\\)\\@\\#\\&]+", "", self.env.user.company_id.name).lower()
|
||||
|
||||
show_menu = fields.Boolean('Has Dedicated Menu', compute='_get_show_menu', inverse='_set_show_menu')
|
||||
menu_id = fields.Many2one('website.menu', 'Event Menu')
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
$(document).ready(function () {
|
||||
|
||||
// Catch registration form event, because of JS for attendee details
|
||||
$('#registration_form .a-submit')
|
||||
.off('click')
|
||||
.removeClass('a-submit')
|
||||
.click(function (ev) {
|
||||
ev.preventDefault();
|
||||
ev.stopPropagation();
|
||||
var $form = $(ev.currentTarget).closest('form');
|
||||
var post = {};
|
||||
$("select").each(function() {
|
||||
post[$(this)[0].name] = $(this).val();
|
||||
});
|
||||
openerp.jsonRpc($form.attr('action'), 'call', post).then(function (modal) {
|
||||
var $modal = $(modal);
|
||||
$modal.appendTo($form).modal()
|
||||
$modal.on('click', '.js_goto_event', function () {
|
||||
$modal.modal('hide');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -5,6 +5,7 @@
|
||||
<template id="event_script" inherit_id="website.assets_frontend" name="Country Events Snippet Script">
|
||||
<xpath expr="/t" position="inside">
|
||||
<script type="text/javascript" src="/website_event/static/src/js/website_geolocation.js"></script>
|
||||
<script type="text/javascript" src="/website_event/static/src/js/website_event.js"></script>
|
||||
</xpath>
|
||||
</template>
|
||||
|
||||
@@ -289,6 +290,9 @@
|
||||
<template id="event_description_full">
|
||||
<t t-call="website_event.event_details">
|
||||
<div class="col-md-8">
|
||||
<t t-call="website_event.registration_template"/>
|
||||
<div class="clearfix"/>
|
||||
<hr/>
|
||||
<div itemprop="description" t-field="event.description"></div>
|
||||
<div class="clearfix"/>
|
||||
<ul class="media-list" id="comment">
|
||||
@@ -367,6 +371,145 @@
|
||||
</t>
|
||||
</template>
|
||||
|
||||
<!-- Registration Templates -->
|
||||
<template id="registration_template">
|
||||
<form id="registration_form" t-attf-action="/event/#{slug(event)}/registration/new" method="post">
|
||||
<table itemprop="offers" class="table table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Ticket Type</th>
|
||||
<th style="min-width: 100px">End of Subscription</th>
|
||||
<th style="min-width: 100px">Price</th>
|
||||
<th></th>
|
||||
<th>Quantity</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>Event Subscription</td>
|
||||
<td>
|
||||
<t t-if="event.date_begin">
|
||||
<span t-field="event.date_begin"/>
|
||||
</t>
|
||||
<t t-if="not event.date_begin">
|
||||
<span>Unlimited</span>
|
||||
</t>
|
||||
</td>
|
||||
<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">
|
||||
<option t-esc="nb"/>
|
||||
</t>
|
||||
</select>
|
||||
<span t-if="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>
|
||||
</form>
|
||||
</template>
|
||||
|
||||
<template id="registration_attendee_details" name="Registration Attendee Details">
|
||||
<div id="modal_attendees_registration" class="modal fade" tabindex="-1" role="dialog">
|
||||
<div class="modal-dialog modal-lg">
|
||||
<form id="attendee_registration" t-attf-action="/event/#{slug(event)}/registration/confirm" method="post">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">x</button>
|
||||
<h4 class="modal-title" id="myModalLabel"><strong>Attendees</strong></h4>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="container">
|
||||
<t t-set="counter" t-value="0"/>
|
||||
<t t-foreach="tickets" t-as="ticket">
|
||||
<t t-foreach="range(1, ticket['quantity'] + 1)" t-as="att_counter">
|
||||
<t t-set="counter" t-value="counter + 1"/>
|
||||
<div>
|
||||
<h4><strong>Ticket #<t t-raw="counter"/>: <t t-esc="ticket['name']"/> (
|
||||
<t t-if="ticket['price'] > 0"><t t-esc="ticket['price']"/><t t-esc="website.pricelist_id.currency_id.symbol"/></t>
|
||||
<t t-if="ticket['price'] == 0">Free</t>
|
||||
)</strong></h4><hr/>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-md-4"><strong>Name</strong></div>
|
||||
<div class="col-md-4"><strong>Phone</strong></div>
|
||||
<div class="col-md-4"><strong>Email</strong></div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-md-4"><input class='form-control input-sm' type='text' t-attf-name="#{counter}-name"/></div>
|
||||
<div class="col-md-4"><input class='form-control input-sm' type='tel' t-attf-name="#{counter}-phone"/></div>
|
||||
<div class="col-md-4"><input class='form-control input-sm' type='email' t-attf-name="#{counter}-email"/></div>
|
||||
<input class='hidden' type='text' t-attf-name="#{counter}-ticket_id" t-attf-value="#{ticket['id']}"/>
|
||||
</div><hr/>
|
||||
</t>
|
||||
<t t-set="counter" t-value="counter + ticket['quantity']"/>
|
||||
</t>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<div class="pull-left">
|
||||
<button type="submit" class="btn btn-primary">Continue</button> or
|
||||
<a class="js_goto_event"> Cancel Registration</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template id="registration_complete" name="Registration Completed">
|
||||
<t t-call="website.layout">
|
||||
<div class="container">
|
||||
<h3>We are glad to confirm your subscription to our event</h3>
|
||||
<div class="row">
|
||||
<div class="col-md-3 mt16">
|
||||
<h4>Event Details</h4>
|
||||
</div>
|
||||
<div class="col-md-9 mt16">
|
||||
<h4><a t-attf-href="/event/#{slug(event)}"><t t-esc="event.name"/></a></h4>
|
||||
<i class="fa fa-clock-o"/> <span itemprop="startDate" t-field="event.date_begin" t-field-options='{"hide_seconds":"True"}'> </span> <i>to</i> <span itemprop="endDate" t-field="event.date_end" t-field-options='{"hide_seconds":"True"}'> </span>
|
||||
<div itemprop="location" class="mt16 mb8" t-field="event.address_id" t-field-options='{
|
||||
"widget": "contact",
|
||||
"fields": ["address", "phone", "mobile", "fax", "email"]
|
||||
}'/>
|
||||
</div>
|
||||
<div class="col-md-3 mt16">
|
||||
<h4>Your subscription</h4>
|
||||
</div>
|
||||
<div class="col-md-9 mt16">
|
||||
<table class='table table-bordered table-striped'>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Reference</th>
|
||||
<th>Name</th>
|
||||
<th>E-mail</th>
|
||||
<th>Phone</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<t t-foreach="attendees" t-as="attendee">
|
||||
<tr>
|
||||
<td><t t-esc="attendee.id"/></td>
|
||||
<td><i class="fa fa-user"></i> <t t-if='attendee.name'><t t-esc="attendee.name"/></t><t t-if='not attendee.name'>N/A</t></td>
|
||||
<td><i class="fa fa-envelope"></i> <t t-if='attendee.email'><t t-esc="attendee.email"/></t><t t-if='not attendee.email'>N/A</t></td>
|
||||
<td><i class="fa fa-phone"></i> <t t-if='attendee.phone'><t t-esc="attendee.phone"/></t><t t-if='not attendee.phone'>N/A</t></td>
|
||||
</tr>
|
||||
</t>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</t>
|
||||
</template>
|
||||
|
||||
<!-- Snippets -->
|
||||
<template id="country_events" name="Country Events" inherit_id="website.snippets">
|
||||
<xpath expr="//div[@id='snippet_content']" position="inside">
|
||||
<div>
|
||||
@@ -398,6 +541,7 @@
|
||||
</xpath>
|
||||
</template>
|
||||
|
||||
<!-- Misc templates -->
|
||||
<template id="country_events_list" name="Country">
|
||||
<t t-ignore="true">
|
||||
<t t-if="events">
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
{
|
||||
'name': "Online Event's Tickets",
|
||||
'category': 'Hidden',
|
||||
'category': 'Website',
|
||||
'summary': "Sell Your Event's Tickets",
|
||||
'website': 'https://www.odoo.com/page/events',
|
||||
'version': '1.0',
|
||||
|
||||
@@ -28,39 +28,51 @@ from openerp.tools.translate import _
|
||||
|
||||
class website_event(website_event):
|
||||
|
||||
@http.route(['/event/cart/update'], type='http', auth="public", methods=['POST'], website=True)
|
||||
def cart_update(self, event_id, **post):
|
||||
cr, uid, context = request.cr, request.uid, request.context
|
||||
ticket_obj = request.registry.get('event.event.ticket')
|
||||
|
||||
sale = False
|
||||
for key, value in post.items():
|
||||
quantity = int(value or "0")
|
||||
if not quantity:
|
||||
def _process_tickets_details(self, data):
|
||||
ticket_post = {}
|
||||
for key, value in data.iteritems():
|
||||
if not key.startswith('nb_register') or not '-' in key:
|
||||
continue
|
||||
sale = True
|
||||
ticket_id = key.split("-")[0] == 'ticket' and int(key.split("-")[1]) or None
|
||||
ticket = ticket_obj.browse(cr, SUPERUSER_ID, ticket_id, context=context)
|
||||
order = request.website.sale_get_order(force_create=1)
|
||||
order.with_context(event_ticket_id=ticket.id)._cart_update(product_id=ticket.product_id.id, add_qty=quantity)
|
||||
items = key.split('-')
|
||||
if len(items) < 2:
|
||||
continue
|
||||
ticket_post[int(items[1])] = int(value)
|
||||
tickets = request.registry['event.event.ticket'].browse(request.cr, request.uid, ticket_post.keys(), request.context)
|
||||
return [{'id': ticket.id, 'name': ticket.name, 'quantity': ticket_post[ticket.id], 'price': ticket.price} for ticket in tickets if ticket_post[ticket.id]]
|
||||
|
||||
@http.route(['/event/<model("event.event"):event>/registration/confirm'], type='http', auth="public", methods=['POST'], website=True)
|
||||
def registration_confirm(self, event, **post):
|
||||
cr, uid, context = request.cr, request.uid, request.context
|
||||
order = request.website.sale_get_order(force_create=1)
|
||||
|
||||
registrations = self._process_registration_details(post)
|
||||
registration_ctx = dict(context, registration_force_draft=True)
|
||||
for registration in registrations:
|
||||
ticket = request.registry['event.event.ticket'].browse(cr, SUPERUSER_ID, int(registration['ticket_id']), context=context)
|
||||
order.with_context(event_ticket_id=ticket.id)._cart_update(product_id=ticket.product_id.id, add_qty=1)
|
||||
|
||||
request.registry['event.registration'].create(cr, SUPERUSER_ID, {
|
||||
'name': registration.get('name'),
|
||||
'phone': registration.get('phone'),
|
||||
'email': registration.get('email'),
|
||||
'event_ticket_id': int(registration['ticket_id']),
|
||||
'partner_id': order.partner_id.id,
|
||||
'event_id': event.id,
|
||||
'origin': order.name,
|
||||
}, context=registration_ctx)
|
||||
|
||||
if not sale:
|
||||
return request.redirect("/event/%s" % event_id)
|
||||
return request.redirect("/shop/checkout")
|
||||
|
||||
def _add_event(self, event_name="New Event", context={}, **kwargs):
|
||||
try:
|
||||
dummy, res_id = request.registry.get('ir.model.data').get_object_reference(request.cr, request.uid, 'event_sale', 'product_product_event')
|
||||
context['default_event_ticket_ids'] = [[0,0,{
|
||||
context['default_event_ticket_ids'] = [[0, 0, {
|
||||
'name': _('Subscription'),
|
||||
'product_id': res_id,
|
||||
'deadline' : False,
|
||||
'deadline': False,
|
||||
'seats_max': 1000,
|
||||
'price': 0,
|
||||
}]]
|
||||
except ValueError:
|
||||
pass
|
||||
return super(website_event, self)._add_event(event_name, context, **kwargs)
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -13,24 +13,40 @@
|
||||
},
|
||||
{
|
||||
waitNot: 'a[href*="/event"]:contains("Conference on Business Apps")',
|
||||
title: "select 2 Standard tickets",
|
||||
title: "select 1 Standard ticket",
|
||||
element: 'select:eq(0)',
|
||||
sampleText: '1',
|
||||
},
|
||||
{
|
||||
title: "select 2 VIP tickets",
|
||||
waitFor: 'select:eq(0) option:contains(1):selected',
|
||||
element: 'select:eq(1)',
|
||||
sampleText: '2',
|
||||
},
|
||||
{
|
||||
title: "select 3 VIP tickets",
|
||||
waitFor: 'select:eq(0) option:contains(2):selected',
|
||||
element: 'select:eq(1)',
|
||||
sampleText: '3',
|
||||
},
|
||||
{
|
||||
title: "Order Now",
|
||||
waitFor: 'select:eq(1) option:contains(3):selected',
|
||||
waitFor: 'select:eq(1) option:contains(2):selected',
|
||||
element: '.btn-primary:contains("Order Now")',
|
||||
},
|
||||
{
|
||||
title: "Add the details of attendees",
|
||||
waitFor: 'form[id="attendee_registration"] .btn:contains("Continue")',
|
||||
autoComplete: function (tour) {
|
||||
$("input[name='1-name']").val("Att1");
|
||||
$("input[name='1-phone']").val("111 111");
|
||||
$("input[name='1-email']").val("att1@example.com");
|
||||
$("input[name='1-name']").val("Att2");
|
||||
$("input[name='1-phone']").val("222 222");
|
||||
$("input[name='1-email']").val("att2@example.com");
|
||||
},
|
||||
},
|
||||
{
|
||||
title: "click in modal on 'Continue' button",
|
||||
element: '.modal button:contains("Continue")',
|
||||
},
|
||||
{
|
||||
title: "Check the cart",
|
||||
element: '#top_menu .my_cart_quantity:contains(5)'
|
||||
element: '#top_menu .my_cart_quantity:contains(3)'
|
||||
},
|
||||
{
|
||||
title: "Check if the cart have 2 order lines and add one VIP ticket",
|
||||
@@ -39,7 +55,7 @@
|
||||
},
|
||||
{
|
||||
title: "Process Checkout",
|
||||
waitFor: '#top_menu .my_cart_quantity:contains(6)',
|
||||
waitFor: '#top_menu .my_cart_quantity:contains(4)',
|
||||
element: '.btn-primary:contains("Process Checkout")'
|
||||
},
|
||||
{
|
||||
|
||||
@@ -25,57 +25,61 @@
|
||||
</xpath>
|
||||
</template>
|
||||
|
||||
<template id="event_description_full" inherit_id="website_event.event_description_full" customize_show="True" name="Event's Ticket form">
|
||||
<xpath expr="//div[@t-field='event.description']" position="before">
|
||||
<form t-attf-action="/event/cart/update?event_id=#{ event.id }" method="post" t-if="event.event_ticket_ids">
|
||||
<table itemprop="offers" class="table table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Ticket Type</th>
|
||||
<th style="min-width: 100px">Sales End</th>
|
||||
<th style="min-width: 100px">Price</th>
|
||||
<th></th>
|
||||
<th>Quantity</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<t t-foreach="event.event_ticket_ids" t-as="ticket">
|
||||
<tr itemscope="itemscope" itemtype="http://data-vocabulary.org/Offer" t-if="not ticket.is_expired">
|
||||
<td itemscope="itemscope" itemtype="http://data-vocabulary.org/Product">
|
||||
<div itemprop="name" t-field="ticket.name"/>
|
||||
<div><small itemprop="description" t-field="ticket.product_id.description_sale"/></div>
|
||||
</td>
|
||||
<td><span itemprop="priceValidUntil" t-field="ticket.deadline"/></td>
|
||||
<td>
|
||||
<t t-if="ticket.price or editable"><span t-field="ticket.price" t-field-options='{
|
||||
"widget": "monetary",
|
||||
"display_currency": "website.pricelist_id.currency_id"
|
||||
}'/>
|
||||
<span itemprop="price" style="display:none;" t-esc="ticket.price"/>
|
||||
<span itemprop="priceCurrency" style="display:none;" t-esc="website.pricelist_id.currency_id.name"/>
|
||||
<template id="registration_template" inherit_id="website_event.registration_template" customize_show="True" name="Event's Ticket form">
|
||||
<xpath expr="//tbody" position="replace">
|
||||
<tbody>
|
||||
<t t-foreach="event.event_ticket_ids" t-as="ticket">
|
||||
<tr itemscope="itemscope" itemtype="http://data-vocabulary.org/Offer" t-if="not ticket.is_expired">
|
||||
<td itemscope="itemscope" itemtype="http://data-vocabulary.org/Product">
|
||||
<div itemprop="name" t-field="ticket.name"/>
|
||||
<div><small itemprop="description" t-field="ticket.product_id.description_sale"/></div>
|
||||
</td>
|
||||
<td>
|
||||
<t t-if="ticket.deadline">
|
||||
<span itemprop="priceValidUntil" t-field="ticket.deadline"/>
|
||||
</t>
|
||||
<t t-if="not ticket.deadline">
|
||||
<span>Unlimited</span>
|
||||
</t>
|
||||
</td>
|
||||
<td>
|
||||
<t t-if="ticket.price or editable"><span t-field="ticket.price" t-field-options='{
|
||||
"widget": "monetary",
|
||||
"display_currency": "website.pricelist_id.currency_id"
|
||||
}'/>
|
||||
<span itemprop="price" style="display:none;" t-esc="ticket.price"/>
|
||||
<span itemprop="priceCurrency" style="display:none;" t-esc="website.pricelist_id.currency_id.name"/>
|
||||
</t>
|
||||
<t t-if="not ticket.price and not editable">
|
||||
<span>Free</span>
|
||||
</t>
|
||||
</td>
|
||||
<td>
|
||||
<span t-if="ticket.seats_max and ((ticket.seats_reserved or 0)*100 / ticket.seats_max)>75" class="text-muted">
|
||||
<t t-esc="ticket.seats_max - ticket.seats_reserved"/> <span>left</span>
|
||||
</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">
|
||||
<option t-esc="nb"/>
|
||||
</t>
|
||||
<t t-if="not ticket.price and not editable">
|
||||
<span>Free</span>
|
||||
</t>
|
||||
</td>
|
||||
<td>
|
||||
<span t-if="ticket.seats_max and ((ticket.seats_reserved or 0)*100 / ticket.seats_max)>75" class="text-muted">
|
||||
<t t-esc="ticket.seats_max - ticket.seats_reserved"/> <span>left</span>
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
<select t-if="ticket.seats_available" t-attf-name="ticket-#{ ticket.id }" class="form-control">
|
||||
<t t-foreach="range(0, 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>
|
||||
</td>
|
||||
</tr>
|
||||
</t>
|
||||
</tbody>
|
||||
</table>
|
||||
<button type="submit" class="btn btn-primary btn-lg pull-right" t-if="event.seats_available">Order Now</button>
|
||||
<div class="clearfix"/>
|
||||
<hr/>
|
||||
</select>
|
||||
<span t-if="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>
|
||||
<form t-if="not event.event_ticket_ids">
|
||||
<div class="alert alert-info">
|
||||
Event registration not yet started.
|
||||
<t t-if="uid">
|
||||
<i class="fa fa-plus-circle"><a t-attf-href="/web#id=#{event.id}&view_type=form&model=event.event"> <em>Configure Event Registration</em></a></i>
|
||||
</t>
|
||||
</div>
|
||||
</form>
|
||||
</xpath>
|
||||
</template>
|
||||
|
||||
Reference in New Issue
Block a user