[MERGE] event_booth_*: manage booths in Events

### Purpose

Allow the event manager to create and manage booths for an event.
They represent physical booths that can be rented (for free or not) at the
event by a sponsor or a partner to present their product for example. Those
booths are gathered under a booth category which is defined by the booth
size, the booth visibility, the rental period or even extra services that
can be offered (non-exhaustive list)

### Specifications

To ensure integration with other modules, it has been split into
six separate modules:

- event_booth:
It's the base module that introduce the new models event.booth,
event.booth.category and event.type.booth. The booth management can only
be done in backend through the dedicated booth views.

- event_booth_sale:
This module introduce a new model event.booth.registration that enable a
booth to be booked multiple time until a user finally pay the related sale
order. A booth sale can be made through the Sales app by using the custom
product configurator after selecting a booth product.

- website_event_booth:
This module adds the website capabilities to booths registration. A new
page is available on the related event and users can book one or more booths
by following the link.

- website_event_booth_sale:
This module adds the website_sale capabilities to booths registration
(e-commerce). When selecting the desired booths the users will add them into
their cart and they will be redirected to the e-commerce flow.

- website_event_booth_exhibitor:
This module adds the website_event_exhibitor capabilities by giving the
possibility to the event manager to create automatically a sponsor by booking
a booth belonging to a certain booth category.

- website_event_booth_sale_exhibitor:
This module is a bridge between website_event_booth_exhibitor module and
website_event_booth_sale module.

### Links

Task-2318655
Upgrade PR : odoo/upgrade#2528

closes odoo/odoo#71104

