19.0 vanilla

This commit is contained in:
Ernad Husremovic 2026-03-09 09:30:27 +01:00
parent d1963a3c3a
commit 2d3ee4855a
7430 changed files with 2687981 additions and 2965473 deletions

View file

@ -1,56 +1,23 @@
# -*- 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
from odoo.exceptions import UserError
from odoo.tools.misc import formatLang
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)
description = fields.Text(
'Description', translate=True,
help="A description of the ticket that you want to communicate to your customers.")
event_type_id = fields.Many2one(
'event.type', string='Event Category', ondelete='cascade', required=True)
# seats
seats_limited = fields.Boolean(string='Limit Attendees', readonly=True, store=True,
compute='_compute_seats_limited')
seats_max = fields.Integer(
string='Maximum Attendees',
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('seats_max')
def _compute_seats_limited(self):
for ticket in self:
ticket.seats_limited = ticket.seats_max
@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', 'description', 'seats_max']
class EventTicket(models.Model):
""" Ticket model allowing to have differnt kind of registrations for a given
class EventEventTicket(models.Model):
""" Ticket model allowing to have different 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'
_inherit = ['event.type.ticket']
_description = 'Event Ticket'
_order = "event_id, sequence, name, id"
@api.model
def default_get(self, fields):
res = super(EventTicket, self).default_get(fields)
res = super().default_get(fields)
if 'name' in fields and (not res.get('name') or res['name'] == _('Registration')) and self.env.context.get('default_event_name'):
res['name'] = _('Registration for %s', self.env.context['default_event_name'])
return res
@ -59,7 +26,7 @@ class EventTicket(models.Model):
event_type_id = fields.Many2one(ondelete='set null', required=False)
event_id = fields.Many2one(
'event.event', string="Event",
ondelete='cascade', required=True)
ondelete='cascade', required=True, index=True)
company_id = fields.Many2one('res.company', related='event_id.company_id')
# sale
start_sale_datetime = fields.Datetime(string="Registration Start")
@ -73,10 +40,14 @@ class EventTicket(models.Model):
# seats
seats_reserved = fields.Integer(string='Reserved Seats', compute='_compute_seats', store=False)
seats_available = fields.Integer(string='Available Seats', compute='_compute_seats', store=False)
seats_unconfirmed = fields.Integer(string='Unconfirmed Seats', compute='_compute_seats', store=False)
seats_used = fields.Integer(string='Used Seats', compute='_compute_seats', store=False)
seats_taken = fields.Integer(string="Taken Seats", compute="_compute_seats", store=False)
limit_max_per_order = fields.Integer(string='Limit per Order', default=0,
help="Maximum of this product per order.\nSet to 0 to ignore this rule")
is_sold_out = fields.Boolean(
'Sold Out', compute='_compute_is_sold_out', help='Whether seats are not available for this ticket.')
# reports
color = fields.Char('Color', default="#875A7B")
@api.depends('end_sale_datetime', 'event_id.date_tz')
def _compute_is_expired(self):
@ -108,21 +79,20 @@ class EventTicket(models.Model):
@api.depends('seats_max', 'registration_ids.state', 'registration_ids.active')
def _compute_seats(self):
""" Determine reserved, available, reserved but unconfirmed and used seats. """
""" Determine available, reserved, used and taken 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
ticket.seats_reserved = ticket.seats_used = ticket.seats_available = 0
# aggregate registrations by ticket and by state
results = {}
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') AND active = true
WHERE event_ticket_id IN %s AND state IN ('open', 'done') AND active = true
GROUP BY event_ticket_id, state
"""
self.env['event.registration'].flush_model(['event_id', 'event_ticket_id', 'state', 'active'])
@ -135,6 +105,7 @@ class EventTicket(models.Model):
ticket.update(results.get(ticket._origin.id or ticket.id, {}))
if ticket.seats_max > 0:
ticket.seats_available = ticket.seats_max - (ticket.seats_reserved + ticket.seats_used)
ticket.seats_taken = ticket.seats_reserved + ticket.seats_used
@api.depends('seats_limited', 'seats_available', 'event_id.event_registrations_sold_out')
def _compute_is_sold_out(self):
@ -151,25 +122,31 @@ class EventTicket(models.Model):
raise UserError(_('The stop date cannot be earlier than the start date. '
'Please check ticket %(ticket_name)s', ticket_name=ticket.name))
@api.constrains('registration_ids', 'seats_max')
def _check_seats_availability(self, minimal_availability=0):
sold_out_tickets = []
@api.constrains('limit_max_per_order', 'seats_max')
def _constrains_limit_max_per_order(self):
for ticket in self:
if ticket.seats_max and ticket.seats_available < minimal_availability:
sold_out_tickets.append((_(
'- the ticket "%(ticket_name)s" (%(event_name)s): Missing %(nb_too_many)i seats.',
ticket_name=ticket.name, event_name=ticket.event_id.name, nb_too_many=-ticket.seats_available)))
if sold_out_tickets:
raise ValidationError(_('There are not enough seats available for:')
+ '\n%s\n' % '\n'.join(sold_out_tickets))
if ticket.seats_max and ticket.limit_max_per_order > ticket.seats_max:
raise UserError(_('The limit per order cannot be greater than the maximum seats number. '
'Please check ticket %(ticket_name)s', ticket_name=ticket.name))
if ticket.limit_max_per_order > ticket.event_id.EVENT_MAX_TICKETS:
raise UserError(_('The limit per order cannot be greater than %(limit_orderable)s. '
'Please check ticket %(ticket_name)s', limit_orderable=ticket.event_id.EVENT_MAX_TICKETS, ticket_name=ticket.name))
if ticket.limit_max_per_order < 0:
raise UserError(_('The limit per order must be positive. '
'Please check ticket %(ticket_name)s', ticket_name=ticket.name))
def name_get(self):
"""Adds ticket seats availability if requested by context."""
@api.depends('seats_max', 'seats_available')
@api.depends_context('name_with_seats_availability')
def _compute_display_name(self):
"""Adds ticket seats availability if requested by context.
Always display the name without availabilities if the event is multi slots
because the availability displayed won't be relative to the possible slot combinations
but only relative to the event and this will confuse the user.
"""
if not self.env.context.get('name_with_seats_availability'):
return super().name_get()
res = []
return super()._compute_display_name()
for ticket in self:
if not ticket.seats_max:
if not ticket.seats_max or ticket.event_id.is_multi_slots:
name = ticket.name
elif not ticket.seats_available:
name = _('%(ticket_name)s (Sold out)', ticket_name=ticket.name)
@ -179,8 +156,26 @@ class EventTicket(models.Model):
ticket_name=ticket.name,
count=formatLang(self.env, ticket.seats_available, digits=0),
)
res.append((ticket.id, name))
return res
ticket.display_name = name
def _get_current_limit_per_order(self, event_slot=False, event=False):
""" Compute the maximum possible number of tickets for an order, taking
into account the given event_slot if applicable.
If no ticket is created (alone event), event_id argument is used. Then
return the dictionary with False as key. """
event_slot.ensure_one() if event_slot else None
if self:
slots_seats_available = self.event_id._get_seats_availability([[event_slot, ticket] for ticket in self])
else:
return {False: event_slot.seats_available if event_slot else (event.seats_available if event.seats_limited else event.EVENT_MAX_TICKETS)}
availabilities = {}
for ticket, seats_available in zip(self, slots_seats_available):
if not seats_available: # "No limit"
seats_available = ticket.limit_max_per_order or ticket.event_id.EVENT_MAX_TICKETS
else:
seats_available = min(ticket.limit_max_per_order or seats_available, seats_available)
availabilities[ticket.id] = seats_available
return availabilities
def _get_ticket_multiline_description(self):
""" Compute a multiline description of this ticket. It is used when ticket