[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:
@@ -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>
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from . import models
|
||||
@@ -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 & 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 & 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 & 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>
|
||||
@@ -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>
|
||||
@@ -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
|
||||
@@ -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')
|
||||
@@ -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')
|
||||
@@ -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
|
||||
|
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 |
@@ -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
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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
|
||||
@@ -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']
|
||||
@@ -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
|
||||
|
@@ -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
|
||||
@@ -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>
|
||||
@@ -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',
|
||||
],
|
||||
|
||||
@@ -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
|
||||
|
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}&view_type=form&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,
|
||||
};
|
||||
|
||||
});
|
||||
+62
@@ -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
Reference in New Issue
Block a user