Signed-off-by: Thibault Delavallee (tde) <tde@openerp.com>
This commit is contained in:
Odoo's Mergebot
2021-09-02 16:14:36 +02:00
committed by GitHub
112 changed files with 3941 additions and 9 deletions
+25
View File
@@ -187,6 +187,7 @@ class EventEvent(models.Model):
date_end_located = fields.Char(string='End Date Located', compute='_compute_date_end_tz')
is_ongoing = fields.Boolean('Is Ongoing', compute='_compute_is_ongoing', search='_search_is_ongoing')
is_one_day = fields.Boolean(compute='_compute_field_is_one_day')
is_finished = fields.Boolean(compute='_compute_is_finished', search='_search_is_finished')
# Location and communication
address_id = fields.Many2one(
'res.partner', string='Venue', default=lambda self: self.env.company.partner_id.id,
@@ -342,6 +343,30 @@ class EventEvent(models.Model):
end_tz = fields.Datetime.context_timestamp(event, event.date_end)
event.is_one_day = (begin_tz.date() == end_tz.date())
@api.depends('date_end')
def _compute_is_finished(self):
for event in self:
if not event.date_end:
event.is_finished = False
continue
event = event._set_tz_context()
current_datetime = fields.Datetime.context_timestamp(event, fields.Datetime.now())
datetime_end = fields.Datetime.context_timestamp(event, event.date_end)
event.is_finished = datetime_end <= current_datetime
def _search_is_finished(self, operator, value):
if operator not in ['=', '!=']:
raise ValueError(_('This operator is not supported'))
if not isinstance(value, bool):
raise ValueError(_('Value should be True or False (not %s)'), value)
now = fields.Datetime.now()
if (operator == '=' and value) or (operator == '!=' and not value):
domain = [('date_end', '<=', now)]
else:
domain = [('date_end', '>', now)]
event_ids = self.env['event.event']._search(domain)
return [('id', 'in', event_ids)]
@api.depends('event_type_id')
def _compute_date_tz(self):
for event in self:
@@ -16,6 +16,7 @@ class ResConfigSettings(models.TransientModel):
module_website_event_questions = fields.Boolean("Registration Survey")
module_event_barcode = fields.Boolean("Barcode")
module_website_event_sale = fields.Boolean("Online Ticketing")
module_event_booth = fields.Boolean("Booth Management")
@api.onchange('module_website_event_track')
def _onchange_module_website_event_track(self):
@@ -61,6 +61,17 @@
</div>
</div>
</div>
<div class="mt-3">
<div class="o_setting_left_pane">
<field name="module_event_booth"/>
</div>
<div class="o_setting_right_pane">
<label string="Booth Management" for="module_event_booth"/>
<div class="text-muted">
Create Booths and manage their reservations
</div>
</div>
</div>
</div>
</div>
<h2>Registration</h2>
+4
View File
@@ -0,0 +1,4 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from . import models
+30
View File
@@ -0,0 +1,30 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
{
'name': "Events Booths",
'category': 'Marketing/Events',
'version': '1.0',
'summary': "Manage event booths",
'description': """
Create booths for your favorite event.
""",
'depends': ['event'],
'data': [
'security/ir.model.access.csv',
'views/event_booth_category_views.xml',
'views/event_type_booth_views.xml',
'views/event_booth_views.xml',
'views/event_type_views.xml',
'views/event_event_views.xml',
'views/event_menus.xml',
'data/event_booth_category_data.xml',
'data/mail_data.xml',
'data/mail_templates.xml',
],
'demo': [
'data/event_booth_demo.xml',
'data/event_type_demo.xml',
],
'license': 'LGPL-3',
}
@@ -0,0 +1,106 @@
<?xml version="1.0" encoding="UTF-8" ?>
<odoo><data>
<record id="event_booth_category_standard" model="event.booth.category">
<field name="name">Standard Booth</field>
<field name="sequence">1</field>
<field name="image_1920" type="base64" file="event_booth/static/src/img/standard-booth.jpeg"/>
<field name="description" type="html">
<div class="card text-center shadow-sm mt32">
<div class="card-header p-4 bg-600">
<h4 class="text-white m-0">Standard</h4>
</div>
<ul class="list-group list-group-flush">
<li class="list-group-item">
<span>1 Branded Booth</span>
</li>
<li class="list-group-item bg-600">
<span class="text-white">4m²</span>
</li>
<li class="list-group-item">
<span>46" display screen</span>
</li>
<li class="list-group-item">
<span>1 desk</span>
</li>
<li class="list-group-item">
<span>Logo &amp; link on website</span>
</li>
</ul>
</div>
</field>
</record>
<record id="event_booth_category_premium" model="event.booth.category">
<field name="name">Premium Booth</field>
<field name="sequence">2</field>
<field name="image_1920" type="base64" file="event_booth/static/src/img/premium-booth.jpeg"/>
<field name="description" type="html">
<div class="card text-center shadow-sm mt32">
<div class="card-header p-4 bg-secondary">
<h4 class="text-white m-0">Premium</h4>
</div>
<ul class="list-group list-group-flush">
<li class="list-group-item">
<span>1 Branded Booth</span>
</li>
<li class="list-group-item bg-secondary">
<span class="text-white">4m²</span>
</li>
<li class="list-group-item">
<span>46" display screen</span>
</li>
<li class="list-group-item">
<span>1 desk</span>
</li>
<li class="list-group-item">
<span>Logo &amp; link on website</span>
</li>
<li class="list-group-item">
<span>50 words description on website</span>
</li>
<li class="list-group-item">
<span>10 + 1 passes</span>
</li>
</ul>
</div>
</field>
</record>
<record id="event_booth_category_vip" model="event.booth.category">
<field name="name">VIP Booth</field>
<field name="sequence">3</field>
<field name="image_1920" type="base64" file="event_booth/static/src/img/vip-booth.jpeg"/>
<field name="description" type="html">
<div class="card text-center shadow-sm mt32">
<div class="card-header p-4 bg-primary">
<h4 class="text-white m-0">VIP</h4>
</div>
<ul class="list-group list-group-flush">
<li class="list-group-item">
<span>2 Branded Booth</span>
</li>
<li class="list-group-item bg-primary">
<span class="text-white">8m²</span>
</li>
<li class="list-group-item">
<span>2 x 46" display screens</span>
</li>
<li class="list-group-item">
<span>2 desks</span>
</li>
<li class="list-group-item">
<span>Logo &amp; link on website</span>
</li>
<li class="list-group-item">
<span>100 words description on website</span>
</li>
<li class="list-group-item">
<span>10 + 1 passes</span>
</li>
</ul>
</div>
</field>
</record>
</data></odoo>
@@ -0,0 +1,103 @@
<?xml version="1.0" encoding="UTF-8" ?>
<odoo><data>
<!-- EVENT BOOTH FOR "Design Fair Los Angeles" EVENT -->
<record id="event_booth_0_event_0" model="event.booth">
<field name="name">Booth A1</field>
<field name="event_id" ref="event.event_0"/>
<field name="booth_category_id" ref="event_booth_category_standard"/>
</record>
<record id="event_booth_1_event_0" model="event.booth">
<field name="name">Booth A2</field>
<field name="event_id" ref="event.event_0"/>
<field name="booth_category_id" ref="event_booth_category_standard"/>
</record>
<record id="event_booth_2_event_0" model="event.booth">
<field name="name">Booth A3</field>
<field name="event_id" ref="event.event_0"/>
<field name="booth_category_id" ref="event_booth_category_standard"/>
</record>
<record id="event_booth_3_event_0" model="event.booth">
<field name="name">Premium Booth A4</field>
<field name="event_id" ref="event.event_0"/>
<field name="booth_category_id" ref="event_booth_category_premium"/>
</record>
<record id="event_booth_4_event_0" model="event.booth">
<field name="name">VIP Booth A5</field>
<field name="event_id" ref="event.event_0"/>
<field name="booth_category_id" ref="event_booth_category_vip"/>
</record>
<!-- EVENT BOOTH FOR "Conference for Architects" EVENT -->
<record id="event.event_2" model="event.event">
<field name="event_booth_ids" eval="[(5, 0)]"/>
</record>
<record id="event_booth_0_event_2" model="event.booth">
<field name="name">Showbooth 1</field>
<field name="event_id" ref="event.event_2"/>
<field name="booth_category_id" ref="event_booth_category_standard"/>
</record>
<record id="event_booth_1_event_2" model="event.booth">
<field name="name">Showbooth 2</field>
<field name="event_id" ref="event.event_2"/>
<field name="booth_category_id" ref="event_booth_category_standard"/>
</record>
<record id="event_booth_2_event_2" model="event.booth">
<field name="name">Premium Showbooth 1</field>
<field name="event_id" ref="event.event_2"/>
<field name="booth_category_id" ref="event_booth_category_premium"/>
</record>
<record id="event_booth_3_event_2" model="event.booth">
<field name="name">Premium Showbooth 2</field>
<field name="event_id" ref="event.event_2"/>
<field name="booth_category_id" ref="event_booth_category_premium"/>
</record>
<!-- EVENT BOOTH FOR "OpenWood Collection Online Reveal" EVENT -->
<record id="event_booth_00_event_7" model="event.booth">
<field name="name">Booth A1</field>
<field name="event_id" ref="event.event_7"/>
<field name="booth_category_id" ref="event_booth_category_standard"/>
</record>
<record id="event_booth_01_event_7" model="event.booth">
<field name="name">Booth A2</field>
<field name="event_id" ref="event.event_7"/>
<field name="booth_category_id" ref="event_booth_category_standard"/>
</record>
<record id="event_booth_02_event_7" model="event.booth">
<field name="name">Booth A3</field>
<field name="event_id" ref="event.event_7"/>
<field name="booth_category_id" ref="event_booth_category_standard"/>
</record>
<record id="event_booth_10_event_7" model="event.booth">
<field name="name">OpenWood Demonstrator 1</field>
<field name="event_id" ref="event.event_7"/>
<field name="booth_category_id" ref="event_booth_category_premium"/>
</record>
<record id="event_booth_11_event_7" model="event.booth">
<field name="name">OpenWood Demonstrator 2</field>
<field name="event_id" ref="event.event_7"/>
<field name="booth_category_id" ref="event_booth_category_premium"/>
</record>
<record id="event_booth_12_event_7" model="event.booth">
<field name="name">OpenWood Demonstrator 3</field>
<field name="event_id" ref="event.event_7"/>
<field name="booth_category_id" ref="event_booth_category_premium"/>
</record>
<record id="event_booth_20_event_7" model="event.booth">
<field name="name">Gold Booth 1</field>
<field name="event_id" ref="event.event_7"/>
<field name="booth_category_id" ref="event_booth_category_vip"/>
</record>
<record id="event_booth_21_event_7" model="event.booth">
<field name="name">Gold Booth 2</field>
<field name="event_id" ref="event.event_7"/>
<field name="booth_category_id" ref="event_booth_category_vip"/>
</record>
<record id="event_booth_22_event_7" model="event.booth">
<field name="name">Gold Booth 3</field>
<field name="event_id" ref="event.event_7"/>
<field name="booth_category_id" ref="event_booth_category_vip"/>
</record>
</data></odoo>
@@ -0,0 +1,25 @@
<?xml version="1.0" encoding="UTF-8" ?>
<odoo><data>
<record id="event_type_booth_demo_conference_0" model="event.type.booth">
<field name="name">Showbooth 1</field>
<field name="event_type_id" ref="event.event_type_data_conference"/>
<field name="booth_category_id" ref="event_booth_category_standard"/>
</record>
<record id="event_type_booth_demo_conference_1" model="event.type.booth">
<field name="name">Showbooth 2</field>
<field name="event_type_id" ref="event.event_type_data_conference"/>
<field name="booth_category_id" ref="event_booth_category_standard"/>
</record>
<record id="event_type_booth_demo_conference_2" model="event.type.booth">
<field name="name">Premium Showbooth 1</field>
<field name="event_type_id" ref="event.event_type_data_conference"/>
<field name="booth_category_id" ref="event_booth_category_premium"/>
</record>
<record id="event_type_booth_demo_conference_3" model="event.type.booth">
<field name="name">Premium Showbooth 2</field>
<field name="event_type_id" ref="event.event_type_data_conference"/>
<field name="booth_category_id" ref="event_booth_category_premium"/>
</record>
</data></odoo>
+10
View File
@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo><data noupdate="1">
<record id="mt_event_booth_booked" model="mail.message.subtype">
<field name="name">Booth Booked</field>
<field name="res_model">event.event</field>
<field name="default" eval="False"/>
</record>
</data></odoo>
@@ -0,0 +1,39 @@
<?xml version="1.0" encoding="UTF-8" ?>
<odoo><data>
<!-- This template is rendered on the event.event model without referring to it. -->
<!-- Instead it shows details about event.booth model. -->
<template id="event_booth_booked_template">
<p>
<span>
Booth
<a href="#" t-att-data-oe-model="booth._name" t-att-data-oe-id="booth.id" t-esc="booth.name"/>
</span>
<span t-esc="'booked by' if booth.partner_id else 'has been reserved by'"/>
<span>
<a href="#" t-att-data-oe-model="booth.partner_id._name or booth.env.user.partner_id._name"
t-att-data-oe-id="booth.partner_id.id or booth.env.user.partner_id.id"
t-esc="booth.partner_id.name or booth.env.user.partner_id.name"/>
</span>
</p>
<ul t-if="booth.partner_id" name="contact_details">
<t t-set="contact_name" t-value="booth.contact_name"/>
<li t-if="contact_name">
<b>Name</b>: <span t-esc="contact_name"/>
</li>
<t t-set="contact_email" t-value="booth.contact_email"/>
<li t-if="contact_email">
<b>Email</b>: <span t-esc="contact_email"/>
</li>
<t t-set="contact_mobile" t-value="booth.contact_mobile"/>
<li t-if="contact_mobile">
<b>Mobile</b>: <span t-esc="contact_mobile"/>
</li>
<t t-set="contact_phone" t-value="booth.contact_phone"/>
<li t-if="contact_phone">
<b>Phone</b>: <span t-esc="contact_phone"/>
</li>
</ul>
</template>
</data></odoo>
+8
View File
@@ -0,0 +1,8 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from . import event_type_booth
from . import event_booth
from . import event_booth_category
from . import event_event
from . import event_type
+117
View File
@@ -0,0 +1,117 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import api, fields, models
from odoo.osv import expression
class EventBooth(models.Model):
_name = 'event.booth'
_description = 'Event Booth'
_inherit = [
'event.type.booth',
'mail.thread',
'mail.activity.mixin'
]
# owner
event_type_id = fields.Many2one(ondelete='set null', required=False)
event_id = fields.Many2one('event.event', string='Event', ondelete='cascade', required=True)
# customer
partner_id = fields.Many2one('res.partner', string='Renter', tracking=True, copy=False)
contact_name = fields.Char('Renter Name', compute='_compute_contact_name', readonly=False, store=True, copy=False)
contact_email = fields.Char('Renter Email', compute='_compute_contact_email', readonly=False, store=True, copy=False)
contact_mobile = fields.Char('Renter Mobile', compute='_compute_contact_mobile', readonly=False, store=True, copy=False)
contact_phone = fields.Char('Renter Phone', compute='_compute_contact_phone', readonly=False, store=True, copy=False)
# state
state = fields.Selection(
[('available', 'Available'), ('unavailable', 'Unavailable')],
string='Status', group_expand='_group_expand_states',
default='available', required=True, tracking=True,
help='Shows the availability of a Booth')
is_available = fields.Boolean(compute='_compute_is_available', search='_search_is_available')
@api.depends('partner_id')
def _compute_contact_name(self):
for booth in self:
if not booth.contact_name:
booth.contact_name = booth.partner_id.name or False
@api.depends('partner_id')
def _compute_contact_email(self):
for booth in self:
if not booth.contact_email:
booth.contact_email = booth.partner_id.email or False
@api.depends('partner_id')
def _compute_contact_mobile(self):
for booth in self:
if not booth.contact_mobile:
booth.contact_mobile = booth.partner_id.mobile or False
@api.depends('partner_id')
def _compute_contact_phone(self):
for booth in self:
if not booth.contact_phone:
booth.contact_phone = booth.partner_id.phone or False
@api.depends('state')
def _compute_is_available(self):
for booth in self:
booth.is_available = booth.state == 'available'
def _search_is_available(self, operator, operand):
negative = operator in expression.NEGATIVE_TERM_OPERATORS
if (negative and operand) or not operand:
return [('state', '=', 'unavailable')]
return [('state', '=', 'available')]
def _group_expand_states(self, states, domain, order):
return [key for key, val in type(self).state.selection]
@api.model_create_multi
def create(self, vals_list):
res = super(EventBooth, self.with_context(mail_create_nosubscribe=True)).create(vals_list)
unavailable_booths = res.filtered(lambda booth: not booth.is_available)
unavailable_booths._post_confirmation_message()
return res
def write(self, vals):
to_confirm = self.filtered(lambda booth: booth.state == 'available')
wpartner = {}
if 'state' in vals or 'partner_id' in vals:
wpartner = dict(
(booth, booth.partner_id.ids)
for booth in self.filtered(lambda booth: booth.partner_id)
)
res = super(EventBooth, self).write(vals)
if vals.get('state') == 'unavailable' or vals.get('partner_id'):
for booth in self:
booth.message_subscribe(booth.partner_id.ids)
for booth in self:
if wpartner.get(booth) and booth.partner_id.id not in wpartner[booth]:
booth.message_unsubscribe(wpartner[booth])
if vals.get('state') == 'unavailable':
to_confirm._action_post_confirm(vals)
return res
def _post_confirmation_message(self):
for booth in self:
booth.event_id.message_post_with_view(
'event_booth.event_booth_booked_template',
values={
'booth': booth,
},
subtype_id=self.env.ref('event_booth.mt_event_booth_booked').id,
)
def action_confirm(self, additional_values=None):
write_vals = dict({'state': 'unavailable'}, **additional_values or {})
self.write(write_vals)
def _action_post_confirm(self, write_vals):
self._post_confirmation_message()
@@ -0,0 +1,17 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import fields, models
class EventBoothCategory(models.Model):
_name = 'event.booth.category'
_description = 'Event Booth Category'
_inherit = ['image.mixin']
_order = 'sequence ASC'
active = fields.Boolean(default=True)
name = fields.Char(string='Name', required=True, translate=True)
sequence = fields.Integer(string='Sequence', default=10)
description = fields.Html(string='Description', translate=True)
booth_ids = fields.One2many('event.booth', 'booth_category_id', string='Booths')
+90
View File
@@ -0,0 +1,90 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import api, fields, models
from odoo import Command
class Event(models.Model):
_inherit = 'event.event'
event_booth_ids = fields.One2many(
'event.booth', 'event_id', string='Booths', copy=True,
compute='_compute_event_booth_ids', readonly=False, store=True)
event_booth_count = fields.Integer(
string='Total Booths',
compute='_compute_event_booth_count')
event_booth_count_available = fields.Integer(
string='Available Booths',
compute='_compute_event_booth_count')
event_booth_category_ids = fields.Many2many(
'event.booth.category', compute='_compute_event_booth_category_ids')
event_booth_category_available_ids = fields.Many2many(
'event.booth.category', compute='_compute_event_booth_category_available_ids',
help='Booth Category for which booths are still available. Used in frontend')
@api.depends('event_type_id')
def _compute_event_booth_ids(self):
""" Update event configuration from its event type. Depends are set only
on event_type_id itself, not its sub fields. Purpose is to emulate an
onchange: if event type is changed, update event configuration. Changing
event type content itself should not trigger this method.
When synchronizing booths:
* lines that are available are removed;
* template lines are added;
"""
for event in self:
if not event.event_type_id and not event.event_booth_ids:
event.event_booth_ids = False
continue
# booths to keep: those that are not available
booths_to_remove = event.event_booth_ids.filtered(lambda booth: booth.is_available)
command = [Command.unlink(booth.id) for booth in booths_to_remove]
if event.event_type_id.event_type_booth_ids:
command += [
Command.create({
attribute_name: line[attribute_name] if not isinstance(line[attribute_name], models.BaseModel) else line[attribute_name].id
for attribute_name in self.env['event.type.booth']._get_event_booth_fields_whitelist()
}) for line in event.event_type_id.event_type_booth_ids
]
event.event_booth_ids = command
def _get_booth_stat_count(self):
elements = self.env['event.booth'].sudo().read_group(
[('event_id', 'in', self.ids)],
['event_id', 'state'], ['event_id', 'state'], lazy=False
)
elements_total_count = dict()
elements_available_count = dict()
for element in elements:
event_id = element['event_id'][0]
if element['state'] == 'available':
elements_available_count[event_id] = element['__count']
elements_total_count.setdefault(event_id, 0)
elements_total_count[event_id] += element['__count']
return elements_available_count, elements_total_count
@api.depends('event_booth_ids', 'event_booth_ids.state')
def _compute_event_booth_count(self):
if self.ids and all(bool(event.id) for event in self): # no new/onchange mode -> optimized
booths_available_count, booths_total_count = self._get_booth_stat_count()
for event in self:
event.event_booth_count_available = booths_available_count.get(event.id, 0)
event.event_booth_count = booths_total_count.get(event.id, 0)
else:
for event in self:
event.event_booth_count = len(event.event_booth_ids)
event.event_booth_count_available = len(event.event_booth_ids.filtered(lambda booth: booth.is_available))
@api.depends('event_booth_ids.booth_category_id')
def _compute_event_booth_category_ids(self):
for event in self:
event.event_booth_category_ids = event.event_booth_ids.mapped('booth_category_id')
@api.depends('event_booth_ids.is_available')
def _compute_event_booth_category_available_ids(self):
for event in self:
event.event_booth_category_available_ids = event.event_booth_ids.filtered(lambda booth: booth.is_available).mapped('booth_category_id')
+12
View File
@@ -0,0 +1,12 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import fields, models
class EventType(models.Model):
_inherit = 'event.type'
event_type_booth_ids = fields.One2many(
'event.type.booth', 'event_type_id',
string='Booths', readonly=False, store=True)
@@ -0,0 +1,27 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import api, fields, models
class EventBooth(models.Model):
_name = 'event.type.booth'
_description = 'Event Booth Template'
def _get_default_booth_category(self):
"""Assign booth category by default if only one exists"""
category_id = self.env['event.booth.category'].search([])
if category_id and len(category_id) == 1:
return category_id
name = fields.Char(string='Name', required=True, translate=True)
event_type_id = fields.Many2one(
'event.type', string='Event Category',
ondelete='cascade', required=True)
booth_category_id = fields.Many2one(
'event.booth.category', string='Booth Category',
default=_get_default_booth_category, ondelete='restrict', required=True)
@api.model
def _get_event_booth_fields_whitelist(self):
return ['name', 'booth_category_id']
@@ -0,0 +1,9 @@
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_event_booth_category,event.booth.category,model_event_booth_category,,0,0,0,0
access_event_booth_category_desk,event.booth.category.desk,model_event_booth_category,event.group_event_registration_desk,1,0,0,0
access_event_booth_category_manager,event.booth.category.manager,model_event_booth_category,event.group_event_manager,1,1,1,1
access_event_booth_all,event.booth.public,model_event_booth,,0,0,0,0
access_event_booth_user,event.booth.user,model_event_booth,event.group_event_registration_desk,1,0,0,0
access_event_booth_manager,event.booth.manager,model_event_booth,event.group_event_manager,1,1,1,1
access_event_type_booth_user,event.type.booth.user,model_event_type_booth,event.group_event_registration_desk,1,0,0,0
access_event_type_booth_manager,event.type.booth.manager,model_event_type_booth,event.group_event_manager,1,1,1,1
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
2 access_event_booth_category event.booth.category model_event_booth_category 0 0 0 0
3 access_event_booth_category_desk event.booth.category.desk model_event_booth_category event.group_event_registration_desk 1 0 0 0
4 access_event_booth_category_manager event.booth.category.manager model_event_booth_category event.group_event_manager 1 1 1 1
5 access_event_booth_all event.booth.public model_event_booth 0 0 0 0
6 access_event_booth_user event.booth.user model_event_booth event.group_event_registration_desk 1 0 0 0
7 access_event_booth_manager event.booth.manager model_event_booth event.group_event_manager 1 1 1 1
8 access_event_type_booth_user event.type.booth.user model_event_type_booth event.group_event_registration_desk 1 0 0 0
9 access_event_type_booth_manager event.type.booth.manager model_event_type_booth event.group_event_manager 1 1 1 1
Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

+5
View File
@@ -0,0 +1,5 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from . import test_event_booth_internals
from . import test_event_internals
+30
View File
@@ -0,0 +1,30 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo.addons.event.tests.common import TestEventCommon
class TestEventBoothCommon(TestEventCommon):
@classmethod
def setUpClass(cls):
super(TestEventBoothCommon, cls).setUpClass()
cls.event_booth_category_1 = cls.env['event.booth.category'].create({
'name': 'Standard',
'description': '<p>Standard</p>',
})
cls.event_booth_category_2 = cls.env['event.booth.category'].create({
'name': 'Premium',
'description': '<p>Premium</p>',
})
cls.event_type_complex.write({
'event_type_booth_ids': [
(5, 0),
(0, 0,
{'name': 'Standard 1', 'booth_category_id': cls.event_booth_category_1.id}),
(0, 0,
{'name': 'Premium 1', 'booth_category_id': cls.event_booth_category_2.id}),
],
})
@@ -0,0 +1,56 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from datetime import datetime, timedelta
from odoo.addons.event_booth.tests.common import TestEventBoothCommon
from odoo.fields import Datetime as FieldsDatetime
from odoo.tests.common import users
class TestEventData(TestEventBoothCommon):
@users('user_eventmanager')
def test_event_booth_contact(self):
""" Test contact details computation """
customer = self.env['res.partner'].browse(self.event_customer.ids)
category = self.env['event.booth.category'].browse(self.event_booth_category_1.ids)
self.assertTrue(all(
bool(customer[fname])
for fname in ['name', 'email', 'country_id', 'phone']
)
)
customer_email = customer.email
event = self.env['event.event'].create({
'name': 'Event',
'date_begin': FieldsDatetime.to_string(datetime.today() + timedelta(days=1)),
'date_end': FieldsDatetime.to_string(datetime.today() + timedelta(days=15)),
'event_type_id': False,
})
self.assertEqual(event.event_booth_ids, self.env['event.booth'])
booth = self.env['event.booth'].create({
'name': 'Test Booth',
'booth_category_id': category.id,
'event_id': event.id,
'partner_id': customer.id,
})
self.assertEqual(booth.contact_name, customer.name)
self.assertEqual(booth.contact_email, customer_email)
self.assertEqual(booth.contact_phone, customer.phone)
self.assertFalse(booth.contact_mobile, 'Data has no mobile')
booth.write({
'contact_email': '"New Emails" <new.email@test.example.com',
'contact_phone': False,
})
self.assertEqual(booth.contact_email, '"New Emails" <new.email@test.example.com')
self.assertEqual(booth.contact_phone, False)
self.assertEqual(customer.email, customer_email, 'No sync from booth to partner')
# partial update of contact fields: we may end up with mixed contact information, is it a good idea ?
booth.write({'partner_id': self.event_customer2.id})
self.assertEqual(booth.contact_name, customer.name)
self.assertEqual(booth.contact_email, '"New Emails" <new.email@test.example.com')
self.assertEqual(booth.contact_phone, self.event_customer2.phone)
@@ -0,0 +1,91 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from datetime import datetime, timedelta
from odoo import Command
from odoo.addons.event_booth.tests.common import TestEventBoothCommon
from odoo.fields import Datetime as FieldsDatetime
from odoo.tests.common import users, Form
class TestEventData(TestEventBoothCommon):
@users('user_eventmanager')
def test_event_configuration_booths_from_type(self):
""" Test data computation (related to booths) of event coming from its event.type template. """
# setup test records
event_type_nobooth = self.env['event.type'].create({
'name': 'No booth',
})
event_type_wbooths = self.env['event.type'].create({
'name': 'Using booths',
'event_type_booth_ids': [
Command.clear(),
Command.create({
'name': 'Standard Booth',
'booth_category_id': self.event_booth_category_1.id,
}),
Command.create({
'name': 'Premium Booth',
'booth_category_id': self.event_booth_category_2.id,
})
]
})
# no booth by default as no booths on type
event = self.env['event.event'].create({
'name': 'Event',
'date_begin': FieldsDatetime.to_string(datetime.today() + timedelta(days=1)),
'date_end': FieldsDatetime.to_string(datetime.today() + timedelta(days=15)),
'event_type_id': event_type_nobooth.id
})
self.assertEqual(event.event_booth_ids, self.env['event.booth'])
# manually create booths: ok
event.write({
'event_booth_ids': [
Command.create({
'name': 'Custom Standard Booth 1',
'booth_category_id': self.event_booth_category_1.id,
}),
Command.create({
'name': 'Custom Standard Booth 2',
'booth_category_id': self.event_booth_category_1.id,
})
]
})
self.assertEqual(event.event_booth_count, 2)
self.assertEqual(event.event_booth_count_available, 2)
self.assertEqual(event.event_booth_category_ids, self.event_booth_category_1)
self.assertEqual(event.event_booth_ids[1].message_partner_ids, self.env['res.partner'])
# updating partner is independent from availability
event.event_booth_ids[1].write({'partner_id': self.event_customer.id})
self.assertEqual(event.event_booth_count, 2)
self.assertEqual(event.event_booth_count_available, 2)
self.assertEqual(event.event_booth_ids[1].message_partner_ids, self.event_customer)
# one booth is sold
event.event_booth_ids[1].write({'state': 'unavailable'})
self.assertEqual(event.event_booth_count, 2)
self.assertEqual(event.event_booth_count_available, 1)
# partner is reset: booth still unavailable but follower removed
event.event_booth_ids[1].write({'partner_id': False})
self.assertEqual(event.event_booth_count, 2)
self.assertEqual(event.event_booth_count_available, 1)
self.assertEqual(event.event_booth_ids[1].message_partner_ids, self.env['res.partner'])
# change event type to one using booths: include event type booths and keep reserved booths
event_form = Form(event)
event_form.event_type_id = event_type_wbooths
self.assertEqual(event_form.event_booth_count, 3)
self.assertEqual(
set(r['name'] for r in event_form.event_booth_ids._records),
set(('Custom Standard Booth 2', 'Standard Booth', 'Premium Booth')),
'Should keep booths with reservation, remove unused ones and add type ones'
)
event_form.save()
self.assertEqual(event.event_booth_count_available, 2)
self.assertEqual(event.event_booth_category_ids, self.event_booth_category_1 + self.event_booth_category_2)
@@ -0,0 +1,65 @@
<?xml version="1.0" encoding="UTF-8" ?>
<odoo><data>
<record id="event_booth_category_view_form" model="ir.ui.view">
<field name="name">event.booth.category.view.form</field>
<field name="model">event.booth.category</field>
<field name="arch" type="xml">
<form string="Booth Type">
<sheet>
<widget name="web_ribbon" title="Archived" bg_color="bg-danger" attrs="{'invisible': [('active', '=', True)]}"/>
<field name="image_1920" widget='image' class="oe_avatar" options='{"preview_image": "image_128"}'/>
<div class="oe_title">
<label for="name" string="Booth Category"/>
<h1><field name="name" placeholder="e.g. Premium Booth"/></h1>
</div>
<group name="main">
<field name="active" invisible="1"/>
</group>
<notebook>
<page string="Description" name="description">
<field name="description"/>
</page>
</notebook>
</sheet>
</form>
</field>
</record>
<record id="event_booth_category_view_tree" model="ir.ui.view">
<field name="name">event.booth.category.view.tree</field>
<field name="model">event.booth.category</field>
<field name="arch" type="xml">
<tree>
<field name="sequence" widget="handle"/>
<field name="name"/>
</tree>
</field>
</record>
<record id="event_booth_category_view_search" model="ir.ui.view">
<field name="name">event.booth.category.view.search</field>
<field name="model">event.booth.category</field>
<field name="arch" type="xml">
<search>
<field name="name"/>
<filter string="Archived" name="archived" domain="[('active', '=', False)]"/>
</search>
</field>
</record>
<record id="event_booth_category_action" model="ir.actions.act_window">
<field name="name">Booth Category</field>
<field name="res_model">event.booth.category</field>
<field name="view_mode">tree,form</field>
<field name="help" type="html">
<p class="o_view_nocontent_smiling_face">
Create a Booth Category
</p>
<p>
Booth categories are used to represent the different types of booths you rent (Premium Booth, Table and Chairs, ...)
</p>
</field>
</record>
</data></odoo>
@@ -0,0 +1,224 @@
<?xml version="1.0" encoding="UTF-8" ?>
<odoo><data>
<record id="event_booth_view_form_from_event" model="ir.ui.view">
<field name="name">event.booth.view.form.from.event</field>
<field name="model">event.booth</field>
<field name="priority">32</field>
<field name="arch" type="xml">
<form string="Booths">
<header>
<field name="state" widget="statusbar" options="{'clickable': '1'}"/>
</header>
<sheet>
<div class="oe_button_box" name="button_box" attrs="{'invisible': [('id', '=', False)]}"/>
<div class="oe_title">
<label for="name" class="oe_edit_only" string="Name"/>
<h1><field name="name" placeholder="e.g. First Booth Alley 1"/></h1>
</div>
<group>
<group name="details">
<field name="booth_category_id"/>
</group>
<group name="renter">
<field name="partner_id"/>
<field name="contact_name"/>
<field name="contact_email"/>
<field name="contact_phone"/>
<field name="contact_mobile"/>
</group>
</group>
</sheet>
<div class="oe_chatter">
<field name="message_follower_ids" groups="base.group_user"/>
<field name="activity_ids"/>
<field name="message_ids"/>
</div>
</form>
</field>
</record>
<record id="event_booth_view_form" model="ir.ui.view">
<field name="name">event.booth.view.form</field>
<field name="model">event.booth</field>
<field name="inherit_id" ref="event_booth_view_form_from_event"/>
<field name="mode">primary</field>
<field name="priority">16</field>
<field name="arch" type="xml">
<field name="booth_category_id" position="before">
<field name="event_id"/>
</field>
</field>
</record>
<record id="event_booth_view_form_simple_from_event" model="ir.ui.view">
<field name="name">event.booth.view.form.simple.from.event</field>
<field name="model">event.booth</field>
<field name="inherit_id" ref="event_booth_view_form_from_event"/>
<field name="mode">primary</field>
<field name="priority">48</field>
<field name="arch" type="xml">
<xpath expr="//div[@name='button_box']" position="replace"/>
<xpath expr="//div[hasclass('oe_chatter')]" position="replace"/>
</field>
</record>
<record id="event_booth_view_tree_from_event" model="ir.ui.view">
<field name="name">event.booth.view.tree.from.event</field>
<field name="model">event.booth</field>
<field name="priority">32</field>
<field name="arch" type="xml">
<tree string="Booths" sample="1" expand="1">
<field name="name" decoration-bf="1"/>
<field name="booth_category_id"/>
<field name="partner_id"/>
<field name="contact_name" optional="hide"/>
<field name="contact_email" optional="hide"/>
<field name="contact_phone" optional="hide"/>
<field name="contact_mobile" optional="hide"/>
<field name="state" widget="badge"
decoration-info="state == 'available'"
decoration-success="state == 'unavailable'"/>
</tree>
</field>
</record>
<record id="event_booth_view_tree" model="ir.ui.view">
<field name="name">event.booth.view.tree</field>
<field name="model">event.booth</field>
<field name="inherit_id" ref="event_booth_view_tree_from_event"/>
<field name="mode">primary</field>
<field name="priority">16</field>
<field name="arch" type="xml">
<field name="name" position="after">
<field name="event_id"/>
</field>
</field>
</record>
<record id="event_booth_view_kanban_from_event" model="ir.ui.view">
<field name="name">event.booth.view.kanban</field>
<field name="model">event.booth</field>
<field name="priority">32</field>
<field name="arch" type="xml">
<kanban default_group_by="state" quick_create_view="event_booth.event_booth_view_form_quick_create" sample="1">
<field name="name"/>
<templates>
<t t-name="kanban-box">
<div class="o_kanban_content oe_kanban_global_click">
<div class="o_kanban_record_title">
<field name="name"/>
</div>
<div class="d-flex flex-column">
<div class="o_kanban_record_bottom">
<div class="oe_kanban_bottom_left">
<field name="booth_category_id"/>
</div>
<div class="oe_kanban_bottom_right">
<field name="activity_ids" widget="kanban_activity"/>
</div>
</div>
</div>
</div>
</t>
</templates>
</kanban>
</field>
</record>
<record id="event_booth_view_kanban" model="ir.ui.view">
<field name="name">event.booth.view.kanban</field>
<field name="model">event.booth</field>
<field name="inherit_id" ref="event_booth_view_kanban_from_event"/>
<field name="mode">primary</field>
<field name="priority">16</field>
<field name="arch" type="xml">
<xpath expr="//div[hasclass('o_kanban_record_bottom')]" position="before">
<field name="event_id"/>
</xpath>
</field>
</record>
<record id="event_booth_view_form_quick_create" model="ir.ui.view">
<field name="name">event.booth.view.form.quick_create</field>
<field name="model">event.booth</field>
<field name="arch" type="xml">
<form>
<group>
<field name="name" placeholder="e.g. First Booth Alley 1"/>
<field name="booth_category_id"/>
</group>
</form>
</field>
</record>
<record id="event_booth_view_search" model="ir.ui.view">
<field name="name">event.booth.view.search</field>
<field name="model">event.booth</field>
<field name="arch" type="xml">
<search string="Event Booth">
<field name="name" string="Name" filter_domain="[('name', 'ilike', self)]"/>
<field name="contact_name" string="Renter Name" filter_domain="[('contact_name', 'ilike', self)]"/>
<field name="contact_email" string="Renter Email" filter_domain="[('contact_email', 'ilike', self)]"/>
<field name="event_id"/>
<filter string="Available" name="filter_booth_available"
domain="[('state', '=', 'available')]"/>
<filter string="Unavailable" name="filter_booth_unavailable"
domain="[('state', '=', 'unavailable')]"/>
<group expand="0" string="Group By">
<filter string="Booth Type" name="group_by_booth_category_id" context="{'group_by': 'booth_category_id'}"/>
<filter string="Event" name="group_by_event_id" context="{'group_by': 'event_id'}"/>
</group>
</search>
</field>
</record>
<record id="event_booth_action" model="ir.actions.act_window">
<field name="name">Booths</field>
<field name="res_model">event.booth</field>
<field name="view_mode">kanban,tree,form</field>
<field name="domain">[]</field>
<field name="context">{}</field>
<field name="help" type="html">
<p class="o_view_nocontent_smiling_face">
Create a Booth
</p><p>
Booths are the physical stands that you rent during your event.
</p>
</field>
</record>
<record id="event_booth_action_from_event" model="ir.actions.act_window">
<field name="name">Booths</field>
<field name="res_model">event.booth</field>
<field name="view_mode">kanban,tree,form</field>
<field name="domain">[('event_id', '=', active_id)]</field>
<field name="context">{'default_event_id': active_id}</field>
<field name="help" type="html">
<p class="o_view_nocontent_smiling_face">
Create a Booth
</p><p>
Booths are the physical stands that you rent during your event.
</p>
</field>
</record>
<record id="event_booth_action_from_event_view_kanban" model="ir.actions.act_window.view">
<field name="sequence">1</field>
<field name="view_mode">kanban</field>
<field name="view_id" ref="event_booth_view_kanban_from_event"/>
<field name="act_window_id" ref="event_booth_action_from_event"/>
</record>
<record id="event_booth_action_from_event_view_tree" model="ir.actions.act_window.view">
<field name="sequence">2</field>
<field name="view_mode">tree</field>
<field name="view_id" ref="event_booth_view_tree_from_event"/>
<field name="act_window_id" ref="event_booth_action_from_event"/>
</record>
<record id="event_booth_action_from_event_view_form" model="ir.actions.act_window.view">
<field name="sequence">3</field>
<field name="view_mode">form</field>
<field name="view_id" ref="event_booth_view_form_from_event"/>
<field name="act_window_id" ref="event_booth_action_from_event"/>
</record>
</data></odoo>
@@ -0,0 +1,37 @@
<?xml version="1.0" encoding="UTF-8" ?>
<odoo><data>
<record id="event_event_view_form" model="ir.ui.view">
<field name="name">event.event.view.form.inherit.event.booth</field>
<field name="model">event.event</field>
<field name="inherit_id" ref="event.view_event_form"/>
<field name="priority" eval="4"/>
<field name="arch" type="xml">
<div name="button_box" position="inside">
<button class="oe_stat_button" name="%(event_booth_action_from_event)d"
type="action" icon="fa-university">
<div class="o_field_widget o_stat_info">
<span class="o_stat_value">
<span attrs="{'invisible': [('event_booth_count', '=', 0)]}">
<field name="event_booth_count_available"/> /
</span>
<field name="event_booth_count"/>
</span>
<span class="o_stat_text">Booths</span>
</div>
</button>
</div>
<page name="tickets" position="after">
<page string="Booths" groups="base.group_no_one">
<!-- The booths created from 'event_type_id' won't be saved if invisible attr is set. -->
<field name="event_booth_ids"
context="{
'form_view_ref': 'event_booth.event_booth_view_form_simple_from_event',
'tree_view_ref': 'event_booth.event_booth_view_tree_from_event',
}"/>
</page>
</page>
</field>
</record>
</data></odoo>
+16
View File
@@ -0,0 +1,16 @@
<?xml version="1.0" encoding="UTF-8" ?>
<odoo><data>
<menuitem name="Booth Categories"
id="menu_event_booth_category"
action="event_booth_category_action"
sequence="20"
parent="event.menu_event_configuration"/>
<menuitem name="Booths"
id="menu_event_booth"
action="event_booth_action"
sequence="21"
parent="event.menu_event_configuration"
groups="base.group_no_one"/>
</data></odoo>
@@ -0,0 +1,85 @@
<?xml version="1.0" encoding="UTF-8" ?>
<odoo><data>
<record id="event_type_booth_view_form_from_type" model="ir.ui.view">
<field name="name">event.type.booth.view.form.from.type</field>
<field name="model">event.type.booth</field>
<field name="arch" type="xml">
<form string="Event Type Booth">
<sheet>
<div class="oe_title">
<label for="name" class="oe_edit_only" string="Name"/>
<h1><field name="name" placeholder="e.g. First Booth Alley 1"/></h1>
</div>
<group>
<field name="booth_category_id"/>
</group>
</sheet>
</form>
</field>
</record>
<record id="event_type_booth_view_form" model="ir.ui.view">
<field name="name">event.type.booth.view.form</field>
<field name="model">event.type.booth</field>
<field name="inherit_id" ref="event_type_booth_view_form_from_type"/>
<field name="mode">primary</field>
<field name="priority">10</field>
<field name="arch" type="xml">
<xpath expr="//field[@name='booth_category_id']" position="before">
<field name="event_type_id"/>
</xpath>
</field>
</record>
<record id="event_type_booth_view_tree_from_type" model="ir.ui.view">
<field name="name">event.type.booth.view.tree.from.type</field>
<field name="model">event.type.booth</field>
<field name="arch" type="xml">
<tree string="Event Type Booths">
<field name="name"/>
<field name="booth_category_id"/>
</tree>
</field>
</record>
<record id="event_type_booth_view_tree" model="ir.ui.view">
<field name="name">event.type.booth.view.tree</field>
<field name="model">event.type.booth</field>
<field name="inherit_id" ref="event_type_booth_view_tree_from_type"/>
<field name="mode">primary</field>
<field name="priority">10</field>
<field name="arch" type="xml">
<xpath expr="//field[@name='booth_category_id']" position="before">
<field name="event_type_id"/>
</xpath>
</field>
</record>
<record id="event_type_booth_view_search" model="ir.ui.view">
<field name="name">event.type.booth.view.search</field>
<field name="model">event.type.booth</field>
<field name="arch" type="xml">
<search string="Event Type Booths">
<field name="name"/>
<group expand="0" string="Group By">
<filter string="Booth Type" name="booth_category" context="{'group_by': 'booth_category_id'}"/>
</group>
</search>
</field>
</record>
<record id="event_type_booth_action" model="ir.actions.act_window">
<field name="name">Event Type Booths</field>
<field name="res_model">event.type.booth</field>
<field name="view_mode">tree,form</field>
<field name="help" type="html">
<p class="o_view_nocontent_smiling_face">
Create a Type Booth
</p><p>
Booths are the physical stands that you rent during your event.
</p>
</field>
</record>
</data></odoo>
@@ -0,0 +1,22 @@
<?xml version="1.0" encoding="UTF-8" ?>
<odoo><data>
<record id="event_type_view_form" model="ir.ui.view">
<field name="name">event.type.view.form.inherit.event.booth</field>
<field name="model">event.type</field>
<field name="inherit_id" ref="event.view_event_type_form"/>
<field name="arch" type="xml">
<page name="event_type_communication" position="after">
<page string="Booths">
<field name="event_type_booth_ids"
class="w-100"
context="{
'tree_view_ref': 'event_booth.event_type_booth_view_tree_from_type',
'form_view_ref': 'event_booth.event_type_booth_view_form_from_type'
}"/>
</page>
</page>
</field>
</record>
</data></odoo>
+5
View File
@@ -0,0 +1,5 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from . import models
from . import wizard
+34
View File
@@ -0,0 +1,34 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
{
'name': "Events Booths Sales",
'category': 'Marketing/Events',
'version': '1.0',
'summary': "Manage event booths sale",
'description': """
Sell your event booths and track payments on sale orders.
""",
'depends': ['event_booth', 'event_sale'],
'data': [
'security/ir.model.access.csv',
'data/product_data.xml',
'data/event_booth_category_data.xml',
'views/product_views.xml',
'views/sale_order_views.xml',
'views/event_type_booth_views.xml',
'views/event_booth_category_views.xml',
'views/event_booth_registration_views.xml',
'views/event_booth_views.xml',
'wizard/event_booth_configurator_views.xml',
],
'auto_install': True,
'assets': {
'web.assets_backend': [
'event_booth_sale/static/src/js/event_booth_configurator_form_controller.js',
'event_booth_sale/static/src/js/event_booth_configurator_form_view.js',
'event_booth_sale/static/src/js/event_booth_configurator_widget.js',
]
},
'license': 'LGPL-3',
}
@@ -0,0 +1,19 @@
<?xml version="1.0" encoding="UTF-8" ?>
<odoo><data>
<record id="event_booth.event_booth_category_standard" model="event.booth.category">
<field name="product_id" ref="event_booth_sale.product_product_event_booth"/>
<field name="price">100</field>
</record>
<record id="event_booth.event_booth_category_premium" model="event.booth.category">
<field name="product_id" ref="event_booth_sale.product_product_event_booth"/>
<field name="price">500</field>
</record>
<record id="event_booth.event_booth_category_vip" model="event.booth.category">
<field name="product_id" ref="event_booth_sale.product_product_event_booth"/>
<field name="price">1000</field>
</record>
</data></odoo>
@@ -0,0 +1,15 @@
<?xml version="1.0" encoding="UTF-8" ?>
<odoo><data noupdate="1">
<record id="product_product_event_booth" model="product.product">
<field name="name">Event Booth</field>
<field name="is_event_booth" eval="True"/>
<field name="purchase_ok" eval="False"/>
<field name="list_price">100.0</field>
<field name="description_sale" eval="False"/>
<field name="categ_id" ref="event_sale.product_category_events"/>
<field name="invoice_policy">order</field>
<field name="type">service</field>
</record>
</data></odoo>
@@ -0,0 +1,11 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from . import account_move
from . import product
from . import event_booth_registration
from . import event_booth
from . import event_booth_category
from . import event_type_booth
from . import sale_order
from . import sale_order_line
@@ -0,0 +1,16 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import models
class AccountMove(models.Model):
_inherit = 'account.move'
def action_invoice_paid(self):
""" When an invoice linked to a sales order selling registrations is
paid, update booths accordingly as they are booked when invoice is paid.
"""
res = super(AccountMove, self).action_invoice_paid()
self.mapped('line_ids.sale_line_ids')._update_event_booths(set_paid=True)
return res
@@ -0,0 +1,49 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import api, fields, models, _
from odoo.exceptions import UserError
class EventBooth(models.Model):
_inherit = 'event.booth'
# registrations
event_booth_registration_ids = fields.One2many('event.booth.registration', 'event_booth_id')
# sale information
sale_order_line_registration_ids = fields.Many2many(
'sale.order.line', 'event_booth_registration',
'event_booth_id', 'sale_order_line_id', string='SO Lines with reservations',
groups='sales_team.group_sale_salesman', copy=False)
sale_order_line_id = fields.Many2one(
'sale.order.line', string='Final Sale Order Line', ondelete='set null',
readonly=True, states={'available': [('readonly', False)]},
groups='sales_team.group_sale_salesman', copy=False)
sale_order_id = fields.Many2one(
related='sale_order_line_id.order_id', store='True', readonly=True,
groups='sales_team.group_sale_salesman')
is_paid = fields.Boolean('Is Paid', copy=False)
@api.ondelete(at_uninstall=False)
def _unlink_except_linked_sale_order(self):
booth_with_so = self.filtered('sale_order_id')
if booth_with_so:
raise UserError(_(
'You can\'t delete the following booths as they are linked to sales orders: '
'%(booths)s', booths=', '.join(booth_with_so.mapped('name'))))
def action_set_paid(self):
self.write({'is_paid': True})
def action_view_sale_order(self):
self.sale_order_id.ensure_one()
action = self.env['ir.actions.actions']._for_xml_id('sale.action_orders')
action['views'] = [(False, 'form')]
action['res_id'] = self.sale_order_id.id
return action
def _get_booth_multiline_description(self):
return '%s : \n%s' % (
self.event_id.display_name,
'\n'.join(['- %s' % booth.name for booth in self])
)
@@ -0,0 +1,84 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
import logging
from odoo import api, fields, models
_logger = logging.getLogger(__name__)
class EventBoothCategory(models.Model):
_inherit = 'event.booth.category'
def _default_product_id(self):
return self.env.ref('event_booth_sale.product_product_event_booth', raise_if_not_found=False)
product_id = fields.Many2one(
'product.product', string='Product', required=True,
domain=[('is_event_booth', '=', True)], default=_default_product_id)
price = fields.Float(string='Price', compute='_compute_price', digits='Product Price', readonly=False, store=True)
price_reduce = fields.Float(
string='Price Reduce', compute='_compute_price_reduce',
compute_sudo=True, digits='Product Price')
image_1920 = fields.Image(compute='_compute_image_1920', readonly=False, store=True)
@api.depends('product_id')
def _compute_image_1920(self):
for category in self:
category.image_1920 = category.image_1920 if category.image_1920 else category.product_id.image_1920
@api.depends('product_id')
def _compute_price(self):
""" By default price comes from category but can be changed by event
people as product may be shared accross various categories. """
for category in self:
if category.product_id and category.product_id.list_price:
category.price = category.product_id.list_price + category.product_id.price_extra
@api.depends('product_id', 'price')
def _compute_price_reduce(self):
for category in self:
product = category.product_id
list_price = product.list_price + product.price_extra
discount = (list_price - product.price) / list_price if list_price else 0.0
category.price_reduce = (1.0 - discount) * category.price
def _init_column(self, column_name):
""" Initialize product_id for existing columns when installing sale
bridge, to ensure required attribute is fulfilled. """
if column_name != "product_id":
return super(EventBoothCategory, self)._init_column(column_name)
# fetch void columns
self.env.cr.execute("SELECT id FROM %s WHERE product_id IS NULL" % self._table)
booth_category_ids = self.env.cr.fetchall()
if not booth_category_ids:
return
# update existing columns
_logger.debug("Table '%s': setting default value of new column %s to unique values for each row",
self._table, column_name)
default_booth_product = self._default_product_id()
if default_booth_product:
product_id = default_booth_product.id
else:
product_id = self.env['product.product'].create({
'name': 'Generic Event Booth Product',
'categ_id': self.env.ref('event_sale.product_category_events').id,
'list_price': 100,
'standard_price': 0,
'is_event_booth': True,
'type': 'service',
'invoice_policy': 'order',
}).id
self.env['ir.model.data'].create({
'name': 'product_product_event_booth',
'module': 'event_booth_sale',
'model': 'product.product',
'res_id': product_id,
})
self.env.cr._obj.execute(
f'UPDATE {self._table} SET product_id = %s WHERE id IN %s;',
(product_id, tuple(booth_category_ids))
)
@@ -0,0 +1,78 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import api, fields, models, _
class EventBoothRegistration(models.Model):
"""event.booth.registrations are used to allow multiple partners to book the same booth.
Whenever a partner has paid his registration all the others linked to the booth will be deleted."""
_name = 'event.booth.registration'
_description = 'Event Booth Registration'
sale_order_line_id = fields.Many2one('sale.order.line', string='Sale Order Line', required=True)
event_booth_id = fields.Many2one('event.booth', string='Booth', required=True)
partner_id = fields.Many2one(
'res.partner', related='sale_order_line_id.order_partner_id', store=True)
contact_name = fields.Char(string='Contact Name', compute='_compute_contact_name', readonly=False, store=True)
contact_email = fields.Char(string='Contact Email', compute='_compute_contact_email', readonly=False, store=True)
contact_phone = fields.Char(string='Contact Phone', compute='_compute_contact_phone', readonly=False, store=True)
contact_mobile = fields.Char(string='Contact Mobile', compute='_compute_contact_mobile', readonly=False, store=True)
_sql_constraints = [('unique_registration', 'unique(sale_order_line_id, event_booth_id)',
'There can be only one registration for a booth by sale order line')]
@api.depends('partner_id')
def _compute_contact_name(self):
for registration in self:
if not registration.contact_name:
registration.contact_name = registration.partner_id.name or False
@api.depends('partner_id')
def _compute_contact_email(self):
for registration in self:
if not registration.contact_email:
registration.contact_email = registration.partner_id.email or False
@api.depends('partner_id')
def _compute_contact_phone(self):
for registration in self:
if not registration.contact_phone:
registration.contact_phone = registration.partner_id.phone or False
@api.depends('partner_id')
def _compute_contact_mobile(self):
for registration in self:
if not registration.contact_mobile:
registration.contact_mobile = registration.partner_id.mobile or False
@api.model
def _get_fields_for_booth_confirmation(self):
return ['sale_order_line_id', 'partner_id', 'contact_name', 'contact_email', 'contact_phone', 'contact_mobile']
def action_confirm(self):
for registration in self:
values = {
field: registration[field].id if isinstance(registration[field], models.BaseModel) else registration[field]
for field in self._get_fields_for_booth_confirmation()
}
registration.event_booth_id.action_confirm(values)
self._cancel_pending_registrations()
def _cancel_pending_registrations(self):
body = '<p>%(message)s: <ul>%(booth_names)s</ul></p>' % {
'message': _('Your order has been cancelled because the following booths have been reserved'),
'booth_names': ''.join('<li>%s</li>' % booth.display_name for booth in self.event_booth_id)
}
other_registrations = self.search([
('event_booth_id', 'in', self.event_booth_id.ids),
('id', 'not in', self.ids)
])
for order in other_registrations.sale_order_line_id.order_id:
order.sudo().message_post(
body=body,
partner_ids=order.user_id.partner_id.ids,
)
order.sudo().action_cancel()
other_registrations.unlink()
@@ -0,0 +1,16 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import api, fields, models
class EventTypeBooth(models.Model):
_inherit = 'event.type.booth'
product_id = fields.Many2one(related='booth_category_id.product_id')
price = fields.Float(related='booth_category_id.price')
@api.model
def _get_event_booth_fields_whitelist(self):
res = super(EventTypeBooth, self)._get_event_booth_fields_whitelist()
return res + ['product_id', 'price']
+26
View File
@@ -0,0 +1,26 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import api, fields, models
class ProductTemplate(models.Model):
_inherit = 'product.template'
is_event_booth = fields.Boolean(string='Is an Event Booth')
@api.onchange('is_event_booth')
def _onchange_is_event_booth(self):
if self.is_event_booth:
self.type = 'service'
self.invoice_policy = 'order'
class Product(models.Model):
_inherit = 'product.product'
@api.onchange('is_event_booth')
def _onchange_is_event_booth(self):
if self.is_event_booth:
self.type = 'service'
self.invoice_policy = 'order'
@@ -0,0 +1,35 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import api, fields, models
class SaleOrder(models.Model):
_inherit = 'sale.order'
event_booth_ids = fields.One2many('event.booth', 'sale_order_id', string='Booths')
event_booth_count = fields.Integer(string='Booth Count', compute='_compute_event_booth_count')
@api.depends('event_booth_ids')
def _compute_event_booth_count(self):
if self.ids:
slot_data = self.env['event.booth'].read_group(
[('sale_order_id', 'in', self.ids)],
['sale_order_id'], ['sale_order_id']
)
slot_mapped = dict((data['sale_order_id'][0], data['sale_order_id_count']) for data in slot_data)
else:
slot_mapped = dict()
for so in self:
so.event_booth_count = slot_mapped.get(so.id, 0)
def action_confirm(self):
res = super(SaleOrder, self).action_confirm()
for so in self:
so.order_line._update_event_booths()
return res
def action_view_booth_list(self):
action = self.env['ir.actions.act_window']._for_xml_id('event_booth.event_booth_action')
action['domain'] = [('sale_order_id', 'in', self.ids)]
return action
@@ -0,0 +1,97 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import api, fields, models, _
from odoo.exceptions import ValidationError
class SaleOrderLine(models.Model):
_inherit = 'sale.order.line'
event_booth_category_id = fields.Many2one('event.booth.category', string='Booths Category', ondelete='set null')
event_booth_pending_ids = fields.Many2many(
'event.booth', string='Pending Booths', search='_search_event_booth_pending_ids',
compute='_compute_event_booth_pending_ids', inverse='_inverse_event_booth_pending_ids',
help='Used to create registration when providing the desired event booth.'
)
event_booth_registration_ids = fields.One2many(
'event.booth.registration', 'sale_order_line_id', string='Confirmed Registration')
event_booth_ids = fields.One2many('event.booth', 'sale_order_line_id', string='Confirmed Booths')
is_event_booth = fields.Boolean(related='product_id.is_event_booth', readonly=True)
@api.depends('event_booth_ids')
def _compute_name_short(self):
wbooth = self.filtered(lambda line: line.event_booth_pending_ids)
for record in wbooth:
record.name_short = record.event_booth_pending_ids.event_id.name
super(SaleOrderLine, self - wbooth)._compute_name_short()
@api.depends('event_booth_registration_ids')
def _compute_event_booth_pending_ids(self):
for so_line in self:
so_line.event_booth_pending_ids = so_line.event_booth_registration_ids.event_booth_id
def _inverse_event_booth_pending_ids(self):
for so_line in self:
self.env['event.booth.registration'].create([{
'event_booth_id': booth.id,
'sale_order_line_id': so_line.id,
'partner_id': so_line.order_id.partner_id.id
} for booth in so_line.event_booth_pending_ids])
def _search_event_booth_pending_ids(self, operator, value):
return [('event_booth_registration_ids.event_booth_id', operator, value)]
@api.constrains('event_booth_registration_ids')
def _check_event_booth_registration_ids(self):
if len(self.event_booth_registration_ids.event_booth_id.event_id) > 1:
raise ValidationError(_('Registrations from the same Order Line must belong to a single event.'))
@api.onchange('product_id')
def _onchange_product_id_booth(self):
"""We reset the event when the selected product doesn't belong to any pending booths."""
if self.event_id and (not self.product_id or self.product_id not in self.event_booth_pending_ids.product_id):
self.event_id = None
@api.onchange('event_id')
def _onchange_event_id_booth(self):
"""We reset the pending booths when the event changes to avoid inconsistent state."""
if self.event_booth_pending_ids and (not self.event_id or self.event_id != self.event_booth_pending_ids.event_id):
self.event_booth_pending_ids = None
@api.onchange('event_booth_pending_ids')
def _onchange_event_booth_pending_ids(self):
# trigger the description computation
self.product_id_change()
def _update_event_booths(self, set_paid=False):
for so_line in self.filtered('is_event_booth'):
if so_line.event_booth_pending_ids and not so_line.event_booth_ids:
unavailable = so_line.event_booth_pending_ids.filtered(lambda booth: not booth.is_available)
if unavailable:
raise ValidationError(
_('The following booths are unavailable, please remove them to continue : %(booth_names)s',
booth_names=''.join('\n\t- %s' % booth.display_name for booth in unavailable)))
so_line.event_booth_registration_ids.action_confirm()
if so_line.event_booth_ids and set_paid:
so_line.event_booth_ids.action_set_paid()
return True
def get_sale_order_line_multiline_description_sale(self, product):
if self.event_booth_pending_ids:
booths = self.event_booth_pending_ids.with_context(
lang=self.order_id.partner_id.lang,
)
return booths._get_booth_multiline_description()
return super(SaleOrderLine, self).get_sale_order_line_multiline_description_sale(product)
def _get_display_price(self, product):
if self.event_booth_pending_ids and self.event_id:
company = self.event_id.company_id or self.env.company
currency = company.currency_id
total_price = sum([booth.price for booth in self.event_booth_pending_ids])
return currency._convert(
total_price, self.order_id.currency_id,
self.order_id.company_id or self.env.company.id,
self.order_id.date_order or fields.Date.today())
return super(SaleOrderLine, self)._get_display_price(product)
@@ -0,0 +1,5 @@
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_event_booth_registration_salesman,access.event.booth.registration.salesman,model_event_booth_registration,sales_team.group_sale_salesman,1,1,1,1
access_event_booth_registration_event_desk,access.event.booth.registration.event.desk,model_event_booth_registration,event.group_event_registration_desk,1,0,0,0
access_event_booth_registration_event_user,access.event.booth.registration.event.user,model_event_booth_registration,event.group_event_user,1,1,1,1
access_event_booth_configurator_salesman,access.event.booth.configurator.salesman,model_event_booth_configurator,sales_team.group_sale_salesman,1,1,1,0
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
2 access_event_booth_registration_salesman access.event.booth.registration.salesman model_event_booth_registration sales_team.group_sale_salesman 1 1 1 1
3 access_event_booth_registration_event_desk access.event.booth.registration.event.desk model_event_booth_registration event.group_event_registration_desk 1 0 0 0
4 access_event_booth_registration_event_user access.event.booth.registration.event.user model_event_booth_registration event.group_event_user 1 1 1 1
5 access_event_booth_configurator_salesman access.event.booth.configurator.salesman model_event_booth_configurator sales_team.group_sale_salesman 1 1 1 0
@@ -0,0 +1,44 @@
odoo.define('event_booth_sale.event_booth_configurator_form_controller', function (require) {
'use strict';
const FormController = require('web.FormController');
/**
* This controller is overridden to allow configuring sale_order_lines through a popup
* window when a product with 'is_event_booth' is selected.
*
* This allows keeping an editable list view for sales order and remove the noise of
* those 3 fields ('event_id', 'event_booth_category_id' and 'event_booth_ids')
*/
const EventBoothConfiguratorFormController = FormController.extend({
//--------------------------------------------------------------------------
// Overrides
//--------------------------------------------------------------------------
saveRecord: function () {
const self = this;
return this._super.apply(this, arguments).then(function () {
const state = self.model.get(self.handle, {raw: true});
self.do_action({
type: 'ir.actions.act_window_close',
infos: {
eventBoothConfiguration: {
event_id: {id: state.data.event_id},
event_booth_category_id: {id: state.data.event_booth_category_id},
event_booth_pending_ids: {
operation: 'MULTI',
commands: [{
operation: 'REPLACE_WITH',
ids: state.data.event_booth_ids,
}],
}
}
}
});
});
}
});
return EventBoothConfiguratorFormController;
});
@@ -0,0 +1,21 @@
odoo.define('event_booth_sale.event_booth_configurator_form_view', function (require) {
'use strict';
const EventBoothConfiguratorFormController = require('event_booth_sale.event_booth_configurator_form_controller');
const FormView = require('web.FormView');
const viewRegistry = require('web.view_registry');
/**
* @see EventBoothConfiguratorFormController for more information.
*/
const EventBoothConfiguratorFormView = FormView.extend({
config: _.extend({}, FormView.prototype.config, {
Controller: EventBoothConfiguratorFormController,
}),
});
viewRegistry.add('event_booth_configurator_form', EventBoothConfiguratorFormView);
return EventBoothConfiguratorFormView;
});
@@ -0,0 +1,149 @@
odoo.define('event_booth_sale.product_configurator', function (require) {
const ProductConfiguratorWidget = require('sale.product_configurator');
/**
* Extension of the ProductConfiguratorWidget to support event booth configuration.
* It opens when an event booth product_product is set.
*
* The event booth information include:
* - event_id
* - event_booth_category_id
* - event_booth_ids
*
*/
ProductConfiguratorWidget.include({
//--------------------------------------------------------------------------
// Overrides
//--------------------------------------------------------------------------
/**
* @returns {boolean}
*
* @override
* @private
*/
_isConfigurableLine: function () {
return this.recordData.is_event_booth || this._super.apply(this, arguments);
},
/**
* Opens the event booth configurator in 'edit' mode
*
* @override
* @private
*/
_onEditLineConfiguration: function () {
if (this.recordData.is_event_booth) {
const defaultValues = {
default_product_id: this.recordData.product_id.data.id,
default_sale_order_line_id: this.recordData.id || null
};
if (this.recordData.event_id) {
defaultValues.default_event_id = this.recordData.event_id.data.id;
}
if (this.recordData.event_booth_category_id) {
defaultValues.default_event_booth_category_id = this.recordData.event_booth_category_id.data.id;
}
if (this.recordData.event_booth_pending_ids) {
defaultValues.default_event_booth_ids = this.recordData.event_booth_pending_ids.res_ids;
}
this._openEventBoothConfigurator(defaultValues, this.dataPointID);
} else {
this._super.apply(this, arguments);
}
},
/**
* @param {integer} productId: product.product ID
* @param {String} dataPointId
* @returns {Promise<Boolean>} stopPropagation true if a suitable configurator has been found.
*
* @override
* @private
*/
_onProductChange: function (productId, dataPointId) {
return this._super.apply(this, arguments).then((stopPropagation) => {
if (stopPropagation) {
return Promise.resolve(true);
} else {
return this._isProductEventBooth(productId, dataPointId);
}
});
},
//--------------------------------------------------------------------------
// Private
//-------------------------------------------------------------------------
/**
* This method will check if the productId needs a configuration or not.
*
* @param {integer} productId: product.product ID
* @param {String} dataPointId
* @returns {Promise<Boolean>} stopPropagation true if the product is an event booth.
*
* @private
*/
_isProductEventBooth: function (productId, dataPointId) {
const self = this;
return this._rpc({
model: 'product.product',
method: 'read',
args: [productId, ['is_event_booth']],
}).then(function (result) {
if (result && result[0].is_event_booth) {
self._openEventBoothConfigurator({
default_product_id: productId
}, dataPointId);
return Promise.resolve(true);
}
return Promise.resolve(false);
});
},
/**
* Opens the event booth configurator allowing to configure the SO line with its informations.
*
* When the window is closed, configured values are used to trigger a 'field_changed'
* event to modify the current SO line.
*
* If the window is closed without providing the required values 'event_id', 'event_booth_category_id'
* and 'event_booth_pending_ids', the product_id field is cleaned.
*
* @param {Object} additionalContext: various "default_" values added to context when calling
* configuration wizard model;
* @param dataPointId
*
* @private
*/
_openEventBoothConfigurator: function (additionalContext, dataPointId) {
const self = this;
this.do_action('event_booth_sale.event_booth_configurator_action', {
additional_context: additionalContext,
on_close: function (result) {
if (result && !result.special) {
self.trigger_up('field_changed', {
changes: result.eventBoothConfiguration,
dataPointID: dataPointId,
onSuccess: function () {
self._onLineConfigured();
}
});
} else if (!self.recordData.event_id || !self.recordData.event_booth_pending_ids) {
self.trigger_up('field_changed', {
changes: {
product_id: false,
name: '',
},
dataPointID: dataPointId,
});
}
}
});
},
});
return ProductConfiguratorWidget;
});
@@ -0,0 +1,4 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from . import test_event_internals
+24
View File
@@ -0,0 +1,24 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo.addons.event_booth.tests.common import TestEventBoothCommon
from odoo.addons.event_sale.tests.common import TestEventSaleCommon
class TestEventBoothSaleCommon(TestEventBoothCommon, TestEventSaleCommon):
@classmethod
def setUpClass(cls):
super(TestEventBoothSaleCommon, cls).setUpClass()
cls.event_booth_product = cls.env['product.product'].create({
'name': 'Test Booth Product',
'description_sale': 'Mighty Booth Description',
'list_price': 20,
'is_event_booth': True,
'standard_price': 60.0,
'type': 'service',
})
(cls.event_booth_category_1 + cls.event_booth_category_2).write({
'product_id': cls.event_booth_product.id,
})
@@ -0,0 +1,95 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from datetime import datetime, timedelta
from odoo import Command
from odoo.addons.event_booth_sale.tests.common import TestEventBoothSaleCommon
from odoo.fields import Datetime as FieldsDatetime
from odoo.tests.common import users, Form
class TestEventData(TestEventBoothSaleCommon):
@users('user_eventmanager')
def test_event_configuration_booths_from_type(self):
""" Test data computation (related to booths) of event coming from its event.type template. """
# setup test records
event_type_nobooth = self.env['event.type'].create({
'name': 'No booth',
})
event_type_wbooths = self.env['event.type'].create({
'name': 'Using booths',
'event_type_booth_ids': [
Command.clear(),
Command.create({
'name': 'Standard Booth',
'booth_category_id': self.event_booth_category_1.id,
}),
Command.create({
'name': 'Premium Booth',
'booth_category_id': self.event_booth_category_2.id,
})
]
})
# no booth by default as no booths on type
event = self.env['event.event'].create({
'name': 'Event',
'date_begin': FieldsDatetime.to_string(datetime.today() + timedelta(days=1)),
'date_end': FieldsDatetime.to_string(datetime.today() + timedelta(days=15)),
'event_type_id': event_type_nobooth.id
})
self.assertEqual(event.event_booth_ids, self.env['event.booth'])
# manually create booths: ok
event.write({
'event_booth_ids': [
Command.create({
'name': 'Custom Standard Booth 1',
'booth_category_id': self.event_booth_category_1.id,
}),
Command.create({
'name': 'Custom Standard Booth 2',
'booth_category_id': self.event_booth_category_1.id,
})
]
})
self.assertEqual(event.event_booth_count, 2)
self.assertEqual(event.event_booth_count_available, 2)
self.assertEqual(event.event_booth_ids.product_id, self.event_booth_product)
# updating partner is independent from availability
event.event_booth_ids[1].write({'partner_id': self.event_customer.id})
self.assertEqual(event.event_booth_count, 2)
self.assertEqual(event.event_booth_count_available, 2)
self.assertEqual(event.event_booth_ids[1].message_partner_ids, self.event_customer)
# one booth is sold
event.event_booth_ids[1].write({'state': 'unavailable'})
self.assertEqual(event.event_booth_count, 2)
self.assertEqual(event.event_booth_count_available, 1)
# partner is reset: booth still unavailable but follower removed
event.event_booth_ids[1].write({'partner_id': False})
self.assertEqual(event.event_booth_count, 2)
self.assertEqual(event.event_booth_count_available, 1)
self.assertEqual(event.event_booth_ids[1].message_partner_ids, self.env['res.partner'])
# add group or the test will fail
self.user_eventmanager.write({'groups_id': [
(4, self.env.ref('sales_team.group_sale_salesman').id),
]})
# change event type to one using booths: include event type booths and keep reserved booths
event_form = Form(event)
event_form.event_type_id = event_type_wbooths
self.assertEqual(event_form.event_booth_count, 3)
self.assertEqual(
set(r['name'] for r in event_form.event_booth_ids._records),
set(('Custom Standard Booth 2', 'Standard Booth', 'Premium Booth')),
'Should keep booths with reservation, remove unused ones and add type ones'
)
event_form.save()
self.assertEqual(event.event_booth_count_available, 2)
self.assertEqual(event.event_booth_category_ids, self.event_booth_category_1 + self.event_booth_category_2)
@@ -0,0 +1,32 @@
<?xml version="1.0" encoding="UTF-8" ?>
<odoo><data>
<record id="event_booth_category_view_form" model="ir.ui.view">
<field name="name">event.booth.category.view.form.inherit.sale</field>
<field name="model">event.booth.category</field>
<field name="inherit_id" ref="event_booth.event_booth_category_view_form"/>
<field name="priority" eval="1"/>
<field name="arch" type="xml">
<group name="main" position="inside">
<group string="Booth Details">
<field name="product_id" context="{'default_is_event_booth': True, 'default_type': 'service'}"/>
<field name="price"/>
</group>
</group>
</field>
</record>
<record id="event_booth_category_view_tree" model="ir.ui.view">
<field name="name">event.booth.category.view.tree.inherit.sale</field>
<field name="model">event.booth.category</field>
<field name="inherit_id" ref="event_booth.event_booth_category_view_tree"/>
<field name="priority">3</field>
<field name="arch" type="xml">
<field name="name" position="after">
<field name="product_id"/>
<field name="price"/>
</field>
</field>
</record>
</data></odoo>
@@ -0,0 +1,46 @@
<?xml version="1.0" encoding="UTF-8" ?>
<odoo><data>
<record id="event_booth_registration_view_form" model="ir.ui.view">
<field name="name">event.booth.registration.view.form</field>
<field name="model">event.booth.registration</field>
<field name="arch" type="xml">
<form string="Booth Registration">
<sheet>
<div class="oe_title">
<h1><field name="partner_id"/></h1>
</div>
<group>
<group string="Details">
<field name="event_booth_id"/>
<field name="sale_order_line_id"/>
</group>
<group string="Contact">
<field name="contact_name"/>
<field name="contact_email"/>
<field name="contact_phone"/>
<field name="contact_mobile"/>
</group>
</group>
</sheet>
</form>
</field>
</record>
<record id="event_booth_registration_view_tree" model="ir.ui.view">
<field name="name">event.booth.registration.view.tree</field>
<field name="model">event.booth.registration</field>
<field name="arch" type="xml">
<tree string="Booth Registration">
<field name="partner_id"/>
<field name="event_booth_id"/>
<field name="sale_order_line_id"/>
<field name="contact_name" optional="hide"/>
<field name="contact_email" optional="hide"/>
<field name="contact_phone" optional="hide"/>
<field name="contact_mobile" optional="hide"/>
</tree>
</field>
</record>
</data></odoo>
@@ -0,0 +1,63 @@
<?xml version="1.0" encoding="UTF-8" ?>
<odoo><data>
<record id="event_booth_view_form_from_event" model="ir.ui.view">
<field name="name">event.booth.view.form.inherit.sale</field>
<field name="model">event.booth</field>
<field name="inherit_id" ref="event_booth.event_booth_view_form_from_event"/>
<field name="priority">10</field>
<field name="arch" type="xml">
<div name="button_box" position="inside">
<button name="action_view_sale_order" type="object" class="oe_stat_button"
icon="fa-usd" groups="sales_team.group_sale_salesman"
string="Sale Order" attrs="{'invisible': [('sale_order_id', '=', False)]}">
</button>
</div>
<div name="button_box" position="after">
<field name="is_paid" invisible="1"/>
<widget name="web_ribbon" title="Paid" attrs="{'invisible': [('is_paid', '=', False)]}"/>
</div>
<field name="booth_category_id" position="after">
<field name="product_id" attrs="{'invisible': [('booth_category_id', '=', False)]}"/>
<field name="price" attrs="{'invisible': [('booth_category_id', '=', False)]}"/>
</field>
<group name="renter" position="after">
<group name="sales" groups="base.group_no_one"
attrs="{'invisible': [('sale_order_line_id', '=', False)]}">
<field name="sale_order_id"/>
<field name="sale_order_line_id"/>
</group>
</group>
<xpath expr="//sheet" position="inside">
<notebook groups="base.group_no_one">
<page string="Registrations">
<field name="event_booth_registration_ids" readonly="1"/>
</page>
</notebook>
</xpath>
</field>
</record>
<record id="event_booth_view_tree" model="ir.ui.view">
<field name="name">event.booth.view.tree.inherit.sale</field>
<field name="model">event.booth</field>
<field name="inherit_id" ref="event_booth.event_booth_view_tree"/>
<field name="arch" type="xml">
<field name="booth_category_id" position="after">
<field name="price"/>
</field>
</field>
</record>
<record id="event_booth_view_search" model="ir.ui.view">
<field name="name">event.booth.view.search.inherit.sale</field>
<field name="model">event.booth</field>
<field name="inherit_id" ref="event_booth.event_booth_view_search"/>
<field name="arch" type="xml">
<xpath expr="//field[@name='name']" position="after">
<field name="sale_order_id"/>
</xpath>
</field>
</record>
</data></odoo>
@@ -0,0 +1,28 @@
<?xml version="1.0" encoding="UTF-8" ?>
<odoo><data>
<record id="event_type_booth_view_form_from_type" model="ir.ui.view">
<field name="name">event.booth.view.form.from.type.inherit.sale</field>
<field name="model">event.type.booth</field>
<field name="inherit_id" ref="event_booth.event_type_booth_view_form_from_type"/>
<field name="arch" type="xml">
<field name="booth_category_id" position="after">
<field name="product_id"/>
<field name="price"/>
</field>
</field>
</record>
<record id="event_type_booth_view_tree_from_type" model="ir.ui.view">
<field name="name">event.booth.view.tree.from.type.inherit.sale</field>
<field name="model">event.type.booth</field>
<field name="inherit_id" ref="event_booth.event_type_booth_view_tree_from_type"/>
<field name="arch" type="xml">
<field name="booth_category_id" position="after">
<field name="product_id"/>
<field name="price"/>
</field>
</field>
</record>
</data></odoo>
@@ -0,0 +1,15 @@
<?xml version="1.0" encoding="UTF-8" ?>
<odoo><data>
<record id="product_template_view_form" model="ir.ui.view">
<field name="name">product.template.view.form.inherit.event.booth.sale</field>
<field name="model">product.template</field>
<field name="inherit_id" ref="product.product_template_form_view"/>
<field name="arch" type="xml">
<field name="event_ok" position="after">
<field name="is_event_booth"/>
</field>
</field>
</record>
</data></odoo>
@@ -0,0 +1,27 @@
<?xml version="1.0" encoding="UTF-8" ?>
<odoo><data>
<record id="sale_order_view_form" model="ir.ui.view">
<field name="name">sale.order.view.form.inherit.event.booth.sale</field>
<field name="model">sale.order</field>
<field name="inherit_id" ref="sale.view_order_form"/>
<field name="arch" type="xml">
<xpath expr="//button[@name='action_view_attendee_list']" position="after">
<field name="event_booth_count" invisible="1"/>
<button name="action_view_booth_list" type="object" class="oe_stat_button" icon="fa-university"
attrs="{'invisible': [('event_booth_count', '=', 0)]}">
<field name="event_booth_count" widget="statinfo" string="Booths"/>
</button>
</xpath>
<xpath expr="//field[@name='order_line']//tree//field[@name='event_ticket_id']" position="after">
<field name="is_event_booth" optional="hide"/>
<field name="event_booth_category_id" optional="hide"/>
<field name="event_booth_pending_ids" optional="hide"/>
</xpath>
<xpath expr="//field[@name='order_line']//tree//field[@name='product_uom_qty']" position="attributes">
<attribute name="attrs">{'readonly': [('is_event_booth', '=', True)]}</attribute>
</xpath>
</field>
</record>
</data></odoo>
@@ -0,0 +1,4 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from . import event_booth_configurator
@@ -0,0 +1,34 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import api, models, fields, _
from odoo.exceptions import ValidationError
class EventBoothConfigurator(models.TransientModel):
_name = 'event.booth.configurator'
_description = 'Event Booth Configurator'
product_id = fields.Many2one('product.product', string='Product', readonly=True)
sale_order_line_id = fields.Many2one('sale.order.line', string='Sale Order Line', readonly=True)
event_id = fields.Many2one('event.event', string='Event', required=True)
event_booth_category_available_ids = fields.Many2many(related='event_id.event_booth_category_available_ids', readonly=True)
event_booth_category_id = fields.Many2one(
'event.booth.category', string='Booth Category', required=True,
compute='_compute_event_booth_category_id', readonly=False, store=True)
event_booth_ids = fields.Many2many(
'event.booth', string='Booth', required=True,
compute='_compute_event_booth_ids', readonly=False, store=True)
@api.depends('event_id')
def _compute_event_booth_category_id(self):
self.event_booth_category_id = False
@api.depends('event_id', 'event_booth_category_id')
def _compute_event_booth_ids(self):
self.event_booth_ids = False
@api.constrains('event_booth_ids')
def _check_if_no_booth_ids(self):
if any(not wizard.event_booth_ids for wizard in self):
raise ValidationError(_('You have to select at least one booth.'))
@@ -0,0 +1,44 @@
<?xml version="1.0" encoding="UTF-8" ?>
<odoo><data>
<record id="event_booth_configurator_view_form" model="ir.ui.view">
<field name="name">event.booth.configurator.view.form</field>
<field name="model">event.booth.configurator</field>
<field name="arch" type="xml">
<form js_class="event_booth_configurator_form">
<group>
<field name="product_id" invisible="1"/>
<field name="event_id" options="{'no_open': True, 'no_create': True}"
domain="[
('event_booth_ids.product_id', '=', product_id),
('is_finished', '=', False)
]"/>
<field name="event_booth_category_available_ids" invisible="1"/>
<field name="event_booth_category_id" options="{'no_open': True, 'no_create': True}"
attrs="{'invisible': [('event_id', '=', False)]}"
domain="[('id', 'in', event_booth_category_available_ids)]"/>
<field name="event_booth_ids" options="{'no_open': True, 'no_create': True}"
widget="many2many_checkboxes" attrs="{'invisible': [('event_booth_category_id', '=', False)]}"
domain="[
('event_id', '=', event_id),
('booth_category_id', '=', event_booth_category_id),
('product_id', '=', product_id),
('state', '=', 'available')
]"/>
</group>
<footer>
<button string="Ok" class="btn-primary" special="save"/>
<button string="Cancel" class="btn-secondary" special="cancel"/>
</footer>
</form>
</field>
</record>
<record id="event_booth_configurator_action" model="ir.actions.act_window">
<field name="name">Configure an event booth</field>
<field name="res_model">event.booth.configurator</field>
<field name="view_mode">form</field>
<field name="target">new</field>
</record>
</data></odoo>
+2 -2
View File
@@ -12,16 +12,16 @@ automatic lead generation, full Online support, ...
""",
'depends': [
'event',
'event_booth',
'event_crm',
'event_sale',
'website_event_booth_sale_exhibitor',
'website_event_crm_questions',
'website_event_exhibitor',
'website_event_questions',
'website_event_questions',
'website_event_meet',
'website_event_sale',
'website_event_track',
'website_event_exhibitor',
'website_event_track_live',
'website_event_track_quiz',
],
+5
View File
@@ -0,0 +1,5 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from . import controllers
from . import models
@@ -0,0 +1,32 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
{
'name': 'Online Event Booths',
'category': 'Marketing/Events',
'version': '1.0',
'summary': 'Events, display your booths on your website',
'description': """
Display your booths on your website for the users to register.
""",
'depends': ['website_event', 'event_booth'],
'data': [
'security/ir.model.access.csv',
'security/event_booth_security.xml',
'views/event_type_views.xml',
'views/event_event_views.xml',
'views/event_booth_registration_templates.xml',
'views/event_booth_templates.xml',
],
'demo': [
'data/event_demo.xml',
],
'auto_install': True,
'assets': {
'web.assets_frontend': [
'/website_event_booth/static/src/js/booth_register.js',
'/website_event_booth/static/src/scss/website_event_booth.scss',
]
},
'license': 'LGPL-3',
}
@@ -0,0 +1,4 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from . import event_booth
@@ -0,0 +1,167 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
import json
import werkzeug
from werkzeug.exceptions import Forbidden, NotFound
from odoo import exceptions, http, _
from odoo.http import request
from odoo.addons.website_event.controllers.main import WebsiteEventController
class WebsiteEventBoothController(WebsiteEventController):
@http.route('/event/<model("event.event"):event>/booth', type='http', auth='public', website=True, sitemap=True)
def event_booth_main(self, event):
try:
event.check_access_rights('read')
event.check_access_rule('read')
except exceptions.AccessError:
raise Forbidden()
event_sudo = event.sudo()
values = {
'event': event_sudo,
'event_booths': event_sudo.event_booth_ids,
'available_booth_category_ids': event_sudo.event_booth_category_available_ids,
'main_object': event,
}
return request.render('website_event_booth.event_booth_registration', values)
@http.route('/event/<model("event.event"):event>/booth/register',
type='http', auth='public', methods=['POST'], website=True, sitemap=False)
def event_booth_register(self, event, booth_category_id):
event_booth_ids = request.httprequest.form.getlist('event_booth_ids')
return request.redirect(('/event/%s/booth/register_form?' % event.id) + werkzeug.urls.url_encode({
'booth_ids': ','.join(event_booth_ids),
'booth_category_id': int(booth_category_id),
}))
@http.route('/event/<model("event.event"):event>/booth/register_form',
type='http', auth='public', methods=['GET'], website=True, sitemap=False)
def event_booth_contact_form(self, event, booth_ids=None, booth_category_id=None):
if not booth_ids or not booth_category_id:
raise NotFound()
booth_category = request.env['event.booth.category'].sudo().browse(int(booth_category_id))
event_booths = request.env['event.booth'].sudo().browse([int(booth_id) for booth_id in booth_ids.split(',')])
default_contact = {}
if not request.env.user._is_public():
default_contact = {
'name': request.env.user.partner_id.name,
'email': request.env.user.partner_id.email,
'phone': request.env.user.partner_id.phone,
'mobile': request.env.user.partner_id.mobile,
}
else:
visitor = request.env['website.visitor']._get_visitor_from_request()
if visitor.email:
default_contact = {
'name': visitor.name,
'email': visitor.email,
'mobile': visitor.mobile,
}
return request.render(
'website_event_booth.event_booth_registration_details',
{'event': event.sudo(),
'default_contact': default_contact,
'booth_category': booth_category,
'event_booths': event_booths,
}
)
def _get_requested_booths(self, event, event_booth_ids):
booth_ids = json.loads(event_booth_ids)
booths = request.env['event.booth'].sudo().search([
('event_id', '=', event.id),
('state', '=', 'available'),
('id', 'in', booth_ids)
])
if booth_ids != booths.ids:
raise Forbidden(_('Booth registration failed. Please try again.'))
if len(booths.booth_category_id) != 1:
raise Forbidden(_('Booths should belong to the same category.'))
return booths
@http.route('/event/<model("event.event"):event>/booth/confirm',
type='http', auth='public', methods=['POST'], website=True, sitemap=False)
def event_booth_registration_confirm(self, event, booth_category_id, event_booth_ids, **kwargs):
booths = self._get_requested_booths(event, event_booth_ids)
booth_values = self._prepare_booth_registration_values(event, kwargs)
booths.action_confirm(booth_values)
return request.redirect(('/event/%s/booth/success?' % event.id) + werkzeug.urls.url_encode({
'booths': ','.join([str(id) for id in booths.ids]),
}))
# This will be removed soon
@http.route('/event/<model("event.event"):event>/booth/success',
type='http', auth='public', methods=['GET'], website=True, sitemap=False)
def event_booth_registration_complete(self, event, booths):
booth_ids = request.env['event.booth'].sudo().search([
('event_id', '=', event.id),
('state', '=', 'unavailable'),
('id', 'in', [int(id) for id in booths.split(',')]),
])
if len(booth_ids.mapped('partner_id')) > 1:
raise NotFound()
event_sudo = event.sudo()
return request.render(
'website_event_booth.event_booth_registration_complete',
{'event': event,
'event_booths': event_sudo.event_booth_ids,
'main_object': event,
'contact_name': booth_ids[0].contact_name or booth_ids.partner_id.name,
'contact_email': booth_ids[0].contact_email or booth_ids.partner_id.email,
'contact_mobile': booth_ids[0].contact_mobile or booth_ids.partner_id.mobile,
'contact_phone': booth_ids[0].contact_phone or booth_ids.partner_id.phone,
}
)
def _prepare_booth_registration_values(self, event, kwargs):
return self._prepare_booth_registration_partner_values(event, kwargs)
def _prepare_booth_registration_partner_values(self, event, kwargs):
if request.env.user._is_public():
contact_email = kwargs['contact_email']
partner = request.env['res.partner'].sudo().find_or_create(contact_email)
if not partner.name and kwargs.get('contact_name'):
partner.name = kwargs['contact_name']
if not partner.phone and kwargs.get('contact_phone'):
partner.phone = kwargs['contact_phone']
if not partner.mobile and kwargs.get('contact_mobile'):
partner.mobile = kwargs['contact_mobile']
else:
partner = request.env.user.partner_id
return {
'partner_id': partner.id,
'contact_name': kwargs.get('contact_name') or partner.name,
'contact_email': kwargs.get('contact_email') or partner.email,
'contact_mobile': kwargs.get('contact_mobile') or partner.mobile,
'contact_phone': kwargs.get('contact_phone') or partner.phone,
}
@http.route('/event/booth/check_availability', type='json', auth='public', methods=['POST'])
def check_booths_availability(self, event_booth_ids=None):
if not event_booth_ids:
return {}
booths = request.env['event.booth'].sudo().browse(event_booth_ids)
return {
'unavailable_booths': booths.filtered(lambda booth: not booth.is_available).ids
}
@http.route(['/event/booth_category/get_available_booths'], type='json', auth='public')
def get_booth_category_available_booths(self, event_id, booth_category_id):
booth_ids = request.env['event.booth'].sudo().search([
('event_id', '=', int(event_id)),
('booth_category_id', '=', int(booth_category_id)),
('state', '=', 'available')
])
return [
{'id': booth.id, 'name': booth.name}
for booth in booth_ids
]
@@ -0,0 +1,15 @@
<?xml version="1.0" encoding="UTF-8" ?>
<odoo><data>
<record id="event.event_2" model="event.event">
<field name="exhibition_map" type="base64" file="website_event_booth/static/src/img/exhibition-map.gif"/>
<field name="website_menu" eval="True"/>
<field name="booth_menu" eval="True"/>
</record>
<record id="event.event_7" model="event.event">
<field name="exhibition_map" type="base64" file="website_event_booth/static/src/img/exhibition-map.gif"/>
<field name="booth_menu" eval="True"/>
</record>
</data></odoo>
@@ -0,0 +1,6 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from . import event_type
from . import event_event
from . import website_event_menu
@@ -0,0 +1,55 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import api, fields, models, _
from odoo.addons.http_routing.models.ir_http import slug
class Event(models.Model):
_inherit = 'event.event'
exhibition_map = fields.Image(string='Exhibition Map', max_width=1024, max_height=1024)
# frontend menu management
booth_menu = fields.Boolean(
string='Booth Register', compute='_compute_booth_menu',
readonly=False, store=True)
booth_menu_ids = fields.One2many(
'website.event.menu', 'event_id', string='Event Booths Menus',
domain=[('menu_type', '=', 'booth')])
@api.depends('event_type_id', 'website_menu')
def _compute_booth_menu(self):
for event in self:
if event.event_type_id and event.event_type_id != event._origin.event_type_id:
event.booth_menu = event.event_type_id.booth_menu
elif event.website_menu and (event.website_menu != event._origin.website_menu or not event.booth_menu):
event.booth_menu = True
elif not event.website_menu:
event.booth_menu = False
# ------------------------------------------------------------
# WEBSITE MENU MANAGEMENT
# ------------------------------------------------------------
def toggle_booth_menu(self, val):
self.booth_menu = val
def _get_menu_update_fields(self):
return super(Event, self)._get_menu_update_fields() + ['booth_menu']
def _update_website_menus(self, menus_update_by_field=None):
super(Event, self)._update_website_menus(menus_update_by_field=menus_update_by_field)
for event in self:
if event.menu_id and (not menus_update_by_field or event in menus_update_by_field.get('booth_menu')):
event._update_website_menu_entry('booth_menu', 'booth_menu_ids', 'booth')
def _get_menu_type_field_matching(self):
res = super(Event, self)._get_menu_type_field_matching()
res['booth'] = 'booth_menu'
return res
def _get_website_menu_entries(self):
self.ensure_one()
return super(Event, self)._get_website_menu_entries() + [
(_('Get A Booth'), '/event/%s/booth' % slug(self), False, 90, 'booth')
]
@@ -0,0 +1,17 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import api, fields, models
class EventType(models.Model):
_inherit = 'event.type'
booth_menu = fields.Boolean(
string='Booths on Website', compute='_compute_booth_menu',
readonly=False, store=True)
@api.depends('website_menu')
def _compute_booth_menu(self):
for event_type in self:
event_type.booth_menu = event_type.website_menu
@@ -0,0 +1,11 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import fields, models
class EventMenu(models.Model):
_inherit = "website.event.menu"
menu_type = fields.Selection(
selection_add=[('booth', 'Event Booth Menus')], ondelete={'booth': 'cascade'})
@@ -0,0 +1,15 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo><data noupdate="1">
<record id="ir_rule_event_booth_public" model="ir.rule">
<field name="name">Event Booth: public/portal: published read</field>
<field name="model_id" ref="event_booth.model_event_booth"/>
<field name="domain_force">[('event_id.website_published', '=', True)]</field>
<field name="groups" eval="[(4, ref('base.group_public')), (4, ref('base.group_portal'))]"/>
<field name="perm_read" eval="True"/>
<field name="perm_write" eval="False"/>
<field name="perm_create" eval="False"/>
<field name="perm_unlink" eval="False"/>
</record>
</data></odoo>
@@ -0,0 +1,2 @@
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_event_booth_all,event.booth.public,event_booth.model_event_booth,,1,0,0,0
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
2 access_event_booth_all event.booth.public event_booth.model_event_booth 1 0 0 0
Binary file not shown.

After

Width:  |  Height:  |  Size: 64 KiB

@@ -0,0 +1,137 @@
odoo.define('website_event_booth.booth_registration', function (require) {
'use strict';
var dom = require('web.dom');
var publicWidget = require('web.public.widget');
publicWidget.registry.boothRegistration = publicWidget.Widget.extend({
selector: '.o_wbooth_registration',
events: {
'change input[name="booth_category_id"]': '_onChangeBoothType',
'change .custom-checkbox > input[type="checkbox"]': '_onChangeBooth',
'click .o_wbooth_registration_submit': '_onSubmitClick',
},
start() {
this.eventId = parseInt(this.$el.data('event-id'));
this.activeType = false;
this.boothCache = {};
return this._super.apply(this, arguments).then(() => {
this.$('input[name="booth_category_id"]:enabled:first').click();
});
},
//--------------------------------------------------------------------------
// Private
//--------------------------------------------------------------------------
_check_booths_availability(eventBoothIds) {
const self = this;
return this._rpc({
route: "/event/booth/check_availability",
params: {
event_booth_ids: eventBoothIds,
},
}).then(function (result) {
if (result.unavailable_booths.length) {
self.$('input[name="event_booth_ids"]').each(function (i, el) {
if (result.unavailable_booths.includes(parseInt(el.value))) {
$(el).closest('.custom-checkbox').addClass('text-danger');
}
});
self.$('.o_wbooth_unavailable_booth_alert').removeClass('d-none');
return Promise.resolve(false);
}
return Promise.resolve(true);
})
},
_countSelectedBooths() {
return this.$('.custom-checkbox > input[type="checkbox"]:checked').length;
},
_fillBooths() {
var $boothElem = this.$('.o_wbooth_booths');
$boothElem.empty();
$.each(this.boothCache[this.activeType], function (key, booth) {
let $checkbox = dom.renderCheckbox({
text: booth.name,
prop: {
name: 'event_booth_ids',
value: booth.id
}
});
$boothElem.append($checkbox);
});
},
_showBoothCategoryDescription() {
this.$('.o_wbooth_booth_description').addClass('d-none');
this.$('#o_wbooth_booth_description_' + this.activeType).removeClass('d-none');
},
_updateUiAfterBoothCategoryChange() {
this._fillBooths();
this._showBoothCategoryDescription();
this._updateUiAfterBoothChange(this._countSelectedBooths());
},
_updateUiAfterBoothChange(boothCount) {
let $button = this.$('button.o_wbooth_registration_submit');
$button.attr('disabled', !boothCount);
},
//--------------------------------------------------------------------------
// Handlers
//--------------------------------------------------------------------------
_onChangeBooth(ev) {
$(ev.currentTarget).closest('.custom-checkbox').removeClass('text-danger');
this._updateUiAfterBoothChange(this._countSelectedBooths());
},
/**
* Load all the booths related to the chosen booth category and
* add them to a local dictionary to avoid making rpc each time the
* user change the booth category.
*
* Then the selection input will be filled with the fetched booth values.
*
* @param ev
* @private
*/
_onChangeBoothType(ev) {
ev.preventDefault();
this.activeType = parseInt(ev.currentTarget.value);
if (this.boothCache[this.activeType] === undefined) {
var self = this;
this._rpc({
route: '/event/booth_category/get_available_booths',
params: {
event_id: this.eventId,
booth_category_id: this.activeType,
},
}).then(function (result) {
self.boothCache[self.activeType] = result;
self._updateUiAfterBoothCategoryChange();
});
} else {
this._updateUiAfterBoothCategoryChange();
}
},
async _onSubmitClick(ev) {
ev.preventDefault();
let $form = this.$('.o_wbooth_registration_form');
let event_booth_ids = this.$('input[name=event_booth_ids]:checked').map(function () {
return parseInt($(this).val());
}).get();
if (await this._check_booths_availability(event_booth_ids)) {
$form.submit();
}
},
});
return publicWidget.registry.boothRegistration;
});
@@ -0,0 +1,69 @@
label.o_wbooth_category_unavailable {
opacity: 0.5;
overflow: hidden;
}
#o_wbooth_contact_details_form {
.col-form-label {
width: 200px;
}
}
.o_wbooth_registration_form label {
position: relative;
& > input {
visibility: hidden;
position: absolute;
}
& > input + div {
padding: 10px;
border: 5px solid #e9ecef;
}
&:not(.o_wbooth_category_unavailable) > input + div {
cursor: pointer;
}
&:not(.o_wbooth_category_unavailable):hover > input + div {
border-color: #6c757d;
}
&:hover > input + div:before {
color: #6c757d;
}
& > input:checked + div,
&:hover > input:checked + div {
border-color: $o-enterprise-primary-color;
}
& > input:checked + div:before,
&:hover > input:checked + div:before {
color: $o-enterprise-primary-color;
}
& > input:checked + div:before,
&:not(.o_wbooth_category_unavailable):hover > input + div:before {
content: "\f058";
font-family: "FontAwesome";
width: 40px;
height: 40px;
position: absolute;
line-height: 40px;
top: -16px;
right: -16px;
background-color: white;
font-size: 2em;
text-align: center;
}
& img {
min-height: 250px;
}
}
@@ -0,0 +1,91 @@
<?xml version="1.0" encoding="UTF-8" ?>
<odoo><data>
<template id="event_booth_registration_details" name="Event Booth Registration Details">
<t t-call="website_event_booth.event_booth_layout">
<form method="post"
id="o_wbooth_contact_details_form"
t-attf-action="/event/#{slug(event)}/booth/confirm"
class="col-12 px-5 py-2 js_website_submit_form">
<input type="hidden" name="csrf_token" t-att-value="request.csrf_token()"/>
<input type="hidden" name="booth_category_id" t-att-value="booth_category.id"/>
<input type="hidden" name="event_booth_ids" t-att-value="event_booths.ids"/>
<div id="o_wbooth_contact_details">
<h4 class="mt32">
<strong>Contact Details</strong>
</h4>
<div class="row form-group">
<label class="col-form-label col-sm-auto">
<span>Name</span>
<span> *</span>
</label>
<div class="col-sm">
<input type="text" class="form-control" name="contact_name" required="True"
t-att-value="default_contact.get('name', '')"/>
</div>
</div>
<div class="row form-group">
<label class="col-form-label col-sm-auto">
<span>Email</span>
<span> *</span>
</label>
<div class="col-sm">
<input type="email" class="form-control" name="contact_email" required="True"
t-att-value="default_contact.get('email', '')"/>
</div>
</div>
<div class="row form-group">
<label class="col-form-label col-sm-auto">Phone</label>
<div class="col-sm">
<input type="tel" class="form-control" name="contact_phone"
t-att-value="default_contact.get('phone', '')"/>
</div>
</div>
<div class="row form-group">
<label class="col-form-label col-sm-auto">Mobile</label>
<div class="col-sm">
<input type="tel" class="form-control" name="contact_mobile"
t-att-value="default_contact.get('mobile', '')"/>
</div>
</div>
</div>
<div class="form-group">
<div class="col-sm-6 offset-sm-3 mt-5">
<button type="submit" class="btn btn-primary btn-block font-weight-bold">Book my Booths</button>
</div>
</div>
</form>
</t>
</template>
<template id="event_booth_registration_complete" name="Event Booth Registration Complete">
<t t-call="website_event_booth.event_booth_layout">
<div class="col-12 p-5">
<div class="row mb-3">
<div class="col-12">
<h3>Booth Registration completed!</h3>
<span class="h4 text-muted" t-esc="event.name"/>
</div>
</div>
<div class="d-flex flex-column">
<span t-if="contact_name" class="font-weight-bold">
<t t-esc="contact_name"/>
</span>
<span t-if="contact_email">
<i class="fa fa-fw fa-envelope mr-2"/>
<t t-esc="contact_email"/>
</span>
<span t-if="contact_phone">
<i class="fa fa-fw fa-phone mr-2"/>
<t t-esc="contact_phone"/>
</span>
<span t-if="contact_mobile">
<i class="fa fa-fw fa-mobile mr-2"/>
<t t-esc="contact_mobile"/>
</span>
</div>
</div>
</t>
</template>
</data></odoo>
@@ -0,0 +1,124 @@
<?xml version="1.0" encoding="UTF-8" ?>
<odoo><data>
<template id="event_booth_layout" name="Event Booth Layout">
<t t-call="website_event.layout">
<div class="o_wbooth_registration" t-att-data-event-id="event.id"
itemscope="itemscope" itemtype="http://schema.org/Event">
<t t-call="website.record_cover">
<t t-set="_record" t-value="event"/>
<t t-set="use_filters" t-value="True"/>
<t t-set="use_text_align" t-value="True"/>
<t t-set="additionnal_classes" t-value="'pb128'"/>
<div class="container d-flex flex-column flex-grow-1">
<h1 class="text-white my-5">Get A Booth</h1>
<div t-if="event.is_finished" class="alert alert-info">
<span>This event is finished. It's no longer possible to book a booth.</span>
</div>
<div t-elif="not event_booths" class="alert alert-info">
<span>This event is not open to exhibitors registration,
<span t-if="request.env.user.has_group('event.group_event_manager')">
you can
<a class="text-nowrap" t-attf-href="/web#id=#{event.id}&amp;view_type=form&amp;model=event.event">
<i class="fa fa-gear mr-1" role="img" aria-label="Configure" title="Configure event booths"/><em>Configure Booths</em>
</a>
for this event.
</span>
<span t-else="">
check our <a href="/event">list of future events</a>.
</span>
</span>
</div>
</div>
</t>
<section class="mt-n5">
<div class="container overflow-hidden">
<div class="row mb64 bg-white no-gutters rounded shadow-sm">
<t t-out="0"/>
</div>
</div>
</section>
</div>
</t>
</template>
<template id="event_booth_registration" name="Event Booth Registration">
<t t-call="website_event_booth.event_booth_layout">
<t t-if="event_booths and not event.is_finished">
<div t-attf-class="#{'col-lg-9' if available_booth_category_ids else 'col-lg-12'} p-4">
<form method="post" class="form-horizontal justify-content-center mt32 js_website_submit_form o_wbooth_registration_form"
t-attf-action="/event/#{slug(event)}/booth/register" t-att-data-event-id="event.id">
<input type="hidden" name="csrf_token" t-att-value="request.csrf_token()"/>
<div class="row form-group justify-content-center">
<t t-foreach="event.event_booth_category_ids" t-as="booth_category">
<t t-set="booth_category_unavailable" t-value="booth_category not in available_booth_category_ids"/>
<div class="col-md-4">
<label t-attf-class="d-block #{'o_wbooth_category_unavailable' if booth_category_unavailable else ''}">
<input type="radio" name="booth_category_id" t-att-value="booth_category.id"
t-att-disabled="booth_category_unavailable"/>
<div>
<h5 name="booth_category_name" class="m-0 text-truncate" t-esc="booth_category.name"/>
<span class="img img-responsive">
<img class="img img-fluid mt-2" t-att-title="booth_category.name" t-att-src="image_data_uri(booth_category.image_256)"/>
</span>
</div>
<div t-if="booth_category_unavailable" class="o_ribbon_right bg-danger">
<span class="text-nowrap">Sold Out</span>
</div>
</label>
</div>
</t>
</div>
<t t-if="available_booth_category_ids">
<div t-if="event.exhibition_map" class="row">
<div class="col-sm-6 offset-sm-3 mb-4">
<button type="button" class="btn btn-info btn-block" data-toggle="modal" data-target="#mapModal">View Floor Plan</button>
<div role="dialog" id="mapModal" class="modal" tabindex="-1">
<div class="modal-dialog modal-lg">
<div class="modal-content">
<div class="modal-body">
<div t-field="event.exhibition_map" t-options="{'widget': 'image'}" class="img img-responsive"/>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="row form-group">
<div class="col-sm-2 offset-sm-1">
<label for="booth_id" class="control-label">Booths</label>
</div>
<div class="col-sm-6 o_wbooth_booths"/>
</div>
<div class="row">
<div class="alert alert-danger col-12 o_wbooth_unavailable_booth_alert d-none" role="alert">
<i class="fa fa-exclamation-triangle"/>
<span>Sorry, several booths are now sold out. Please change your choices before validating again.</span>
</div>
</div>
<div class="row" name="booth_registration_submit">
<div class="col-sm-6 offset-sm-3 mt-5">
<button type="submit" class="btn btn-primary btn-block font-weight-bold o_wbooth_registration_submit" disabled="true">
<span>Book my Booths</span>
</button>
</div>
</div>
</t>
<div t-else="" class="alert alert-info">
<span>Sorry, all the booths are sold out. <a href="/contactus">Contact Us</a> if you have any question.</span>
</div>
</form>
</div>
<div t-if="available_booth_category_ids" class="col-lg-3 bg-200 hidden-sm hidden-xs p-4">
<t t-foreach="event.event_booth_category_ids" t-as="booth_category">
<div t-attf-id="o_wbooth_booth_description_#{booth_category.id}"
class="o_wbooth_booth_description d-none" t-field="booth_category.description"/>
</t>
</div>
</t>
</t>
</template>
</data></odoo>
@@ -0,0 +1,19 @@
<?xml version="1.0" encoding="UTF-8" ?>
<odoo><data>
<record id="event_event_view_form" model="ir.ui.view">
<field name="name">event.event.view.form.inherit.website.event.booth</field>
<field name="model">event.event</field>
<field name="inherit_id" ref="website_event.event_event_view_form"/>
<field name="arch" type="xml">
<field name="address_id" position="after">
<field name="exhibition_map"/>
</field>
<field name="website_menu" position="after">
<label for="booth_menu"/>
<field name="booth_menu"/>
</field>
</field>
</record>
</data></odoo>
@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="UTF-8" ?>
<odoo><data>
<record id="event_type_view_form" model="ir.ui.view">
<field name="name">event.type.view.form.inherit.website.event.booth</field>
<field name="model">event.type</field>
<field name="inherit_id" ref="website_event.event_type_view_form"/>
<field name="arch" type="xml">
<xpath expr="//span[@name='website_menu']" position='after'>
<span>
<label for="booth_menu" string="Booth Menu Item"/>
<field name="booth_menu"/>
</span>
</xpath>
</field>
</record>
</data></odoo>
@@ -0,0 +1,5 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from . import controllers
from . import models
@@ -0,0 +1,32 @@
# -*- coding: utf-8 -*-
{
'name': 'Booths/Exhibitors Bridge',
'category': 'Marketing/Events',
'version': '1.0',
'summary': 'Event Booths, automatically create a sponsor.',
'description': """
Automatically create a sponsor when renting a booth.
""",
'depends': ['website_event_exhibitor', 'website_event_booth'],
'data': [
'data/event_booth_category_data.xml',
'views/event_booth_category_views.xml',
'views/event_booth_views.xml',
'views/event_booth_registration_templates.xml',
'views/event_booth_templates.xml',
'views/mail_templates.xml'
],
'auto_install': True,
'assets': {
'web.assets_frontend': [
'/website_event_booth_exhibitor/static/src/js/booth_sponsor_details.js',
],
'web.assets_tests': [
'website_event_booth_exhibitor/static/tests/tours/website_event_booth_exhibitor.js',
],
},
'license': 'LGPL-3',
}
@@ -0,0 +1,4 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from . import event_booth
@@ -0,0 +1,41 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
import base64
from odoo.addons.website_event.controllers.main import WebsiteEventController
from odoo.tools import plaintext2html
class WebsiteEventBoothController(WebsiteEventController):
def _prepare_booth_registration_values(self, event, kwargs):
booth_values = super(WebsiteEventBoothController, self)._prepare_booth_registration_values(event, kwargs)
if not booth_values.get('contact_email'):
booth_values['contact_email'] = kwargs.get('sponsor_email')
if not booth_values.get('contact_name'):
booth_values['contact_name'] = kwargs.get('sponsor_name')
if not booth_values.get('contact_mobile'):
booth_values['contact_mobile'] = kwargs.get('sponsor_mobile')
if not booth_values.get('contact_phone'):
booth_values['contact_phone'] = kwargs.get('sponsor_phone')
booth_values.update(**self._prepare_booth_registration_sponsor_values(event, booth_values, kwargs))
return booth_values
def _prepare_booth_registration_partner_values(self, event, kwargs):
if not kwargs.get('contact_email') and kwargs.get('sponsor_email'):
kwargs['contact_email'] = kwargs['sponsor_email']
return super(WebsiteEventBoothController, self)._prepare_booth_registration_partner_values(event, kwargs)
def _prepare_booth_registration_sponsor_values(self, event, booth_values, kwargs):
sponsor_values = {
'sponsor_name': kwargs.get('sponsor_name') or booth_values.get('contact_name'),
'sponsor_email': kwargs.get('sponsor_email') or booth_values.get('contact_email'),
'sponsor_mobile': kwargs.get('sponsor_mobile') or booth_values.get('contact_mobile'),
'sponsor_phone': kwargs.get('sponsor_phone') or booth_values.get('contact_phone'),
'sponsor_subtitle': kwargs.get('sponsor_slogan'),
'sponsor_website_description': plaintext2html(kwargs.get('sponsor_description')) if kwargs.get('sponsor_description') else '',
'sponsor_image_512': base64.b64encode(kwargs['sponsor_image'].read()) if kwargs.get('sponsor_image') else False,
}
return sponsor_values
@@ -0,0 +1,16 @@
<?xml version="1.0" encoding="UTF-8" ?>
<odoo><data>
<record id="event_booth.event_booth_category_premium" model="event.booth.category">
<field name="use_sponsor" eval="True"/>
<field name="sponsor_type_id" ref="website_event_exhibitor.event_sponsor_type2"/>
<field name="exhibitor_type">exhibitor</field>
</record>
<record id="event_booth.event_booth_category_vip" model="event.booth.category">
<field name="use_sponsor" eval="True"/>
<field name="sponsor_type_id" ref="website_event_exhibitor.event_sponsor_type3"/>
<field name="exhibitor_type">online</field>
</record>
</data></odoo>
@@ -0,0 +1,5 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from . import event_booth
from . import event_booth_category
@@ -0,0 +1,54 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import fields, models
class EventBooth(models.Model):
_inherit = 'event.booth'
use_sponsor = fields.Boolean(related='booth_category_id.use_sponsor')
sponsor_type_id = fields.Many2one(related='booth_category_id.sponsor_type_id')
sponsor_id = fields.Many2one('event.sponsor', string='Sponsor', copy=False)
sponsor_name = fields.Char(string='Sponsor Name', related='sponsor_id.name')
sponsor_email = fields.Char(string='Sponsor Email', related='sponsor_id.email')
sponsor_mobile = fields.Char(string='Sponsor Mobile', related='sponsor_id.mobile')
sponsor_phone = fields.Char(string='Sponsor Phone', related='sponsor_id.phone')
sponsor_subtitle = fields.Char(string='Sponsor Slogan', related='sponsor_id.subtitle')
sponsor_website_description = fields.Html(string='Sponsor Description', related='sponsor_id.website_description')
sponsor_image_512 = fields.Image(string='Sponsor Logo', related='sponsor_id.image_512')
def action_view_sponsor(self):
action = self.env['ir.actions.act_window']._for_xml_id('website_event_exhibitor.event_sponsor_action')
action['views'] = [(False, 'form')]
action['res_id'] = self.sponsor_id.id
return action
def _get_or_create_sponsor(self, vals):
self.ensure_one()
sponsor_id = self.env['event.sponsor'].sudo().search([
('partner_id', '=', self.partner_id.id),
('sponsor_type_id', '=', self.sponsor_type_id.id),
('exhibitor_type', '=', self.booth_category_id.exhibitor_type),
('event_id', '=', self.event_id.id),
], limit=1)
if not sponsor_id:
values = {
'event_id': self.event_id.id,
'sponsor_type_id': self.sponsor_type_id.id,
'exhibitor_type': self.booth_category_id.exhibitor_type,
'partner_id': self.partner_id.id,
**{key.partition('sponsor_')[2]: value for key, value in vals.items() if key.startswith('sponsor_')},
}
if self.booth_category_id.exhibitor_type == 'online':
values.update({
'room_name': 'odoo-exhibitor-%s' % self.partner_id.name,
})
sponsor_id = self.env['event.sponsor'].sudo().create(values)
return sponsor_id.id
def _action_post_confirm(self, write_vals):
for booth in self:
if booth.use_sponsor and booth.partner_id:
booth.sponsor_id = booth._get_or_create_sponsor(write_vals)
super(EventBooth, self)._action_post_confirm(write_vals)
@@ -0,0 +1,24 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import api, fields, models
class EventBoothCategory(models.Model):
_inherit = 'event.booth.category'
@api.model
def _get_exhibitor_type(self):
return self.env['event.sponsor']._fields['exhibitor_type'].selection
use_sponsor = fields.Boolean(string='Create Sponsor', help="If set, when booking a booth a sponsor will be created for the user")
sponsor_type_id = fields.Many2one('event.sponsor.type', string='Sponsor Level')
exhibitor_type = fields.Selection(_get_exhibitor_type, string='Sponsor Type')
@api.onchange('use_sponsor')
def _onchange_use_sponsor(self):
if self.use_sponsor:
if not self.sponsor_type_id:
self.sponsor_type_id = self.env['event.sponsor.type'].search([], order="sequence desc", limit=1).id
if not self.exhibitor_type:
self.exhibitor_type = self._get_exhibitor_type()[0][0]
@@ -0,0 +1,29 @@
odoo.define('website_event_booth_exhibitor.booth_sponsor_details', function (require) {
'use strict';
var publicWidget = require('web.public.widget');
publicWidget.registry.boothSponsorDetails = publicWidget.Widget.extend({
selector: '#o_wbooth_contact_details_form',
events: {
'click input[id="contact_details"]': '_onClickContactDetails',
},
//--------------------------------------------------------------------------
// Handler
//--------------------------------------------------------------------------
_onClickContactDetails(ev) {
this.useContactDetails = ev.currentTarget.checked;
this.$('#o_wbooth_contact_details').toggleClass('d-none', !this.useContactDetails);
this.$('label[for="sponsor_name"] > .mandatory_mark, label[for="sponsor_email"] > .mandatory_mark').toggleClass('d-none', this.useContactDetails);
this.$('input[name="contact_name"], input[name="contact_email"]').attr('required', this.useContactDetails);
},
});
return {
boothSponsorDetails: publicWidget.registry.boothSponsorDetails,
};
});
@@ -0,0 +1,62 @@
odoo.define("website_event_booth_exhibitor.tour_steps", function (require) {
"use strict";
var core = require('web.core');
var FinalSteps = core.Class.extend({
_getSteps: function () {
return [{
trigger: 'h3:contains("Booth Registration completed!")',
run: function() {},
}];
},
});
return FinalSteps;
});
odoo.define("website_event_booth_exhibitor.tour", function (require) {
"use strict";
var tour = require("web_tour.tour");
var FinalSteps = require('website_event_booth_exhibitor.tour_steps');
tour.register("webooth_exhibitor_register", {
test: true,
url: "/event",
}, [{
content: 'Go on "Online Reveal" page',
trigger: 'a[href*="/event"]:contains("Online Reveal"):first',
}, {
content: 'Browse Booths',
trigger: 'a:contains("Get A Booth")',
}, {
content: 'Choose Premium Booths',
trigger: 'img[title="Premium Booth"]',
}, {
content: 'Choose Booth',
trigger: 'div:contains("OpenWood Demonstrator 2") input',
}, {
content: "Validate attendees details",
trigger: 'button:contains("Book my Booths")',
run: 'click',
}, {
content: "Fill booth details",
trigger: 'form[id="o_wbooth_contact_details_form"]',
run: function () {
$("input[name='sponsor_name']").val("Patrick Sponsor");
$("input[name='sponsor_email']").val("patrick.sponssor@test.example.com");
$("input[name='sponsor_phone']").val("+32456001122");
$("input[name='sponsor_slogan']").val("Patrick is Your Sponsor");
$("textarea[name='sponsor_description']").text("Really eager to meet you !");
},
}, {
content: "Validate booth details",
extra_trigger: "input[name='sponsor_name'], input[name='sponsor_email'], input[name='sponsor_phone']",
trigger: 'button:contains("Book my Booths")',
run: 'click',
}, ...new FinalSteps()._getSteps()].filter(Boolean));
});
@@ -0,0 +1,4 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from . import test_wevent_booth_exhibitor
@@ -0,0 +1,17 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo.addons.base.tests.common import HttpCaseWithUserDemo, HttpCaseWithUserPortal
from odoo.tests import tagged
@tagged('post_install', '-at_install')
class TestWEventBoothExhibitorCommon(HttpCaseWithUserDemo, HttpCaseWithUserPortal):
def test_register(self):
self.browser_js(
'/event',
'odoo.__DEBUG__.services["web_tour.tour"].run("webooth_exhibitor_register")',
'odoo.__DEBUG__.services["web_tour.tour"].tours.webooth_exhibitor_register.ready',
login='admin'
)
@@ -0,0 +1,51 @@
<?xml version="1.0" encoding="UTF-8" ?>
<odoo><data>
<record id="event_booth_category_view_form" model="ir.ui.view">
<field name="name">event.booth.category.view.form.inherit.website.event.booth.exhibitor</field>
<field name="model">event.booth.category</field>
<field name="inherit_id" ref="event_booth.event_booth_category_view_form"/>
<field name="priority" eval="2"/>
<field name="arch" type="xml">
<group name="main" position="inside">
<group string="Sponsorship" name="sponsor">
<field name="use_sponsor"/>
<field name="sponsor_type_id" attrs="{'invisible': [('use_sponsor', '=', False)], 'required': [('use_sponsor', '=', True)]}"/>
<field name="exhibitor_type" attrs="{'invisible': [('use_sponsor', '=', False)], 'required': [('use_sponsor', '=', True)]}"/>
</group>
</group>
</field>
</record>
<record id="event_booth_category_view_tree" model="ir.ui.view">
<field name="name">event.booth.category.view.tree.inherit.website.event.booth.exhibitor</field>
<field name="model">event.booth.category</field>
<field name="inherit_id" ref="event_booth.event_booth_category_view_tree"/>
<field name="priority">5</field>
<field name="arch" type="xml">
<field name="name" position="after">
<field name="use_sponsor"/>
<field name="sponsor_type_id" optional="hide"/>
<field name="exhibitor_type" optional="hide"/>
</field>
</field>
</record>
<record id="event_booth_category_view_search" model="ir.ui.view">
<field name="name">event.booth.category.view.search.inherit.website.event.booth.exhibitor</field>
<field name="model">event.booth.category</field>
<field name="inherit_id" ref="event_booth.event_booth_category_view_search"/>
<field name="arch" type="xml">
<xpath expr="//field[@name='name']" position="after">
<field name="use_sponsor"/>
<field name="sponsor_type_id"/>
<field name="exhibitor_type"/>
<group expand="0" string="Group By">
<filter name="group_by_sponsor_type" string="Sponsor type" context="{'group_by': 'sponsor_type_id'}"/>
<filter name="group_by_exhibitor_type" string="Exhibitor type" context="{'group_by':'exhibitor_type'}"/>
</group>
</xpath>
</field>
</record>
</data></odoo>
@@ -0,0 +1,75 @@
<?xml version="1.0" encoding="UTF-8" ?>
<odoo><data>
<template id="event_booth_registration_details" inherit_id="website_event_booth.event_booth_registration_details">
<form id="o_wbooth_contact_details_form" position="attributes">
<attribute name="enctype">multipart/form-data</attribute>
</form>
<xpath expr="//div[@id='o_wbooth_contact_details']" position="before">
<div id="o_wbooth_sponsor_details" t-if="booth_category.use_sponsor">
<h4 class="mt32">
<strong>Sponsor Details</strong>
</h4>
<div class="form-text text-muted mb16">
This booth type allows you to have visibility on the event website. Please fill in this form
</div>
<div class="row form-group">
<label class="col-form-label col-sm-auto" for="sponsor_name">
<span>Name</span>
<span class="mandatory_mark"> *</span>
</label>
<div class="col-sm">
<input class="form-control" type="text" name="sponsor_name" id="sponsor_name" required="True"/>
</div>
</div>
<div class="row form-group">
<label class="col-form-label col-sm-auto" for="sponsor_email">
<span>Email</span>
<span class="mandatory_mark"> *</span>
</label>
<div class="col-sm">
<input class="form-control" type="email" name="sponsor_email" id="sponsor_email" required="True"/>
</div>
</div>
<div class="row form-group">
<label class="col-form-label col-sm-auto" for="sponsor_phone">Phone</label>
<div class="col-sm">
<input class="form-control" type="text" name="sponsor_phone" id="sponsor_phone"/>
</div>
</div>
<div class="row form-group">
<label class="col-form-label col-sm-auto" for="sponsor_slogan">Slogan</label>
<div class="col-sm">
<input class="form-control" type="text" name="sponsor_slogan" id="sponsor_slogan"/>
</div>
</div>
<div class="row form-group" t-if="booth_category.exhibitor_type != 'sponsor'">
<label class="col-form-label col-sm-auto" for="sponsor_description">Description</label>
<div class="col-sm">
<textarea class="form-control" name="sponsor_description" id="sponsor_description"/>
</div>
</div>
<div class="row form-group">
<label class="col-form-label col-sm-auto" for="sponsor_image">Picture</label>
<div class="col-sm">
<input name="sponsor_image" type="file" accept="image/*"/>
</div>
</div>
<div class="form-check">
<input class="form-check-input" type="checkbox" id="contact_details"/>
<label class="font-weight-normal" for="contact_details">Contact me through a different email/phone.</label>
</div>
</div>
</xpath>
<xpath expr="//div[@id='o_wbooth_contact_details']" position="attributes">
<attribute name="t-att-class">'d-none' if booth_category.use_sponsor else ''</attribute>
</xpath>
<xpath expr="//input[@name='contact_name']" position="attributes">
<attribute name="t-att-required">False if booth_category.use_sponsor else True</attribute>
</xpath>
<xpath expr="//input[@name='contact_email']" position="attributes">
<attribute name="t-att-required">False if booth_category.use_sponsor else True</attribute>
</xpath>
</template>
</data></odoo>
@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8" ?>
<odoo><data>
<template id="event_booth_registration" inherit_id="website_event_booth.event_booth_registration">
<xpath expr="//input[@name='booth_category_id']" position="attributes">
<attribute name="t-att-data-use-sponsor">'true' if booth_category.use_sponsor else 'false'</attribute>
</xpath>
</template>
</data></odoo>
@@ -0,0 +1,30 @@
<?xml version="1.0" encoding="UTF-8" ?>
<odoo><data>
<record id="event_booth_view_form_from_event" model="ir.ui.view">
<field name="name">event.booth.view.form.inherit.website.event.booth.exhibitor</field>
<field name="model">event.booth</field>
<field name="inherit_id" ref="event_booth.event_booth_view_form_from_event"/>
<field name="priority">5</field>
<field name="arch" type="xml">
<div name="button_box" position="inside">
<field name="sponsor_id" invisible="1"/>
<button name="action_view_sponsor" type="object" class="oe_stat_button"
icon="fa-black-tie" string="Sponsor" attrs="{'invisible': [('sponsor_id', '=', False)]}">
</button>
</div>
</field>
</record>
<record id="event_booth_view_search" model="ir.ui.view">
<field name="name">event.booth.view.search.inherit.website.event.booth.exhibitor</field>
<field name="model">event.booth</field>
<field name="inherit_id" ref="event_booth.event_booth_view_search"/>
<field name="arch" type="xml">
<xpath expr="//filter[@name='group_by_booth_category_id']" position="after">
<filter string="Sponsor" name="group_by_sponsor_id" context="{'group_by': 'sponsor_id'}"/>
</xpath>
</field>
</record>
</data></odoo>
@@ -0,0 +1,13 @@
<?xml version="1.0" encoding="UTF-8" ?>
<odoo><data>
<template id="event_booth_booked_template" inherit_id="event_booth.event_booth_booked_template">
<ul name="contact_details" position="inside">
<t t-set="renter_sponsor" t-value="booth.sponsor_id"/>
<li t-if="renter_sponsor">
<b>Sponsor</b>: <a href="#" t-att-data-oe-model="booth.sponsor_id._name" t-att-data-oe-id="booth.sponsor_id.id" t-esc="booth.sponsor_id.name"/>
</li>
</ul>
</template>
</data></odoo>
@@ -0,0 +1,5 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from . import controllers
from . import models
@@ -0,0 +1,23 @@
# -*- coding: utf-8 -*-
{
'name': 'Online Event Booth Sale',
'category': 'Marketing/Events',
'version': '1.0',
'summary': 'Events, sell your booths online',
'description': """
Use the e-commerce to sell your event booths.
""",
'depends': ['event_booth_sale', 'website_event_booth', 'website_sale'],
'data': [
'views/event_booth_templates.xml',
],
'demo': [],
'auto_install': True,
'assets': {
'web.assets_frontend': [
'/website_event_booth_sale/static/src/js/booth_register.js',
]
},
'license': 'LGPL-3',
}
@@ -0,0 +1,4 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from . import event_booth
@@ -0,0 +1,39 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
import werkzeug
from odoo import http
from odoo.http import request
from odoo.addons.website_event.controllers.main import WebsiteEventController
class WebsiteEventBoothController(WebsiteEventController):
@http.route()
def event_booth_main(self, event):
pricelist = request.website.get_current_pricelist()
if pricelist:
event = event.with_context(pricelist=pricelist.id)
return super(WebsiteEventBoothController, self).event_booth_main(event)
@http.route()
def event_booth_registration_confirm(self, event, booth_category_id, event_booth_ids, **kwargs):
booths = self._get_requested_booths(event, event_booth_ids)
booth_category = request.env['event.booth.category'].sudo().browse(int(booth_category_id))
order = request.website.sale_get_order(force_create=1)
order._cart_update(
product_id=booth_category.product_id.id,
set_qty=1,
event_booth_pending_ids=booths.ids,
registration_values=self._prepare_booth_registration_values(event, kwargs),
)
if order.amount_total:
return request.redirect('/shop/checkout')
elif order:
order.action_confirm()
request.website.sale_reset()
return request.redirect(('/event/%s/booth/success?' % event.id) + werkzeug.urls.url_encode({
'booths': ','.join([str(id) for id in booths.ids]),
}))
@@ -0,0 +1,5 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from . import sale_order
from . import website

Some files were not shown because too many files have changed in this diff Show More