[ADD] event_booth_sale: add sale capabilities to event_booth
PURPOSE This module allows to sell booths to partners by setting a product and a price to the booths categories. When selling booths on a sale order it will open the product configurator and ask the event, booth category and booths. It's only possible to select booths belonging to the same category for one sale order line. A new model is introduced by this module : - Booth Registration that allows multiple registrations to a booth. The booth will be set unavailable when the sale order linked to the registration is paid. All the others registrations will be cancelled LINKS Task-2318655 Closes : odoo/odoo#71104
This commit is contained in:
@@ -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>
|
||||
Reference in New Issue
Block a user