summaryrefslogtreecommitdiff
path: root/addons
diff options
context:
space:
mode:
authorThibault Delavallée <tde@odoo.com>2020-01-17 12:57:52 +0000
committerThibault Delavallée <tde@odoo.com>2020-01-30 15:18:08 +0000
commit6b09c1b8c62fc8b5bbd210d4759561ec67269155 (patch)
treedc94f8c1f2922bf05762a7a4cfadb5dd1214195d /addons
parentec121565ccc5b9ab42bf381db15a08c98e76afba (diff)
[REF] event: move ticket definition from event_sale to event and use ticket templates
PURPOSE Integration between event and eCommerce is required only when users handle the entire selling process online. However users may require ticketing support while managing payments outside of Odoo. Purpose of this commit is to support tickets directly in event application without need of sales. RATIONALE Remove the need to have event_sale installed to manage basic multi ticket event type. Integration with eCommerce is needed only when one wants to handle the entire selling flow online, i.e. order, payment, ... Integration with Sales is needed only when one wants to create sale orders linked to attendees. Many event users do not need all of this. Their attendees pay through bank transfers or they simply manage payments outside of Odoo while still requiring tickets management. SPECIFICATIONS Remove the need to have event_sale installed to manage basic ticketing on events. Move ticket model (event.event.ticket) directly into event, copying most fields from event_sale. Only sale specific fields and behavior should be kept in event_sale : * keep product_id and price information in event_sale; * keep sales analysis in event_sale; We also split tickets model used for event type (event.type.ticket) and events (event.event.ticket). Indeed previously to this commit both are modeled in the same table, with the following issues : * tickets on templates use only a subset of fields: name, seats availability, product, price; * a ticket has either an event_id, either an event_type_id, and there are constraints to try to avoid having lost tickets. This leads to a strange model where m2o fields are required only in some cases with a dual behavior; * tickets are not shared between event.type and event.event. They are copied and having a single model is therefore not necessary; We therefore choose to have a light model for event.type.ticket. It is linked to event.type when configuring template tickets. They are copied in the onchange copying event template configuration to the event itself, leading to event.event.ticket creation. Some tests are moved / completed accordingly. Access rights are copied from website_event_sale to website_even concerning ticket access for public / portal. Currently they are kept as they are with some rewording as it is not the purpose of this commit to rewrite them. LINKS Task ID 2177281 Community PR odoo/odoo#43488
Diffstat (limited to 'addons')
-rw-r--r--addons/event/__manifest__.py1
-rw-r--r--addons/event/data/event_data.xml6
-rw-r--r--addons/event/models/__init__.py1
-rw-r--r--addons/event/models/event_event.py45
-rw-r--r--addons/event/models/event_registration.py15
-rw-r--r--addons/event/models/event_ticket.py139
-rw-r--r--addons/event/security/ir.model.access.csv4
-rw-r--r--addons/event/tests/common.py7
-rw-r--r--addons/event/tests/test_event_internals.py110
-rw-r--r--addons/event/views/event_ticket_views.xml178
-rw-r--r--addons/event/views/event_views.xml36
-rw-r--r--addons/event/views/res_config_settings_views.xml2
-rw-r--r--addons/event_sale/data/event_sale_data.xml7
-rw-r--r--addons/event_sale/models/event_event.py57
-rw-r--r--addons/event_sale/models/event_registration.py14
-rw-r--r--addons/event_sale/models/event_ticket.py182
-rw-r--r--addons/event_sale/models/sale_order.py2
-rw-r--r--addons/event_sale/security/ir.model.access.csv2
-rw-r--r--addons/event_sale/tests/test_event_internals.py128
-rw-r--r--addons/event_sale/views/event_registration_views.xml23
-rw-r--r--addons/event_sale/views/event_ticket_views.xml141
-rw-r--r--addons/event_sale/views/event_views.xml38
-rw-r--r--addons/website_event/security/event_security.xml11
-rw-r--r--addons/website_event/security/ir.model.access.csv4
-rw-r--r--addons/website_event_sale/data/event_data.xml2
-rw-r--r--addons/website_event_sale/models/sale_order.py2
-rw-r--r--addons/website_event_sale/security/ir.model.access.csv3
-rw-r--r--addons/website_event_sale/security/website_event_sale_security.xml11
28 files changed, 780 insertions, 391 deletions
diff --git a/addons/event/__manifest__.py b/addons/event/__manifest__.py
index f58fc32fb75..b71e33db688 100644
--- a/addons/event/__manifest__.py
+++ b/addons/event/__manifest__.py
@@ -22,6 +22,7 @@ Key Features
'security/event_security.xml',
'security/ir.model.access.csv',
'views/event_menu_views.xml',
+ 'views/event_ticket_views.xml',
'views/event_views.xml',
'views/event_stage_views.xml',
'report/event_event_templates.xml',
diff --git a/addons/event/data/event_data.xml b/addons/event/data/event_data.xml
index 03df5f2923a..a9ab41942a6 100644
--- a/addons/event/data/event_data.xml
+++ b/addons/event/data/event_data.xml
@@ -27,6 +27,12 @@
<field name="auto_confirm" eval="True"/>
<field name="use_mail_schedule" eval="False"/>
</record>
+ <record id="event_type_data_ticket" model="event.type">
+ <field name="name">Ticketing</field>
+ <field name="auto_confirm" eval="False"/>
+ <field name="use_mail_schedule" eval="True"/>
+ <field name="use_ticket" eval="True"/>
+ </record>
<!-- Event stages -->
<record id="event_stage_new" model="event.stage">
diff --git a/addons/event/models/__init__.py b/addons/event/models/__init__.py
index 727b34645d7..943b57206f9 100644
--- a/addons/event/models/__init__.py
+++ b/addons/event/models/__init__.py
@@ -5,6 +5,7 @@ from . import event_event
from . import event_mail
from . import event_registration
from . import event_stage
+from . import event_ticket
from . import res_config_settings
from . import res_partner
from . import ir_autovacuum
diff --git a/addons/event/models/event_event.py b/addons/event/models/event_event.py
index 2a3a4597c9d..263b3153941 100644
--- a/addons/event/models/event_event.py
+++ b/addons/event/models/event_event.py
@@ -41,6 +41,12 @@ class EventType(models.Model):
name = fields.Char('Event Category', required=True, translate=True)
sequence = fields.Integer()
+ # tickets
+ use_ticket = fields.Boolean('Ticketing')
+ event_type_ticket_ids = fields.One2many(
+ 'event.type.ticket', 'event_type_id',
+ string='Tickets', compute='_compute_event_type_ticket_ids',
+ readonly=False, store=True)
# registration
has_seats_limitation = fields.Boolean('Limited Seats')
default_registration_min = fields.Integer(
@@ -69,6 +75,16 @@ class EventType(models.Model):
copy=False,
default=lambda self: self._get_default_event_type_mail_ids())
+ @api.depends('use_ticket')
+ def _compute_event_type_ticket_ids(self):
+ for template in self:
+ if not template.use_ticket:
+ template.event_type_ticket_ids = [(5, 0)]
+ elif not template.event_type_ticket_ids:
+ template.event_type_ticket_ids = [(0, 0, {
+ 'name': _('Registration'),
+ })]
+
@api.onchange('has_seats_limitation')
def _onchange_has_seats_limitation(self):
if not self.has_seats_limitation:
@@ -143,6 +159,9 @@ class EventEvent(models.Model):
auto_confirm = fields.Boolean(string='Autoconfirm Registrations')
registration_ids = fields.One2many('event.registration', 'event_id', string='Attendees')
event_registrations_open = fields.Boolean('Registration open', compute='_compute_event_registrations_open')
+ event_ticket_ids = fields.One2many(
+ 'event.event.ticket', 'event_id', string='Event Ticket',
+ copy=True)
# Date fields
date_tz = fields.Selection(
_tz_get, string='Timezone', required=True,
@@ -152,6 +171,7 @@ class EventEvent(models.Model):
date_begin_located = fields.Char(string='Start Date Located', compute='_compute_date_begin_tz')
date_end_located = fields.Char(string='End Date Located', compute='_compute_date_end_tz')
is_one_day = fields.Boolean(compute='_compute_field_is_one_day')
+ start_sale_date = fields.Date('Start sale date', compute='_compute_start_sale_date')
# Location and communication
is_online = fields.Boolean('Online Event')
address_id = fields.Many2one(
@@ -196,10 +216,12 @@ class EventEvent(models.Model):
event.seats_available = event.seats_max - (event.seats_reserved + event.seats_used)
event.seats_expected = event.seats_unconfirmed + event.seats_reserved + event.seats_used
- @api.depends('date_end', 'seats_available', 'seats_availability')
+ @api.depends('date_end', 'seats_available', 'seats_availability', 'event_ticket_ids.sale_available')
def _compute_event_registrations_open(self):
for event in self:
- event.event_registrations_open = event.date_end > fields.Datetime.now() and (event.seats_available or event.seats_availability == 'unlimited')
+ event.event_registrations_open = (event.date_end > fields.Datetime.now()) and \
+ (event.seats_available or event.seats_availability == 'unlimited') and \
+ (not event.event_ticket_ids or any(ticket.sale_available for ticket in event.event_ticket_ids))
@api.depends('stage_id', 'kanban_state')
def _compute_kanban_state_label(self):
@@ -239,6 +261,12 @@ 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('event_ticket_ids.start_sale_date')
+ def _compute_start_sale_date(self):
+ for event in self:
+ start_dates = [ticket.start_sale_date for ticket in event.event_ticket_ids if ticket.start_sale_date]
+ event.start_sale_date = min(start_dates) if start_dates else False
+
@api.onchange('is_online')
def _onchange_is_online(self):
if self.is_online:
@@ -271,6 +299,19 @@ class EventEvent(models.Model):
})
for line in self.event_type_id.event_type_mail_ids]
+ # compute tickets information
+ if self.event_type_id.use_ticket:
+ all_ticket_values = []
+ for ticket in self.event_type_id.event_type_ticket_ids:
+ ticket_vals = dict(
+ (attribute_name, ticket[attribute_name] if not isinstance(ticket[attribute_name], models.BaseModel) else ticket[attribute_name].id)
+ for attribute_name in self.env['event.type.ticket']._get_event_ticket_fields_whitelist()
+ )
+ ticket_vals['name'] = self.name and _('Registration for %s') % self.name or ticket.name
+ all_ticket_values.append(ticket_vals)
+
+ self.event_ticket_ids = [(5, 0, 0)] + [(0, 0, item) for item in all_ticket_values]
+
@api.constrains('seats_min', 'seats_max', 'seats_availability')
def _check_seats_min_max(self):
if any(event.seats_availability == 'limited' and event.seats_min > event.seats_max for event in self):
diff --git a/addons/event/models/event_registration.py b/addons/event/models/event_registration.py
index e8789776628..575e3442786 100644
--- a/addons/event/models/event_registration.py
+++ b/addons/event/models/event_registration.py
@@ -22,6 +22,9 @@ class EventRegistration(models.Model):
event_id = fields.Many2one(
'event.event', string='Event', required=True,
readonly=True, states={'draft': [('readonly', False)]})
+ event_ticket_id = fields.Many2one(
+ 'event.event.ticket', string='Event Ticket', readonly=True,
+ states={'draft': [('readonly', False)]})
# attendee
partner_id = fields.Many2one(
'res.partner', string='Contact',
@@ -43,12 +46,24 @@ class EventRegistration(models.Model):
('open', 'Confirmed'), ('done', 'Attended')],
string='Status', default='draft', readonly=True, copy=False, tracking=True)
+ @api.onchange('event_id')
+ def _onchange_event_id(self):
+ # We reset the ticket when keeping it would lead to an inconstitent state.
+ if self.event_ticket_id and (not self.event_id or self.event_id != self.event_ticket_id.event_id):
+ self.event_ticket_id = None
+
@api.constrains('event_id', 'state')
def _check_seats_limit(self):
for registration in self:
if registration.event_id.seats_availability == 'limited' and registration.event_id.seats_max and registration.event_id.seats_available < (1 if registration.state == 'draft' else 0):
raise ValidationError(_('No more seats available for this event.'))
+ @api.constrains('event_ticket_id', 'state')
+ def _check_ticket_seats_limit(self):
+ for record in self:
+ if record.event_ticket_id.seats_max and record.event_ticket_id.seats_available < 0:
+ raise ValidationError(_('No more available seats for this ticket'))
+
@api.onchange('partner_id')
def _onchange_partner(self):
if self.partner_id:
diff --git a/addons/event/models/event_ticket.py b/addons/event/models/event_ticket.py
new file mode 100644
index 00000000000..acdc899e3d1
--- /dev/null
+++ b/addons/event/models/event_ticket.py
@@ -0,0 +1,139 @@
+# -*- 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, UserError
+
+
+class EventTemplateTicket(models.Model):
+ _name = 'event.type.ticket'
+ _description = 'Event Template Ticket'
+
+ # description
+ name = fields.Char(
+ string='Name', default=lambda self: _('Registration'),
+ required=True, translate=True)
+ event_type_id = fields.Many2one(
+ 'event.type', string='Event Category', ondelete='cascade', required=True)
+ # sale
+ start_sale_date = fields.Date(string="Registration Start")
+ end_sale_date = fields.Date(string="Registration End")
+ is_expired = fields.Boolean(string='Is Expired', compute='_compute_is_expired')
+ sale_available = fields.Boolean(string='Is Available', compute='_compute_sale_available')
+ # seats
+ seats_availability = fields.Selection([
+ ('limited', 'Limited'), ('unlimited', 'Unlimited')], string='Seats Limit',
+ readonly=True, store=True, compute='_compute_seats_availability')
+ seats_max = fields.Integer(
+ string='Maximum Seats',
+ help="Define the number of available tickets. If you have too many registrations you will "
+ "not be able to sell tickets anymore. Set 0 to ignore this rule set as unlimited.")
+
+ @api.depends('end_sale_date', 'event_type_id')
+ def _compute_is_expired(self):
+ for ticket in self:
+ if ticket.end_sale_date:
+ current_date = fields.Date.context_today(ticket.with_context(tz=ticket._get_ticket_tz()))
+ ticket.is_expired = ticket.end_sale_date < current_date
+ else:
+ ticket.is_expired = False
+
+ @api.depends('start_sale_date', 'end_sale_date', 'event_type_id')
+ def _compute_sale_available(self):
+ for ticket in self:
+ current_date = fields.Date.context_today(ticket.with_context(tz=ticket._get_ticket_tz()))
+ if (ticket.start_sale_date and ticket.start_sale_date > current_date) or \
+ ticket.end_sale_date and ticket.end_sale_date < current_date:
+ ticket.sale_available = False
+ else:
+ ticket.sale_available = True
+
+ @api.depends('seats_max')
+ def _compute_seats_availability(self):
+ for ticket in self:
+ ticket.seats_availability = 'limited' if ticket.seats_max else 'unlimited'
+
+ @api.constrains('start_sale_date', 'end_sale_date')
+ def _constrains_dates_coherency(self):
+ for ticket in self:
+ if ticket.start_sale_date and ticket.end_sale_date and ticket.start_sale_date > ticket.end_sale_date:
+ raise UserError(_('The stop date cannot be earlier than the start date.'))
+
+ def _get_ticket_tz(self):
+ return self.event_type_id.use_timezone and self.event_type_id.default_timezone or self.env.user.tz
+
+ @api.model
+ def _get_event_ticket_fields_whitelist(self):
+ """ Whitelist of fields that are copied from event_type_ticket_ids to event_ticket_ids when
+ changing the event_type_id field of event.event """
+ return ['name']
+
+
+class EventTicket(models.Model):
+ """ Ticket model allowing to have differnt kind of registrations for a given
+ event. Ticket are based on ticket type as they share some common fields
+ and behavior. Those models come from <= v13 Odoo event.event.ticket that
+ modeled both concept: tickets for event templates, and tickets for events. """
+ _name = 'event.event.ticket'
+ _inherit = 'event.type.ticket'
+ _description = 'Event Ticket'
+
+ # description
+ event_type_id = fields.Many2one(ondelete='set null', required=False)
+ event_id = fields.Many2one(
+ 'event.event', string="Event",
+ ondelete='cascade', required=True)
+ company_id = fields.Many2one('res.company', related='event_id.company_id')
+ # sale
+ registration_ids = fields.One2many('event.registration', 'event_ticket_id', string='Registrations')
+ # seats
+ seats_reserved = fields.Integer(string='Reserved Seats', compute='_compute_seats', store=True)
+ seats_available = fields.Integer(string='Available Seats', compute='_compute_seats', store=True)
+ seats_unconfirmed = fields.Integer(string='Unconfirmed Seats', compute='_compute_seats', store=True)
+ seats_used = fields.Integer(string='Used Seats', compute='_compute_seats', store=True)
+
+ @api.depends('seats_max', 'registration_ids.state')
+ def _compute_seats(self):
+ """ Determine reserved, available, reserved but unconfirmed and used seats. """
+ # initialize fields to 0 + compute seats availability
+ for ticket in self:
+ ticket.seats_unconfirmed = ticket.seats_reserved = ticket.seats_used = ticket.seats_available = 0
+ # aggregate registrations by ticket and by state
+ if self.ids:
+ state_field = {
+ 'draft': 'seats_unconfirmed',
+ 'open': 'seats_reserved',
+ 'done': 'seats_used',
+ }
+ query = """ SELECT event_ticket_id, state, count(event_id)
+ FROM event_registration
+ WHERE event_ticket_id IN %s AND state IN ('draft', 'open', 'done')
+ GROUP BY event_ticket_id, state
+ """
+ self.env['event.registration'].flush(['event_id', 'event_ticket_id', 'state'])
+ self.env.cr.execute(query, (tuple(self.ids),))
+ for event_ticket_id, state, num in self.env.cr.fetchall():
+ ticket = self.browse(event_ticket_id)
+ ticket[state_field[state]] += num
+ # compute seats_available
+ for ticket in self:
+ if ticket.seats_max > 0:
+ ticket.seats_available = ticket.seats_max - (ticket.seats_reserved + ticket.seats_used)
+
+ @api.constrains('seats_available', 'seats_max')
+ def _constrains_seats_available(self):
+ if any(record.seats_max and record.seats_available < 0 for record in self):
+ raise ValidationError(_('No more available seats for this ticket.'))
+
+ def _get_ticket_multiline_description(self):
+ """ Compute a multiline description of this ticket. It is used when ticket
+ description are necessary without having to encode it manually, like sales
+ information. """
+ self.ensure_one()
+ lines = [self.display_name]
+ if self.event_id:
+ lines.append(self.event_id.display_name)
+ return '\n'.join(lines)
+
+ def _get_ticket_tz(self):
+ return self.event_id.date_tz or self.env.user.tz
diff --git a/addons/event/security/ir.model.access.csv b/addons/event/security/ir.model.access.csv
index d00f94350cc..9d680924f5d 100644
--- a/addons/event/security/ir.model.access.csv
+++ b/addons/event/security/ir.model.access.csv
@@ -1,9 +1,13 @@
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_event_type,event.type,model_event_type,event.group_event_user,1,0,0,0
access_event_type_manager,event.type manager,model_event_type,event.group_event_manager,1,1,1,1
+access_event_type_ticket,event.type.ticket.user,model_event_type_ticket,event.group_event_user,1,0,0,0
+access_event_type_ticket_manager,event.type.ticket.manager,model_event_type_ticket,event.group_event_manager,1,1,1,1
access_event_event_portal,event.event.portal,model_event_event,,1,0,0,0
access_event_event_user,event.event.user,model_event_event,event.group_event_user,1,0,0,0
access_event_event_manager,event.event.manager,model_event_event,event.group_event_manager,1,1,1,1
+access_event_event_ticket_user,event.event.ticket.user,model_event_event_ticket,event.group_event_user,1,0,0,0
+access_event_event_ticket_manager,event.event.ticket.manager,model_event_event_ticket,event.group_event_manager,1,1,1,1
access_event_registration,event.registration,model_event_registration,event.group_event_user,1,1,1,1
access_event_registration_employee,event.registration,model_event_registration,base.group_user,1,0,0,0
access_event_registration_portal,event.registration,model_event_registration,base.group_portal,1,0,0,0
diff --git a/addons/event/tests/common.py b/addons/event/tests/common.py
index 44aabb1d5d5..9dc01aa8011 100644
--- a/addons/event/tests/common.py
+++ b/addons/event/tests/common.py
@@ -59,6 +59,13 @@ class TestEventCommon(common.SavepointCase):
'default_timezone': 'Europe/Paris',
'use_hashtag': True,
'default_hashtag': 'amazing',
+ 'use_ticket': True,
+ 'event_type_ticket_ids': [(0, 0, {
+ 'name': 'First Ticket',
+ }), (0, 0, {
+ 'name': 'Second Ticket',
+ })
+ ],
'use_mail_schedule': True,
'event_type_mail_ids': [
(0, 0, { # right at subscription
diff --git a/addons/event/tests/test_event_internals.py b/addons/event/tests/test_event_internals.py
index 1f388eee3d8..779f2ea3445 100644
--- a/addons/event/tests/test_event_internals.py
+++ b/addons/event/tests/test_event_internals.py
@@ -1,11 +1,12 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
-from datetime import datetime, timedelta
+from datetime import date, datetime, timedelta
from unittest.mock import patch
from odoo.addons.event.tests.common import TestEventCommon
-from odoo.fields import Datetime as FieldsDatetime
+from odoo import exceptions
+from odoo.fields import Datetime as FieldsDatetime, Date as FieldsDate
from odoo.tests.common import users
from odoo.tools import mute_logger
@@ -149,6 +150,105 @@ class TestEventData(TestEventCommon):
self.assertEqual(event.event_mail_ids.interval_type, 'before_event')
self.assertEqual(event.event_mail_ids.template_id, self.env.ref('event.event_reminder'))
+ @users('user_eventmanager')
+ def test_event_registrable(self):
+ """Test if `_compute_event_registrations_open` works properly."""
+ event = self.event_0.with_user(self.env.user)
+ self.assertTrue(event.event_registrations_open)
+
+ # ticket without dates boundaries -> ok
+ ticket = self.env['event.event.ticket'].create({
+ 'name': 'TestTicket',
+ 'event_id': event.id,
+ })
+ self.assertTrue(event.event_registrations_open)
+
+ # even with valid tickets, date limits registrations
+ event.write({
+ 'date_begin': datetime.now() - timedelta(days=3),
+ 'date_end': datetime.now() - timedelta(days=1),
+ })
+ self.assertFalse(event.event_registrations_open)
+
+ # no more seats available
+ registration = self.env['event.registration'].create({
+ 'name': 'Albert Test',
+ 'event_id': event.id,
+ })
+ registration.action_confirm()
+ event.write({
+ 'date_end': datetime.now() + timedelta(days=3),
+ 'seats_max': 1,
+ 'seats_availability': 'limited',
+ })
+ self.assertEqual(event.seats_available, 0)
+ self.assertFalse(event.event_registrations_open)
+
+ # seats available are back
+ registration.unlink()
+ self.assertEqual(event.seats_available, 1)
+ self.assertTrue(event.event_registrations_open)
+
+ # but tickets are expired
+ ticket.write({'end_sale_date': datetime.now() - timedelta(days=2)})
+ self.assertTrue(ticket.is_expired)
+ self.assertFalse(event.event_registrations_open)
+
+
+class TestEventTicketData(TestEventCommon):
+
+ def setUp(self):
+ super(TestEventTicketData, self).setUp()
+ self.ticket_date_patcher = patch('odoo.addons.event.models.event_ticket.fields.Date', wraps=FieldsDate)
+ self.ticket_date_patcher_mock = self.ticket_date_patcher.start()
+ self.ticket_date_patcher_mock.context_today.return_value = date(2020, 1, 31)
+
+ def tearDown(self):
+ super(TestEventTicketData, self).tearDown()
+ self.ticket_date_patcher.stop()
+
+ @users('user_eventmanager')
+ def test_event_ticket_fields(self):
+ """ Test event ticket fields synchronization """
+ self.event_type_complex.write({
+ 'use_ticket': True,
+ 'event_type_ticket_ids': [
+ (5, 0),
+ (0, 0, {
+ 'name': 'First Ticket',
+ 'seats_max': 30,
+ }), (0, 0, { # limited in time, available (01/10 (start) < 01/31 (today) < 02/10 (end))
+ 'name': 'Second Ticket',
+ 'start_sale_date': date(2020, 1, 10),
+ 'end_sale_date': date(2020, 2, 10),
+ })
+ ],
+ })
+ first_ticket = self.event_type_complex.event_type_ticket_ids.filtered(lambda t: t.name == 'First Ticket')
+ second_ticket = self.event_type_complex.event_type_ticket_ids.filtered(lambda t: t.name == 'Second Ticket')
+
+ self.assertEqual(first_ticket.seats_availability, 'limited')
+ self.assertTrue(first_ticket.sale_available)
+ self.assertFalse(first_ticket.is_expired)
+
+ self.assertEqual(second_ticket.seats_availability, 'unlimited')
+ self.assertTrue(second_ticket.sale_available)
+ self.assertFalse(second_ticket.is_expired)
+ # sale is ended
+ second_ticket.write({'end_sale_date': date(2020, 1, 20)})
+ self.assertFalse(second_ticket.sale_available)
+ self.assertTrue(second_ticket.is_expired)
+ # sale has not started
+ second_ticket.write({
+ 'start_sale_date': date(2020, 2, 10),
+ 'end_sale_date': date(2020, 2, 20),
+ })
+ self.assertFalse(second_ticket.sale_available)
+ self.assertFalse(second_ticket.is_expired)
+ # incoherent dates are invalid
+ with self.assertRaises(exceptions.UserError):
+ second_ticket.write({'end_sale_date': date(2020, 1, 20)})
+
class TestEventTypeData(TestEventCommon):
@@ -161,11 +261,13 @@ class TestEventTypeData(TestEventCommon):
'has_seats_limitation': True,
'default_registration_min': 5,
'default_registration_max': 30,
+ 'use_ticket': True,
})
event_type._onchange_has_seats_limitation()
self.assertTrue(event_type.has_seats_limitation)
self.assertEqual(event_type.default_registration_min, 5)
self.assertEqual(event_type.default_registration_max, 30)
+ self.assertEqual(event_type.event_type_ticket_ids.mapped('name'), ['Registration'])
# reset seats limitation
event_type.write({'has_seats_limitation': False})
@@ -173,3 +275,7 @@ class TestEventTypeData(TestEventCommon):
self.assertFalse(event_type.has_seats_limitation)
self.assertEqual(event_type.default_registration_min, 0)
self.assertEqual(event_type.default_registration_max, 0)
+
+ # reset tickets
+ event_type.write({'use_ticket': False})
+ self.assertEqual(event_type.event_type_ticket_ids, self.env['event.type.ticket'])
diff --git a/addons/event/views/event_ticket_views.xml b/addons/event/views/event_ticket_views.xml
new file mode 100644
index 00000000000..e098b44a7ad
--- /dev/null
+++ b/addons/event/views/event_ticket_views.xml
@@ -0,0 +1,178 @@
+<?xml version="1.0"?>
+<odoo><data>
+
+ <!-- EVENT.TYPE.TICKET -->
+ <record id="event_type_ticket_view_tree_from_type" model="ir.ui.view">
+ <field name="name">event.type.ticket.view.tree.from.type</field>
+ <field name="model">event.type.ticket</field>
+ <field name="priority" eval="20"/>
+ <field name="arch" type="xml">
+ <tree string="Event Template Tickets" editable="bottom">
+ <field name="name"/>
+ <field name="seats_max"/>
+ <field name="seats_availability"/>
+ </tree>
+ </field>
+ </record>
+
+ <record id="event_type_ticket_view_form_from_type" model="ir.ui.view">
+ <field name="name">event.type.ticket.view.form.from.type</field>
+ <field name="model">event.type.ticket</field>
+ <field name="priority" eval="20"/>
+ <field name="arch" type="xml">
+ <form string="Event Template Ticket">
+ <sheet>
+ <group>
+ <field name="name"/>
+ <field name="seats_availability"/>
+ <field name="seats_max"/>
+ </group>
+ </sheet>
+ </form>
+ </field>
+ </record>
+
+ <record id="event_type_ticket_view_tree" model="ir.ui.view">
+ <field name="name">event.type.ticket.view.tree</field>
+ <field name="model">event.type.ticket</field>
+ <field name="inherit_id" ref="event_type_ticket_view_tree_from_type"/>
+ <field name="mode">primary</field>
+ <field name="priority" eval="10"/>
+ <field name="arch" type="xml">
+ <xpath expr="//tree" position="attributes">
+ <attribute name="editable"></attribute>
+ </xpath>
+ <xpath expr="//field[@name='name']" position="after">
+ <field name="event_type_id"/>
+ </xpath>
+ </field>
+ </record>
+
+ <record id="event_type_ticket_view_form" model="ir.ui.view">
+ <field name="name">event.type.ticket.view.form</field>
+ <field name="model">event.type.ticket</field>
+ <field name="inherit_id" ref="event_type_ticket_view_form_from_type"/>
+ <field name="mode">primary</field>
+ <field name="priority" eval="10"/>
+ <field name="arch" type="xml">
+ <xpath expr="//field[@name='name']" position="after">
+ <field name="event_type_id"/>
+ </xpath>
+ </field>
+ </record>
+
+ <!-- EVENT.TICKET -->
+ <record id="event_event_ticket_view_tree_from_event" model="ir.ui.view">
+ <field name="name">event.event.ticket.view.tree.from.event</field>
+ <field name="model">event.event.ticket</field>
+ <field name="priority" eval="20"/>
+ <field name="arch" type="xml">
+ <tree string="Tickets" editable="bottom">
+ <field name="name"/>
+ <field name="start_sale_date"/>
+ <field name="end_sale_date"/>
+ <field name="seats_max"/>
+ <field name="seats_reserved"/>
+ <field name="seats_unconfirmed"/>
+ </tree>
+ </field>
+ </record>
+
+ <record id="event_event_ticket_view_form_from_event" model="ir.ui.view">
+ <field name="name">event.event.ticket.view.form.from.event</field>
+ <field name="model">event.event.ticket</field>
+ <field name="priority" eval="20"/>
+ <field name="arch" type="xml">
+ <form string="Ticket">
+ <sheet>
+ <group>
+ <group>
+ <field name="name"/>
+ <field name="start_sale_date"/>
+ <field name="end_sale_date"/>
+ </group><group>
+ <field name="seats_max"/>
+ <field name="seats_reserved"/>
+ <field name="seats_unconfirmed"/>
+ </group>
+ </group>
+ </sheet>
+ </form>
+ </field>
+ </record>
+
+ <record id="event_event_ticket_view_kanban_from_event" model="ir.ui.view">
+ <field name="name">event.event.ticket.view.kanban.from.event</field>
+ <field name="model">event.event.ticket</field>
+ <field name="priority" eval="20"/>
+ <field name="arch" type="xml">
+ <kanban class="o_kanban_mobile">
+ <field name="name"/>
+ <field name="seats_max"/>
+ <field name="seats_reserved"/>
+ <field name="seats_unconfirmed"/>
+ <templates>
+ <t t-name="kanban-box">
+ <div t-attf-class="oe_kanban_card oe_kanban_global_click">
+ <div class="row">
+ <div class="col-8">
+ <strong><t t-esc="record.name.value"/></strong>
+ </div>
+ </div>
+ <div><i>
+ <t t-esc="record.seats_reserved.value"/> reserved + <t t-esc="record.seats_reserved.value"/> unconfirmed
+ </i></div>
+ </div>
+ </t>
+ </templates>
+ </kanban>
+ </field>
+ </record>
+
+ <record id="event_event_ticket_view_tree" model="ir.ui.view">
+ <field name="name">event.event.ticket.view.tree</field>
+ <field name="model">event.event.ticket</field>
+ <field name="inherit_id" ref="event_event_ticket_view_tree_from_event"/>
+ <field name="mode">primary</field>
+ <field name="priority" eval="10"/>
+ <field name="arch" type="xml">
+ <xpath expr="//tree" position="attributes">
+ <attribute name="editable"></attribute>
+ </xpath>
+ <field name="name" position="after">
+ <field name="event_id"/>
+ </field>
+ </field>
+ </record>
+
+ <record id="event_event_ticket_form_view" model="ir.ui.view">
+ <field name="name">event.event.ticket.view.form</field>
+ <field name="model">event.event.ticket</field>
+ <field name="arch" type="xml">
+ <form string="Event's Ticket">
+ <sheet>
+ <div class="oe_title">
+ <label for="name" class="oe_edit_only"/>
+ <h1><field name="name" placeholder="Event Name"/></h1>
+ </div>
+ <group>
+ <group>
+ <field name="event_id"/>
+ <field name="seats_availability"/>
+ <field name="seats_available"/>
+ <field name="start_sale_date"/>
+ <field name="end_sale_date"/>
+ </group>
+ <group>
+ <field name="seats_max"/>
+ <field name="seats_reserved"/>
+ <field name="seats_unconfirmed"/>
+ <field name="seats_used"/>
+ <field name="is_expired"/>
+ </group>
+ </group>
+ </sheet>
+ </form>
+ </field>
+ </record>
+</data></odoo> \ No newline at end of file
diff --git a/addons/event/views/event_views.xml b/addons/event/views/event_views.xml
index 9d02e87f1c1..e02bac699c9 100644
--- a/addons/event/views/event_views.xml
+++ b/addons/event/views/event_views.xml
@@ -107,6 +107,24 @@
</div>
<h2>Attendees</h2>
<div class="row mt16 o_settings_container" name="event_type_attendees">
+ <div class="col-12 o_setting_box" name="event_type_attendees_tickets">
+ <div class="o_setting_left_pane">
+ <field name="use_ticket"/>
+ </div>
+ <div class="o_setting_right_pane">
+ <label for="use_ticket"/>
+ <div class="row mt16" attrs="{'invisible': [('use_ticket', '=', False)]}">
+ <div class="col">
+ <field name="event_type_ticket_ids"
+ class="w-100"
+ context="{
+ 'tree_view_ref': 'event.event_type_ticket_view_tree_from_type',
+ 'form_view_ref': 'event.event_type_ticket_view_form_from_type'
+ }"/>
+ </div>
+ </div>
+ </div>
+ </div>
<div class="col-12 col-lg-6 o_setting_box" name="event_type_attendees_auto_confirm">
<div class="o_setting_left_pane">
<field name="auto_confirm"/>
@@ -259,6 +277,13 @@
</group>
</group>
<notebook>
+ <page string="Tickets" name="tickets">
+ <field name="event_ticket_ids" context="{
+ 'default_name': name,
+ 'tree_view_ref': 'event.event_event_ticket_view_tree_from_event',
+ 'form_view_ref': 'event.event_event_ticket_view_form_from_event',
+ 'kanban_view_ref': 'event.event_event_ticket_view_kanban_from_event'}" mode="tree,kanban"/>
+ </page>
<page string="Communication" name="event_communication">
<field name="event_mail_ids">
<tree string="Communication" editable="bottom">
@@ -494,7 +519,8 @@
<field name="email" optional="hide"/>
<field name="phone" optional="hide"/>
<field name="mobile" optional="hide"/>
- <field name="event_id" />
+ <field name="event_id"/>
+ <field name="event_ticket_id"/>
<field name="origin" optional="hide"/>
<field name="state" readonly="0"/>
<field name="company_id" groups="base.group_multi_company" optional="hide"/>
@@ -531,6 +557,13 @@
</group>
<group string="Event Information" name="event">
<field name="event_id" attrs="{'readonly': [('state', '!=', 'draft')]}" options="{'no_create': True}"/>
+ <field name="event_ticket_id"
+ domain="[
+ ('event_id', '=', event_id),
+ '|', ('seats_availability', '=', 'unlimited'), ('seats_available', '>', 0)
+ ]"
+ attrs="{'invisible': [('event_id', '=', False)]}"
+ options="{'no_open': True, 'no_create': True}"/>
<field name="date_open" groups="base.group_no_one"/>
<field name="date_closed" groups="base.group_no_one"/>
</group>
@@ -643,6 +676,7 @@
<group expand="0" string="Group By">
<filter string="Partner" name="partner" domain="[]" context="{'group_by':'partner_id'}"/>
<filter string="Event" name="group_event" domain="[]" context="{'group_by':'event_id'}"/>
+ <filter string="Ticket Type" name ="group_event_ticket_id" domain="[]" context="{'group_by': 'event_ticket_id'}"/>
<filter string="Status" name="status" domain="[]" context="{'group_by':'state'}"/>
<filter string="Registration Date" name="createmonth" domain="[]" context="{'group_by': 'create_date:month'}"/>
</group>
diff --git a/addons/event/views/res_config_settings_views.xml b/addons/event/views/res_config_settings_views.xml
index 8636b17efa3..0e8c5455e8d 100644
--- a/addons/event/views/res_config_settings_views.xml
+++ b/addons/event/views/res_config_settings_views.xml
@@ -34,7 +34,7 @@
<div class="o_setting_right_pane">
<label for="module_event_sale"/>
<div class="text-muted">
- Sell tickets with sales orders
+ Configure tickets and sell them online
</div>
</div>
</div>
diff --git a/addons/event_sale/data/event_sale_data.xml b/addons/event_sale/data/event_sale_data.xml
index ce5c21e136e..a7928ded17b 100644
--- a/addons/event_sale/data/event_sale_data.xml
+++ b/addons/event_sale/data/event_sale_data.xml
@@ -17,13 +17,6 @@
<field name="categ_id" ref="event_sale.product_category_events"/>
<field name="type">service</field>
</record>
-
- <record id="event_type_data_sale" model="event.type">
- <field name="name">Ticketing</field>
- <field name="auto_confirm" eval="False"/>
- <field name="use_mail_schedule" eval="True"/>
- <field name="use_ticketing" eval="True"/>
- </record>
</data>
</odoo>
diff --git a/addons/event_sale/models/event_event.py b/addons/event_sale/models/event_event.py
index 1d7bc163a36..b4e45656a2d 100644
--- a/addons/event_sale/models/event_event.py
+++ b/addons/event_sale/models/event_event.py
@@ -1,42 +1,12 @@
# -*- 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'
-
- @api.model
- def _get_default_event_ticket_ids(self):
- product = self.env.ref('event_sale.product_product_event', raise_if_not_found=False)
- if not product:
- return False
- return [(0, 0, {
- 'name': _('Registration'),
- 'product_id': product.id,
- 'price': 0,
- })]
-
- use_ticketing = fields.Boolean('Ticketing')
- event_ticket_ids = fields.One2many(
- 'event.event.ticket', 'event_type_id',
- string='Tickets', default=_get_default_event_ticket_ids)
-
- @api.onchange('name')
- def _onchange_name(self):
- if self.name:
- self.event_ticket_ids.filtered(lambda ticket: ticket.name == _('Registration')).update({
- 'name': _('Registration for %s') % self.name
- })
+from odoo import api, fields, models
class Event(models.Model):
_inherit = 'event.event'
- event_ticket_ids = fields.One2many(
- 'event.event.ticket', 'event_id', string='Event Ticket',
- copy=True)
sale_order_lines_ids = fields.One2many(
'sale.order.line', 'event_id',
string='All sale order lines pointing to this event')
@@ -44,25 +14,6 @@ class Event(models.Model):
currency_id = fields.Many2one(
'res.currency', string='Currency',
related='company_id.currency_id', readonly=True)
- start_sale_date = fields.Date('Start sale date', compute='_compute_start_sale_date')
-
- @api.onchange('event_type_id')
- def _onchange_type(self):
- super(Event, self)._onchange_type()
- if self.event_type_id.use_ticketing:
- self.event_ticket_ids = [(5, 0, 0)] + [
- (0, 0, {
- 'name': self.name and _('Registration for %s') % self.name or ticket.name,
- 'product_id': ticket.product_id.id,
- 'price': ticket.price,
- })
- for ticket in self.event_type_id.event_ticket_ids]
-
- @api.depends('event_ticket_ids.start_sale_date')
- def _compute_start_sale_date(self):
- for event in self:
- start_dates = [ticket.start_sale_date for ticket in event.event_ticket_ids if ticket.start_sale_date]
- event.start_sale_date = min(start_dates) if start_dates else False
@api.depends('company_id.currency_id',
'sale_order_lines_ids.price_subtotal', 'sale_order_lines_ids.currency_id',
@@ -78,12 +29,6 @@ class Event(models.Model):
for sale_order_line_id in event.sale_order_lines_ids
])
- @api.depends('event_ticket_ids.sale_available')
- def _compute_event_registrations_open(self):
- non_open_events = self.filtered(lambda event: not any(event.event_ticket_ids.mapped('sale_available')))
- non_open_events.event_registrations_open = False
- super(Event, self - non_open_events)._compute_event_registrations_open()
-
def action_view_linked_orders(self):
""" Redirects to the orders linked to the current events """
sale_order_action = self.env.ref('sale.action_orders').read()[0]
diff --git a/addons/event_sale/models/event_registration.py b/addons/event_sale/models/event_registration.py
index 88086102663..be96547f10e 100644
--- a/addons/event_sale/models/event_registration.py
+++ b/addons/event_sale/models/event_registration.py
@@ -2,14 +2,12 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import api, fields, models, _
-from odoo.exceptions import ValidationError
from odoo.tools import float_is_zero
class EventRegistration(models.Model):
_inherit = 'event.registration'
- event_ticket_id = fields.Many2one('event.event.ticket', string='Event Ticket', readonly=True, states={'draft': [('readonly', False)]})
# in addition to origin generic fields, add real relational fields to correctly
# handle attendees linked to sales orders and their lines
# TDE FIXME: maybe add an onchange on sale_order_id + origin
@@ -19,18 +17,6 @@ class EventRegistration(models.Model):
source_id = fields.Many2one('utm.source', 'Source', related="sale_order_id.source_id", store=True)
medium_id = fields.Many2one('utm.medium', 'Medium', related="sale_order_id.medium_id", store=True)
- @api.onchange('event_id')
- def _onchange_event_id(self):
- # We reset the ticket when keeping it would lead to an inconstitent state.
- if self.event_ticket_id and (not self.event_id or self.event_id != self.event_ticket_id.event_id):
- self.event_ticket_id = None
-
- @api.constrains('event_ticket_id', 'state')
- def _check_ticket_seats_limit(self):
- for record in self:
- if record.event_ticket_id.seats_max and record.event_ticket_id.seats_available < 0:
- raise ValidationError(_('No more available seats for this ticket'))
-
def _check_auto_confirmation(self):
res = super(EventRegistration, self)._check_auto_confirmation()
if res:
diff --git a/addons/event_sale/models/event_ticket.py b/addons/event_sale/models/event_ticket.py
index 06711ddd2a1..beaa73a7090 100644
--- a/addons/event_sale/models/event_ticket.py
+++ b/addons/event_sale/models/event_ticket.py
@@ -1,145 +1,95 @@
# -*- 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, UserError
+import logging
+from odoo import api, fields, models
-class EventTicket(models.Model):
- _name = 'event.event.ticket'
- _description = 'Event Ticket'
+_logger = logging.getLogger(__name__)
+
+
+class EventTemplateTicket(models.Model):
+ _inherit = 'event.type.ticket'
def _default_product_id(self):
return self.env.ref('event_sale.product_product_event', raise_if_not_found=False)
- name = fields.Char(string='Name', required=True, translate=True)
- event_type_id = fields.Many2one('event.type', string='Event Category', ondelete='cascade')
- event_id = fields.Many2one('event.event', string="Event", ondelete='cascade')
- company_id = fields.Many2one('res.company', related='event_id.company_id')
# product
- product_id = fields.Many2one('product.product', string='Product',
- required=True, domain=[("event_ok", "=", True)],
+ product_id = fields.Many2one(
+ 'product.product', string='Product', required=True, domain=[("event_ok", "=", True)],
default=_default_product_id)
price = fields.Float(string='Price', digits='Product Price')
price_reduce = fields.Float(string="Price Reduce", compute="_compute_price_reduce", digits='Product Price')
- price_reduce_taxinc = fields.Float(compute='_get_price_reduce_tax', string='Price Reduce Tax inc')
- # sale
- start_sale_date = fields.Date(string="Sales Start")
- end_sale_date = fields.Date(string="Sales End")
- is_expired = fields.Boolean(string='Is Expired', compute='_compute_is_expired')
- sale_available = fields.Boolean(string='Is Available', compute='_compute_sale_available')
- registration_ids = fields.One2many('event.registration', 'event_ticket_id', string='Registrations')
- # seats fields
- seats_availability = fields.Selection([('limited', 'Limited'), ('unlimited', 'Unlimited')],
- string='Available Seat', required=True, store=True, compute='_compute_seats', default="limited")
- seats_max = fields.Integer(string='Maximum Available Seats',
- help="Define the number of available tickets. If you have too much registrations you will "
- "not be able to sell tickets anymore. Set 0 to ignore this rule set as unlimited.")
- seats_reserved = fields.Integer(string='Reserved Seats', compute='_compute_seats', store=True)
- seats_available = fields.Integer(string='Available Seats', compute='_compute_seats', store=True)
- seats_unconfirmed = fields.Integer(string='Unconfirmed Seat Reservations', compute='_compute_seats', store=True)
- seats_used = fields.Integer(compute='_compute_seats', store=True)
-
- def _compute_is_expired(self):
- for ticket in self:
- if ticket.end_sale_date:
- current_date = fields.Date.context_today(ticket.with_context(tz=ticket.event_id.date_tz))
- ticket.is_expired = ticket.end_sale_date < current_date
- else:
- ticket.is_expired = False
-
- @api.depends('product_id.active', 'start_sale_date', 'end_sale_date')
- def _compute_sale_available(self):
- for ticket in self:
- current_date = fields.Date.context_today(ticket.with_context(tz=ticket.event_id.date_tz))
- if not ticket.product_id.active:
- ticket.sale_available = False
- elif ticket.start_sale_date and ticket.start_sale_date > current_date:
- ticket.sale_available = False
- elif ticket.end_sale_date and ticket.end_sale_date < current_date:
- ticket.sale_available = False
- else:
- ticket.sale_available = True
+ @api.depends('product_id')
def _compute_price_reduce(self):
for record in self:
product = record.product_id
discount = product.lst_price and (product.lst_price - product.price) / product.lst_price or 0.0
record.price_reduce = (1.0 - discount) * record.price
- def _get_price_reduce_tax(self):
- for record in self:
- # sudo necessary here since the field is most probably accessed through the website
- tax_ids = record.sudo().product_id.taxes_id.filtered(lambda r: r.company_id == record.event_id.company_id)
- taxes = tax_ids.compute_all(record.price_reduce, record.event_id.company_id.currency_id, 1.0, product=record.product_id)
- record.price_reduce_taxinc = taxes['total_included']
-
- @api.depends('seats_max', 'registration_ids.state')
- def _compute_seats(self):
- """ Determine reserved, available, reserved but unconfirmed and used seats. """
- # initialize fields to 0 + compute seats availability
- for ticket in self:
- ticket.seats_availability = 'unlimited' if ticket.seats_max == 0 else 'limited'
- ticket.seats_unconfirmed = ticket.seats_reserved = ticket.seats_used = ticket.seats_available = 0
- # aggregate registrations by ticket and by state
- if self.ids:
- state_field = {
- 'draft': 'seats_unconfirmed',
- 'open': 'seats_reserved',
- 'done': 'seats_used',
- }
- query = """ SELECT event_ticket_id, state, count(event_id)
- FROM event_registration
- WHERE event_ticket_id IN %s AND state IN ('draft', 'open', 'done')
- GROUP BY event_ticket_id, state
- """
- self.env['event.registration'].flush(['event_id', 'event_ticket_id', 'state'])
- self.env.cr.execute(query, (tuple(self.ids),))
- for event_ticket_id, state, num in self.env.cr.fetchall():
- ticket = self.browse(event_ticket_id)
- ticket[state_field[state]] += num
- # compute seats_available
- for ticket in self:
- if ticket.seats_max > 0:
- ticket.seats_available = ticket.seats_max - (ticket.seats_reserved + ticket.seats_used)
-
- @api.constrains('registration_ids', 'seats_max')
- def _check_seats_limit(self):
- for record in self:
- if record.seats_max and record.seats_available < 0:
- raise ValidationError(_('No more available seats for this ticket type.'))
-
- @api.constrains('event_type_id', 'event_id')
- def _constrains_event(self):
- if any(ticket.event_type_id and ticket.event_id for ticket in self):
- raise UserError(_('Ticket cannot belong to both the event category and the event itself.'))
+ @api.depends('product_id.active')
+ def _compute_sale_available(self):
+ inactive_product_tickets = self.filtered(lambda ticket: not ticket.product_id.active)
+ for ticket in inactive_product_tickets:
+ ticket.sale_available = False
+ super(EventTemplateTicket, self - inactive_product_tickets)._compute_sale_available()
@api.onchange('product_id')
def _onchange_product_id(self):
self.price = self.product_id.list_price or 0
- def _get_ticket_multiline_description_sale(self):
- """ Compute a multiline description of this ticket, in the context of sales.
- It will often be used as the default description of a sales order line referencing this ticket.
-
- 1. the first line is the ticket name
- 2. the second line is the event name (if it exists, which should be the case with a normal workflow) or the product name (if it exists)
+ def _init_column(self, column_name):
+ if column_name != "product_id":
+ return super(EventTemplateTicket, self)._init_column(column_name)
+
+ # fetch void columns
+ self.env.cr.execute("SELECT id FROM %s WHERE product_id IS NULL" % self._table)
+ ticket_type_ids = self.env.cr.fetchall()
+ if not ticket_type_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_event_product = self.env.ref('event_sale.product_product_event', raise_if_not_found=False)
+ if default_event_product:
+ product_id = default_event_product.id
+ else:
+ product_id = self.env['product.product'].create({
+ 'name': 'Generic Registration Product',
+ 'list_price': 0,
+ 'standard_price': 0,
+ 'type': 'service',
+ 'default_code': 'EVENT_REG',
+ 'type': 'service',
+ }).id
+ self.env['ir.model.data'].create({
+ 'name': 'product_product_event',
+ 'module': 'event_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(ticket_type_ids))
+ )
+
+ @api.model
+ def _get_event_ticket_fields_whitelist(self):
+ """ Add sale specific fields to copy from template to ticket """
+ return super(EventTemplateTicket, self)._get_event_ticket_fields_whitelist() + ['product_id', 'price']
- We decided to ignore entirely the product name and the product description_sale because they are considered to be replaced by the ticket name and event name.
- -> the workflow of creating a new event also does not lead to filling them correctly, as the product is created through the event interface
- """
- name = self.display_name
-
- if self.event_id:
- name += '\n' + self.event_id.display_name
- elif self.product_id:
- name += '\n' + self.product_id.display_name
+class EventTicket(models.Model):
+ _inherit = 'event.event.ticket'
- return name
+ # product
+ price_reduce_taxinc = fields.Float(string='Price Reduce Tax inc', compute='_compute_price_reduce_taxinc')
- @api.constrains('start_sale_date', 'end_sale_date')
- def _check_start_sale_date_and_end_sale_date(self):
- for ticket in self:
- if ticket.start_sale_date and ticket.end_sale_date and ticket.start_sale_date > ticket.end_sale_date:
- raise UserError(_('The stop date cannot be earlier than the start date.'))
+ def _compute_price_reduce_taxinc(self):
+ for record in self:
+ # sudo necessary here since the field is most probably accessed through the website
+ tax_ids = record.sudo().product_id.taxes_id.filtered(lambda r: r.company_id == record.event_id.company_id)
+ taxes = tax_ids.compute_all(record.price_reduce, record.event_id.company_id.currency_id, 1.0, product=record.product_id)
+ record.price_reduce_taxinc = taxes['total_included']
diff --git a/addons/event_sale/models/sale_order.py b/addons/event_sale/models/sale_order.py
index e1ff94f78b6..a46eeee1503 100644
--- a/addons/event_sale/models/sale_order.py
+++ b/addons/event_sale/models/sale_order.py
@@ -102,7 +102,7 @@ class SaleOrderLine(models.Model):
lang=self.order_id.partner_id.lang,
)
- return ticket._get_ticket_multiline_description_sale() + self._get_sale_order_line_multiline_description_variants()
+ return ticket._get_ticket_multiline_description() + self._get_sale_order_line_multiline_description_variants()
else:
return super(SaleOrderLine, self).get_sale_order_line_multiline_description_sale(product)
diff --git a/addons/event_sale/security/ir.model.access.csv b/addons/event_sale/security/ir.model.access.csv
index c8ea33e6655..fbbe3fbeb29 100644
--- a/addons/event_sale/security/ir.model.access.csv
+++ b/addons/event_sale/security/ir.model.access.csv
@@ -1,5 +1,3 @@
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
-access_event_event_ticket_user,event.event.ticket.user,event_sale.model_event_event_ticket,event.group_event_user,1,0,0,0
-access_event_event_ticket_admin,event.event.ticket.admin,event_sale.model_event_event_ticket,event.group_event_manager,1,1,1,1
access_product_template_event_manager,product.template.event.manager,product.model_product_template,event.group_event_manager,1,1,1,1
access_product_product_event_manager,product.product.event.manager,product.model_product_product,event.group_event_manager,1,1,1,1
diff --git a/addons/event_sale/tests/test_event_internals.py b/addons/event_sale/tests/test_event_internals.py
index 9f8539ec6bd..f740fb4643b 100644
--- a/addons/event_sale/tests/test_event_internals.py
+++ b/addons/event_sale/tests/test_event_internals.py
@@ -2,13 +2,14 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from datetime import date, datetime, timedelta
+from unittest.mock import patch
from odoo.addons.event_sale.tests.common import TestEventSaleCommon
-from odoo.fields import Datetime as FieldsDatetime
+from odoo.fields import Datetime as FieldsDatetime, Date as FieldsDate
from odoo.tests.common import users
-class EventSaleTest(TestEventSaleCommon):
+class TestEventData(TestEventSaleCommon):
@users('user_eventmanager')
def test_event_configuration_from_type(self):
@@ -17,11 +18,9 @@ class EventSaleTest(TestEventSaleCommon):
event_type = self.event_type_complex.with_user(self.env.user)
event_type.write({
'use_mail_schedule': False,
- 'use_ticketing': False,
+ 'use_ticket': False,
})
- # Event type does not use tickets but data is kept for compatibility and avoid recreating them
- self.assertEqual(len(event_type.event_ticket_ids), 1)
- self.assertEqual(event_type.event_ticket_ids.name, 'Registration')
+ self.assertEqual(event_type.event_type_ticket_ids, self.env['event.type.ticket'])
event = self.env['event.event'].create({
'name': 'Event Update Type',
@@ -33,53 +32,112 @@ class EventSaleTest(TestEventSaleCommon):
self.assertEqual(event.event_ticket_ids, self.env['event.event.ticket'])
event_type.write({
- 'use_ticketing': True,
- 'event_ticket_ids': [(5, 0), (0, 0, {
+ 'use_ticket': True,
+ 'event_type_ticket_ids': [(5, 0), (0, 0, {
'name': 'First Ticket',
'product_id': self.event_product.id,
})]
})
- event_type.event_ticket_ids._onchange_product_id()
+ event_type.event_type_ticket_ids._onchange_product_id()
event._onchange_type()
self.assertEqual(event.event_ticket_ids.name, 'Registration for %s' % event.name)
self.assertEqual(event.event_ticket_ids.product_id, self.event_product)
self.assertEqual(event.event_ticket_ids.price, self.event_product.list_price)
def test_event_registrable(self):
- """Test if `_compute_event_registrations_open` works properly."""
- test_event = self.env['event.event'].create({
- 'name': 'TestEvent',
- 'date_begin': datetime.now() - timedelta(days=5),
- 'date_end': datetime.now() + timedelta(days=5),
- })
- test_event_ticket = self.env['event.event.ticket'].create({
+ """Test if `_compute_event_registrations_open` works properly with additional
+ product active conditions compared to base tests (see event) """
+ event = self.event_0.with_user(self.env.user)
+ self.assertTrue(event.event_registrations_open)
+
+ # ticket without dates boundaries -> ok
+ ticket = self.env['event.event.ticket'].create({
'name': 'TestTicket',
- 'event_id': test_event.id,
- 'product_id': self.env['product.product'].search([], limit=1).id,
+ 'event_id': event.id,
+ 'product_id': self.event_product.id,
})
+ self.assertTrue(event.event_registrations_open)
- self.assertEqual(test_event.event_registrations_open, True)
+ # ticket has inactive product -> ko
+ ticket.product_id.action_archive()
+ self.assertFalse(event.event_registrations_open)
- test_event.write({
- 'date_begin': datetime.now() - timedelta(days=5),
- 'date_end': datetime.now() - timedelta(days=1),
+ # at least one valid ticket -> ok is back
+ event_product = self.env['product.product'].create({'name': 'Test Registration Product New',})
+ new_ticket = self.env['event.event.ticket'].create({
+ 'name': 'TestTicket 2',
+ 'event_id': event.id,
+ 'product_id': event_product.id,
+ 'end_sale_date': datetime.now() + timedelta(days=2),
})
+ self.assertTrue(new_ticket.sale_available)
+ self.assertTrue(event.event_registrations_open)
+
+
+class TestEventTicketData(TestEventSaleCommon):
+
+ def setUp(self):
+ super(TestEventTicketData, self).setUp()
+ self.ticket_date_patcher = patch('odoo.addons.event.models.event_ticket.fields.Date', wraps=FieldsDate)
+ self.ticket_date_patcher_mock = self.ticket_date_patcher.start()
+ self.ticket_date_patcher_mock.context_today.return_value = date(2020, 1, 31)
- test_event.date_end = datetime.now() - timedelta(days=1)
- self.assertEqual(test_event.event_registrations_open, False)
+ def tearDown(self):
+ super(TestEventTicketData, self).tearDown()
+ self.ticket_date_patcher.stop()
- test_event.write({
- 'date_begin': datetime.now() - timedelta(days=1),
- 'date_end': datetime.now() + timedelta(days=5),
+ @users('user_eventmanager')
+ def test_event_ticket_fields(self):
+ """ Test event ticket fields synchronization """
+ self.event_type_complex.write({
+ 'use_ticket': True,
+ 'event_type_ticket_ids': [
+ (5, 0),
+ (0, 0, {
+ 'name': 'First Ticket',
+ 'product_id': self.event_product.id,
+ 'seats_max': 30,
+ }), (0, 0, { # limited in time, available (01/10 (start) < 01/31 (today) < 02/10 (end))
+ 'name': 'Second Ticket',
+ 'product_id': self.event_product.id,
+ 'start_sale_date': date(2020, 1, 10),
+ 'end_sale_date': date(2020, 2, 10),
+ })
+ ],
})
+ first_ticket = self.event_type_complex.event_type_ticket_ids.filtered(lambda t: t.name == 'First Ticket')
+ first_ticket._onchange_product_id()
+ second_ticket = self.event_type_complex.event_type_ticket_ids.filtered(lambda t: t.name == 'Second Ticket')
+ second_ticket._onchange_product_id()
+ # force second ticket price, after calling the onchange
+ second_ticket.write({'price': 8.0})
- test_event.write({'event_ticket_ids': [(5, 0)]})
- self.assertEqual(test_event.event_registrations_open, False, 'cannot register if no tickets')
+ # price coming from product
+ self.assertEqual(first_ticket.price, self.event_product.list_price)
+ self.assertEqual(second_ticket.price, 8.0)
- test_event_ticket = self.env['event.event.ticket'].create({
- 'name': 'TestTicket',
- 'event_id': test_event.id,
- 'product_id': self.env['product.product'].search([], limit=1).id,
+ # default availability
+ self.assertEqual(first_ticket.seats_availability, 'limited')
+ self.assertTrue(first_ticket.sale_available)
+ self.assertFalse(first_ticket.is_expired)
+ self.assertEqual(second_ticket.seats_availability, 'unlimited')
+ self.assertTrue(second_ticket.sale_available)
+ self.assertFalse(second_ticket.is_expired)
+
+ # product archived
+ self.event_product.action_archive()
+ self.assertFalse(first_ticket.sale_available)
+ self.assertFalse(second_ticket.sale_available)
+
+ # sale is ended
+ self.event_product.action_unarchive()
+ second_ticket.write({'end_sale_date': date(2020, 1, 20)})
+ self.assertFalse(second_ticket.sale_available)
+ self.assertTrue(second_ticket.is_expired)
+ # sale has not started
+ second_ticket.write({
+ 'start_sale_date': date(2020, 2, 10),
+ 'end_sale_date': date(2020, 2, 20),
})
- test_event_ticket.product_id.active = False
- self.assertEqual(test_event.event_registrations_open, False, 'cannot register if product linked to the tickets are all archived')
+ self.assertFalse(second_ticket.sale_available)
+ self.assertFalse(second_ticket.is_expired)
diff --git a/addons/event_sale/views/event_registration_views.xml b/addons/event_sale/views/event_registration_views.xml
index 79394c37acf..c66daf53e02 100644
--- a/addons/event_sale/views/event_registration_views.xml
+++ b/addons/event_sale/views/event_registration_views.xml
@@ -1,24 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo><data>
- <record id="view_event_registration_ticket_search" model="ir.ui.view">
- <field name="name">event.registration.search.inherit</field>
- <field name="model">event.registration</field>
- <field name="inherit_id" ref="event.view_registration_search" />
- <field name="arch" type="xml">
- <filter name="group_event" position="after">
- <filter string="Ticket Type" name ="tickettype" domain="['|', ('company_id', '=', False), ('company_id', '=', parent.company_id)]" context="{'group_by':'event_ticket_id'}"/>
- </filter>
- </field>
- </record>
-
<record id="view_event_registration_ticket_tree" model="ir.ui.view">
<field name="name">event.registration.tree.inherit</field>
<field name="model">event.registration</field>
<field name="inherit_id" ref="event.view_event_registration_tree" />
<field name="arch" type="xml">
<field name="event_id" position="after">
- <field name="event_ticket_id"/>
<field name="origin"/>
</field>
</field>
@@ -41,17 +29,6 @@
<field name="model">event.registration</field>
<field name="inherit_id" ref="event.view_event_registration_form" />
<field name="arch" type="xml">
- <field name="event_id" position="after">
- <field
- name="event_ticket_id"
- domain="[
- ('event_id', '=', event_id),
- '|', ('seats_availability', '=', 'unlimited'), ('seats_available', '>', 0)
- ]"
- attrs="{'invisible': [('event_id', '=', False)]}"
- options="{'no_open': True, 'no_create': True}"
- />
- </field>
<group name="event" position="after">
<group string="Origin">
<field name="sale_order_id"/>
diff --git a/addons/event_sale/views/event_ticket_views.xml b/addons/event_sale/views/event_ticket_views.xml
index 80e96a5cc5d..b10579396dc 100644
--- a/addons/event_sale/views/event_ticket_views.xml
+++ b/addons/event_sale/views/event_ticket_views.xml
@@ -1,106 +1,91 @@
<?xml version="1.0" encoding="utf-8"?>
-<odoo>
+<odoo><data>
+
+ <!-- EVENT.TYPE.TICKET -->
+ <record id="event_type_ticket_view_tree_from_type" model="ir.ui.view">
+ <field name="name">event.type.ticket.view.tree.inherit.sale</field>
+ <field name="model">event.type.ticket</field>
+ <field name="inherit_id" ref="event.event_type_ticket_view_tree_from_type"/>
+ <field name="arch" type="xml">
+ <field name="name" position="after">
+ <field name="product_id"/>
+ <field name="price"/>
+ </field>
+ </field>
+ </record>
+
+ <record id="event_type_ticket_view_form_from_type" model="ir.ui.view">
+ <field name="name">event.type.ticket.view.form.inherit.sale</field>
+ <field name="model">event.type.ticket</field>
+ <field name="inherit_id" ref="event.event_type_ticket_view_form_from_type"/>
+ <field name="arch" type="xml">
+ <field name="name" position="after">
+ <field name="product_id"/>
+ <field name="price"/>
+ </field>
+ </field>
+ </record>
+
<!-- EVENT.TICKET -->
<record id="event_event_ticket_view_tree_from_event" model="ir.ui.view">
- <field name="name">event.event.ticket.view.tree.from_event</field>
+ <field name="name">event.event.ticket.view.tree.from.event.inherit.sale</field>
<field name="model">event.event.ticket</field>
- <field name="priority" eval="20"/>
+ <field name="inherit_id" ref="event.event_event_ticket_view_tree_from_event"/>
<field name="arch" type="xml">
- <tree string="Tickets" editable="bottom">
- <field name="name"/>
- <field name="product_id" context="{'default_event_ok':1}"/>
- <field name="start_sale_date"/>
- <field name="end_sale_date"/>
+ <field name="name" position="after">
+ <field name="product_id" context="{'default_event_ok': 1}"/>
+ </field>
+ <field name="end_sale_date" position="after">
<field name="price"/>
- <field name="seats_max"/>
- <field name="seats_reserved" readonly="1"/>
- <field name="seats_unconfirmed" readonly="1"/>
- </tree>
+ </field>
</field>
</record>
<record id="event_event_ticket_view_form_from_event" model="ir.ui.view">
- <field name="name">event.event.ticket.view.form.from.event</field>
+ <field name="name">event.event.ticket.view.form.from.event.inherit.sale</field>
<field name="model">event.event.ticket</field>
- <field name="priority" eval="20"/>
+ <field name="inherit_id" ref="event.event_event_ticket_view_form_from_event"/>
<field name="arch" type="xml">
- <form string="Ticket">
- <group>
- <group>
- <field name="name"/>
- <field name="product_id" context="{'default_event_ok':1}"/>
- <field name="end_sale_date"/>
- <field name="price"/>
- </group><group>
- <field name="seats_max"/>
- <field name="seats_reserved" readonly="1"/>
- <field name="seats_unconfirmed" readonly="1"/>
- </group>
- </group>
- </form>
+ <field name="name" position="after">
+ <field name="product_id" context="{'default_event_ok':1}"/>
+ </field>
+ <field name="end_sale_date" position="after">
+ <field name="price"/>
+ </field>
</field>
</record>
<record id="event_event_ticket_view_kanban_from_event" model="ir.ui.view">
<field name="name">event.event.ticket.view.kanban.from.event</field>
<field name="model">event.event.ticket</field>
- <field name="priority" eval="20"/>
+ <field name="inherit_id" ref="event.event_event_ticket_view_kanban_from_event"/>
<field name="arch" type="xml">
- <kanban class="o_kanban_mobile">
+ <field name="name" position="after">
<field name="product_id"/>
- <field name="name"/>
<field name="price"/>
- <field name="seats_max"/>
- <field name="seats_reserved"/>
- <field name="seats_unconfirmed"/>
- <templates>
- <t t-name="kanban-box">
- <div t-attf-class="oe_kanban_card oe_kanban_global_click">
- <div class="row">
- <div class="col-8">
- <strong><t t-esc="record.name.value"/></strong>
- </div>
- <div class="col-4 text-right"><strong> <t t-esc="record.price.value"/></strong></div>
- </div>
- <div t-esc="record.product_id.value"/>
- <div><i>
- <t t-esc="record.seats_reserved.value"/> reserved + <t t-esc="record.seats_reserved.value"/> unconfirmed
- </i></div>
- </div>
- </t>
- </templates>
- </kanban>
+ </field>
+ <xpath expr="//div[hasclass('col-8')]" position="after">
+ <div class="col-4 text-right"><strong> <t t-esc="record.price.value"/></strong></div>
+ </xpath>
+ <xpath expr="//div[hasclass('row')]" position="after">
+ <div t-esc="record.product_id.value"/>
+ </xpath>
</field>
</record>
- <record id="event_event_ticket_view_form" model="ir.ui.view">
- <field name="name">event.event.ticket.view.form</field>
+ <record id="event_event_ticket_form_view" model="ir.ui.view">
+ <field name="name">event.event.ticket.view.form.inherit.sale</field>
<field name="model">event.event.ticket</field>
+ <field name="inherit_id" ref="event.event_event_ticket_form_view"/>
<field name="arch" type="xml">
- <form string="Event's Ticket">
- <div class="oe_title">
- <label for="name" class="oe_edit_only"/>
- <h1><field name="name" placeholder="Event Name"/></h1>
- </div>
- <group>
- <group>
- <field name="event_id"/>
- <field name="seats_availability"/>
- <field name="seats_available"/>
- <field name="end_sale_date"/>
- <field name="price"/>
- <field name="price_reduce" groups="base.group_no_one"/>
- </group>
- <group>
- <field name="seats_max"/>
- <field name="seats_reserved"/>
- <field name="seats_unconfirmed"/>
- <field name="seats_used"/>
- <field name="product_id"/>
- <field name="is_expired"/>
- </group>
- </group>
- </form>
+ <field name="end_sale_date" position="after">
+ <field name="price"/>
+ <field name="price_reduce" groups="base.group_no_one"/>
+ </field>
+ <field name="seats_used" position="after">
+ <field name="product_id"/>
+ </field>
</field>
</record>
-</odoo>
+</data></odoo>
+
diff --git a/addons/event_sale/views/event_views.xml b/addons/event_sale/views/event_views.xml
index e18cbb1ae3b..5d846504792 100644
--- a/addons/event_sale/views/event_views.xml
+++ b/addons/event_sale/views/event_views.xml
@@ -1,33 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
- <record id="event_type_view_form_inherit_sale" model="ir.ui.view">
- <field name="name">event.type.view.form.inherit.sale</field>
- <field name="model">event.type</field>
- <field name="inherit_id" ref="event.view_event_type_form"/>
- <field name="arch" type="xml">
- <div name="event_type_attendees_auto_confirm" position="after">
- <div class="col-12 col-lg-12 o_setting_box">
- <div class="o_setting_left_pane">
- <field name="use_ticketing"/>
- </div>
- <div class="o_setting_right_pane">
- <label for="use_ticketing"/>
- <div class="row mt16" attrs="{'invisible': [('use_ticketing', '=', False)]}">
- <div class="col-lg-9">
- <field name="event_ticket_ids">
- <tree string="Tickets" editable="bottom">
- <field name="name"/>
- <field name="product_id"/>
- <field name="price"/>
- </tree>
- </field>
- </div>
- </div>
- </div>
- </div>
- </div>
- </field>
- </record>
<record id="view_event_form_inherit_ticket" model="ir.ui.view">
<field name="name">event.form.inherit</field>
@@ -49,15 +21,7 @@
</div>
</button>
</xpath>
- <page name="event_communication" position="before">
- <page string="Tickets" name="tickets">
- <field name="event_ticket_ids" context="{
- 'default_name': name,
- 'tree_view_ref': 'event_sale.event_event_ticket_view_tree_from_event',
- 'form_view_ref': 'event_sale.event_event_ticket_view_form_from_event',
- 'kanban_view_ref': 'event_sale.event_event_ticket_view_kanban_from_event'}" mode="tree,kanban"/>
- </page>
- </page>
</field>
</record>
+
</odoo>
diff --git a/addons/website_event/security/event_security.xml b/addons/website_event/security/event_security.xml
index a30ce1f6c42..0b86e8cf8cc 100644
--- a/addons/website_event/security/event_security.xml
+++ b/addons/website_event/security/event_security.xml
@@ -11,6 +11,17 @@
<field name="perm_unlink" eval="False"/>
</record>
+ <record id="ir_rule_event_event_ticket_public" model="ir.rule">
+ <field name="name">Event ticket: public/portal may read published</field>
+ <field name="model_id" ref="event.model_event_event_ticket"/>
+ <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>
+
<record id="event.group_event_manager" model="res.groups">
<field name="implied_ids" eval="[(4, ref('website.group_website_publisher'))]"/>
</record>
diff --git a/addons/website_event/security/ir.model.access.csv b/addons/website_event/security/ir.model.access.csv
index 3b9ee7e6aa4..1024bdc8213 100644
--- a/addons/website_event/security/ir.model.access.csv
+++ b/addons/website_event/security/ir.model.access.csv
@@ -1,4 +1,6 @@
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_event_event_public,event.event.public,event.model_event_event,base.group_public,1,0,0,0
access_event_event_portal,event.event.portal,event.model_event_event,base.group_portal,1,0,0,0
-access_event_type_public,event.type.public,event.model_event_type,,1,0,0,0 \ No newline at end of file
+access_event_type_public,event.type.public,event.model_event_type,,1,0,0,0
+access_event_event_ticket_public,event.event.ticket.public,event.model_event_event_ticket,base.group_public,1,0,0,0
+access_event_event_ticket_portal,event.event.ticket.portal,event.model_event_event_ticket,base.group_portal,1,0,0,0
diff --git a/addons/website_event_sale/data/event_data.xml b/addons/website_event_sale/data/event_data.xml
index 089c5aa2d24..9df17e43c99 100644
--- a/addons/website_event_sale/data/event_data.xml
+++ b/addons/website_event_sale/data/event_data.xml
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data noupdate="1">
- <record id="event_sale.event_type_data_sale" model="event.type">
+ <record id="event.event_type_data_ticket" model="event.type">
<field name="name">Sell Online</field>
<field name="website_menu" eval="True"/>
</record>
diff --git a/addons/website_event_sale/models/sale_order.py b/addons/website_event_sale/models/sale_order.py
index 662217167ad..215412e0a9d 100644
--- a/addons/website_event_sale/models/sale_order.py
+++ b/addons/website_event_sale/models/sale_order.py
@@ -43,7 +43,7 @@ class SaleOrder(models.Model):
values['price_unit'] = ticket.price
else:
values['price_unit'] = ticket.price_reduce
- values['name'] = ticket._get_ticket_multiline_description_sale()
+ values['name'] = ticket._get_ticket_multiline_description()
# avoid writing related values that end up locking the product record
values.pop('event_ok', None)
diff --git a/addons/website_event_sale/security/ir.model.access.csv b/addons/website_event_sale/security/ir.model.access.csv
index ec5ea43cd7d..74efdec9621 100644
--- a/addons/website_event_sale/security/ir.model.access.csv
+++ b/addons/website_event_sale/security/ir.model.access.csv
@@ -1,4 +1,3 @@
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
-access_event_event_ticket_public,event.event.ticket.public,event_sale.model_event_event_ticket,,1,0,0,0
access_event_product_product_public,event.product.product.public,product.model_product_product,base.group_public,1,0,0,0
-access_event_product_template_public,event.product.template.public,product.model_product_template,base.group_public,1,0,0,0 \ No newline at end of file
+access_event_product_template_public,event.product.template.public,product.model_product_template,base.group_public,1,0,0,0
diff --git a/addons/website_event_sale/security/website_event_sale_security.xml b/addons/website_event_sale/security/website_event_sale_security.xml
index 662b935cb56..a3b77d8ad05 100644
--- a/addons/website_event_sale/security/website_event_sale_security.xml
+++ b/addons/website_event_sale/security/website_event_sale_security.xml
@@ -1,16 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
- <record id="event_event_ticket_public" model="ir.rule">
- <field name="name">event ticket: Public</field>
- <field name="model_id" ref="event_sale.model_event_event_ticket"/>
- <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>
-
<record id="event_product_template_public" model="ir.rule">
<field name="name">Product template linked to event: Public</field>
<field name="model_id" ref="product.model_product_template"/>