summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--addons/event/models/__init__.py3
-rw-r--r--addons/event/models/event_event.py (renamed from addons/event/models/event.py)198
-rw-r--r--addons/event/models/event_mail.py1
-rw-r--r--addons/event/models/event_registration.py202
-rw-r--r--addons/event/models/event_stage.py1
-rw-r--r--addons/event/tests/test_event_internals.py2
-rw-r--r--addons/event_sale/models/__init__.py6
-rw-r--r--addons/event_sale/models/account_move.py (renamed from addons/event_sale/models/account_invoice.py)0
-rw-r--r--addons/event_sale/models/event.py321
-rw-r--r--addons/event_sale/models/event_event.py98
-rw-r--r--addons/event_sale/models/event_registration.py85
-rw-r--r--addons/event_sale/models/event_ticket.py144
-rw-r--r--addons/event_sms/models/__init__.py2
-rw-r--r--addons/event_sms/models/event_registration.py (renamed from addons/event_sms/models/event.py)0
-rw-r--r--addons/website_event/models/__init__.py2
-rw-r--r--addons/website_event/models/event_event.py (renamed from addons/website_event/models/event.py)0
-rw-r--r--addons/website_event_questions/models/__init__.py4
-rw-r--r--addons/website_event_questions/models/event_event.py37
-rw-r--r--addons/website_event_questions/models/event_question.py (renamed from addons/website_event_questions/models/event.py)54
-rw-r--r--addons/website_event_questions/models/event_registration.py25
-rw-r--r--addons/website_event_track/models/__init__.py4
-rw-r--r--addons/website_event_track/models/event_event.py (renamed from addons/website_event_track/models/event.py)0
-rw-r--r--addons/website_event_track/models/event_sponsor.py26
-rw-r--r--addons/website_event_track/models/event_track.py41
-rw-r--r--addons/website_event_track/models/event_track_stage.py23
25 files changed, 658 insertions, 621 deletions
diff --git a/addons/event/models/__init__.py b/addons/event/models/__init__.py
index 750d7b5de79..727b34645d7 100644
--- a/addons/event/models/__init__.py
+++ b/addons/event/models/__init__.py
@@ -1,8 +1,9 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
-from . import event
+from . import event_event
from . import event_mail
+from . import event_registration
from . import event_stage
from . import res_config_settings
from . import res_partner
diff --git a/addons/event/models/event.py b/addons/event/models/event_event.py
index d49b79d369a..4f366871155 100644
--- a/addons/event/models/event.py
+++ b/addons/event/models/event_event.py
@@ -1,16 +1,15 @@
# -*- coding: utf-8 -*-
+# Part of Odoo. See LICENSE file for full copyright and licensing details.
import logging
import pytz
from odoo import _, api, fields, models
from odoo.tools import format_datetime
-from odoo.exceptions import AccessError, UserError, ValidationError
+from odoo.exceptions import ValidationError
from odoo.tools.translate import html_translate
from odoo.tools.misc import _format_time_ago
-from dateutil.relativedelta import relativedelta
-
_logger = logging.getLogger(__name__)
try:
@@ -395,196 +394,3 @@ class EventEvent(models.Model):
result[event.id] = cal.serialize().encode('utf-8')
return result
-
-
-class EventRegistration(models.Model):
- _name = 'event.registration'
- _description = 'Event Registration'
- _inherit = ['mail.thread', 'mail.activity.mixin']
- _order = 'id desc'
-
- # event
- origin = fields.Char(
- string='Source Document', readonly=True,
- help="Reference of the document that created the registration, for example a sales order")
- event_id = fields.Many2one(
- 'event.event', string='Event', required=True,
- readonly=True, states={'draft': [('readonly', False)]})
- # attendee
- partner_id = fields.Many2one(
- 'res.partner', string='Contact',
- states={'done': [('readonly', True)]})
- name = fields.Char(string='Attendee Name', index=True)
- email = fields.Char(string='Email')
- phone = fields.Char(string='Phone')
- mobile = fields.Char(string='Mobile')
- # organization
- date_open = fields.Datetime(string='Registration Date', readonly=True, default=lambda self: fields.Datetime.now()) # weird crash is directly now
- date_closed = fields.Datetime(string='Attended Date', readonly=True)
- event_begin_date = fields.Datetime(string="Event Start Date", related='event_id.date_begin', readonly=True)
- event_end_date = fields.Datetime(string="Event End Date", related='event_id.date_end', readonly=True)
- company_id = fields.Many2one(
- 'res.company', string='Company', related='event_id.company_id',
- store=True, readonly=True, states={'draft': [('readonly', False)]})
- state = fields.Selection([
- ('draft', 'Unconfirmed'), ('cancel', 'Cancelled'),
- ('open', 'Confirmed'), ('done', 'Attended')],
- string='Status', default='draft', readonly=True, copy=False, tracking=True)
-
- @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.'))
-
- def _check_auto_confirmation(self):
- if self._context.get('registration_force_draft'):
- return False
- if any(not registration.event_id.auto_confirm or
- (not registration.event_id.seats_available and registration.event_id.seats_availability == 'limited') for registration in self):
- return False
- return True
-
- @api.model
- def create(self, vals):
- registration = super(EventRegistration, self).create(vals)
- if registration._check_auto_confirmation():
- registration.sudo().confirm_registration()
- return registration
-
- @api.model
- def _prepare_attendee_values(self, registration):
- """ Method preparing the values to create new attendees based on a
- sales order line. It takes some registration data (dict-based) that are
- optional values coming from an external input like a web page. This method
- is meant to be inherited in various addons that sell events. """
- partner_id = registration.pop('partner_id', self.env.user.partner_id)
- event_id = registration.pop('event_id', False)
- data = {
- 'name': registration.get('name', partner_id.name),
- 'phone': registration.get('phone', partner_id.phone),
- 'mobile': registration.get('mobile', partner_id.mobile),
- 'email': registration.get('email', partner_id.email),
- 'partner_id': partner_id.id,
- 'event_id': event_id and event_id.id or False,
- }
- data.update({key: value for key, value in registration.items() if key in self._fields})
- return data
-
- def do_draft(self):
- self.write({'state': 'draft'})
-
- def confirm_registration(self):
- self.write({'state': 'open'})
-
- # auto-trigger after_sub (on subscribe) mail schedulers, if needed
- onsubscribe_schedulers = self.event_id.event_mail_ids.filtered(
- lambda s: s.interval_type == 'after_sub')
- onsubscribe_schedulers.execute()
-
- def button_reg_close(self):
- """ Close Registration """
- for registration in self:
- registration.write({'state': 'done', 'date_closed': fields.Datetime.now()})
-
- def button_reg_cancel(self):
- self.write({'state': 'cancel'})
-
- @api.onchange('partner_id')
- def _onchange_partner(self):
- if self.partner_id:
- contact_id = self.partner_id.address_get().get('contact', False)
- if contact_id:
- contact = self.env['res.partner'].browse(contact_id)
- self.name = contact.name or self.name
- self.email = contact.email or self.email
- self.phone = contact.phone or self.phone
- self.mobile = contact.mobile or self.mobile
-
- def _message_get_suggested_recipients(self):
- recipients = super(EventRegistration, self)._message_get_suggested_recipients()
- public_users = self.env['res.users'].sudo()
- public_groups = self.env.ref("base.group_public", raise_if_not_found=False)
- if public_groups:
- public_users = public_groups.sudo().with_context(active_test=False).mapped("users")
- try:
- for attendee in self:
- is_public = attendee.sudo().with_context(active_test=False).partner_id.user_ids in public_users if public_users else False
- if attendee.partner_id and not is_public:
- attendee._message_add_suggested_recipient(recipients, partner=attendee.partner_id, reason=_('Customer'))
- elif attendee.email:
- attendee._message_add_suggested_recipient(recipients, email=attendee.email, reason=_('Customer Email'))
- except AccessError: # no read access rights -> ignore suggested recipients
- pass
- return recipients
-
- def _message_get_default_recipients(self):
- # Prioritize registration email over partner_id, which may be shared when a single
- # partner booked multiple seats
- return {r.id: {
- 'partner_ids': [],
- 'email_to': r.email,
- 'email_cc': False}
- for r in self}
-
- def _message_post_after_hook(self, message, msg_vals):
- if self.email and not self.partner_id:
- # we consider that posting a message with a specified recipient (not a follower, a specific one)
- # on a document without customer means that it was created through the chatter using
- # suggested recipients. This heuristic allows to avoid ugly hacks in JS.
- new_partner = message.partner_ids.filtered(lambda partner: partner.email == self.email)
- if new_partner:
- self.search([
- ('partner_id', '=', False),
- ('email', '=', new_partner.email),
- ('state', 'not in', ['cancel']),
- ]).write({'partner_id': new_partner.id})
- return super(EventRegistration, self)._message_post_after_hook(message, msg_vals)
-
- def action_send_badge_email(self):
- """ Open a window to compose an email, with the template - 'event_badge'
- message loaded by default
- """
- self.ensure_one()
- template = self.env.ref('event.event_registration_mail_template_badge')
- compose_form = self.env.ref('mail.email_compose_message_wizard_form')
- ctx = dict(
- default_model='event.registration',
- default_res_id=self.id,
- default_use_template=bool(template),
- default_template_id=template.id,
- default_composition_mode='comment',
- custom_layout="mail.mail_notification_light",
- )
- return {
- 'name': _('Compose Email'),
- 'type': 'ir.actions.act_window',
- 'view_mode': 'form',
- 'res_model': 'mail.compose.message',
- 'views': [(compose_form.id, 'form')],
- 'view_id': compose_form.id,
- 'target': 'new',
- 'context': ctx,
- }
-
- def get_date_range_str(self):
- self.ensure_one()
- today = fields.Datetime.now()
- event_date = self.event_begin_date
- diff = (event_date.date() - today.date())
- if diff.days <= 0:
- return _('today')
- elif diff.days == 1:
- return _('tomorrow')
- elif (diff.days < 7):
- return _('in %d days') % (diff.days, )
- elif (diff.days < 14):
- return _('next week')
- elif event_date.month == (today + relativedelta(months=+1)).month:
- return _('next month')
- else:
- return _('on ') + format_datetime(self.env, self.event_begin_date, tz=self.event_id.date_tz, dt_format='medium')
-
- def summary(self):
- self.ensure_one()
- return {'information': []}
diff --git a/addons/event/models/event_mail.py b/addons/event/models/event_mail.py
index fd53adbb0bc..4f2407609f9 100644
--- a/addons/event/models/event_mail.py
+++ b/addons/event/models/event_mail.py
@@ -1,4 +1,5 @@
# -*- coding: utf-8 -*-
+# Part of Odoo. See LICENSE file for full copyright and licensing details.
from datetime import datetime
from dateutil.relativedelta import relativedelta
diff --git a/addons/event/models/event_registration.py b/addons/event/models/event_registration.py
new file mode 100644
index 00000000000..8f940a2812e
--- /dev/null
+++ b/addons/event/models/event_registration.py
@@ -0,0 +1,202 @@
+# -*- coding: utf-8 -*-
+# Part of Odoo. See LICENSE file for full copyright and licensing details.
+
+
+from odoo import _, api, fields, models
+from odoo.tools import format_datetime
+from odoo.exceptions import AccessError, ValidationError
+
+from dateutil.relativedelta import relativedelta
+
+
+class EventRegistration(models.Model):
+ _name = 'event.registration'
+ _description = 'Event Registration'
+ _inherit = ['mail.thread', 'mail.activity.mixin']
+ _order = 'id desc'
+
+ # event
+ origin = fields.Char(
+ string='Source Document', readonly=True,
+ help="Reference of the document that created the registration, for example a sales order")
+ event_id = fields.Many2one(
+ 'event.event', string='Event', required=True,
+ readonly=True, states={'draft': [('readonly', False)]})
+ # attendee
+ partner_id = fields.Many2one(
+ 'res.partner', string='Contact',
+ states={'done': [('readonly', True)]})
+ name = fields.Char(string='Attendee Name', index=True)
+ email = fields.Char(string='Email')
+ phone = fields.Char(string='Phone')
+ mobile = fields.Char(string='Mobile')
+ # organization
+ date_open = fields.Datetime(string='Registration Date', readonly=True, default=lambda self: fields.Datetime.now()) # weird crash is directly now
+ date_closed = fields.Datetime(string='Attended Date', readonly=True)
+ event_begin_date = fields.Datetime(string="Event Start Date", related='event_id.date_begin', readonly=True)
+ event_end_date = fields.Datetime(string="Event End Date", related='event_id.date_end', readonly=True)
+ company_id = fields.Many2one(
+ 'res.company', string='Company', related='event_id.company_id',
+ store=True, readonly=True, states={'draft': [('readonly', False)]})
+ state = fields.Selection([
+ ('draft', 'Unconfirmed'), ('cancel', 'Cancelled'),
+ ('open', 'Confirmed'), ('done', 'Attended')],
+ string='Status', default='draft', readonly=True, copy=False, tracking=True)
+
+ @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.'))
+
+ def _check_auto_confirmation(self):
+ if self._context.get('registration_force_draft'):
+ return False
+ if any(not registration.event_id.auto_confirm or
+ (not registration.event_id.seats_available and registration.event_id.seats_availability == 'limited') for registration in self):
+ return False
+ return True
+
+ @api.model
+ def create(self, vals):
+ registration = super(EventRegistration, self).create(vals)
+ if registration._check_auto_confirmation():
+ registration.sudo().confirm_registration()
+ return registration
+
+ @api.model
+ def _prepare_attendee_values(self, registration):
+ """ Method preparing the values to create new attendees based on a
+ sales order line. It takes some registration data (dict-based) that are
+ optional values coming from an external input like a web page. This method
+ is meant to be inherited in various addons that sell events. """
+ partner_id = registration.pop('partner_id', self.env.user.partner_id)
+ event_id = registration.pop('event_id', False)
+ data = {
+ 'name': registration.get('name', partner_id.name),
+ 'phone': registration.get('phone', partner_id.phone),
+ 'mobile': registration.get('mobile', partner_id.mobile),
+ 'email': registration.get('email', partner_id.email),
+ 'partner_id': partner_id.id,
+ 'event_id': event_id and event_id.id or False,
+ }
+ data.update({key: value for key, value in registration.items() if key in self._fields})
+ return data
+
+ def do_draft(self):
+ self.write({'state': 'draft'})
+
+ def confirm_registration(self):
+ self.write({'state': 'open'})
+
+ # auto-trigger after_sub (on subscribe) mail schedulers, if needed
+ onsubscribe_schedulers = self.event_id.event_mail_ids.filtered(
+ lambda s: s.interval_type == 'after_sub')
+ onsubscribe_schedulers.execute()
+
+ def button_reg_close(self):
+ """ Close Registration """
+ for registration in self:
+ registration.write({'state': 'done', 'date_closed': fields.Datetime.now()})
+
+ def button_reg_cancel(self):
+ self.write({'state': 'cancel'})
+
+ @api.onchange('partner_id')
+ def _onchange_partner(self):
+ if self.partner_id:
+ contact_id = self.partner_id.address_get().get('contact', False)
+ if contact_id:
+ contact = self.env['res.partner'].browse(contact_id)
+ self.name = contact.name or self.name
+ self.email = contact.email or self.email
+ self.phone = contact.phone or self.phone
+ self.mobile = contact.mobile or self.mobile
+
+ def _message_get_suggested_recipients(self):
+ recipients = super(EventRegistration, self)._message_get_suggested_recipients()
+ public_users = self.env['res.users'].sudo()
+ public_groups = self.env.ref("base.group_public", raise_if_not_found=False)
+ if public_groups:
+ public_users = public_groups.sudo().with_context(active_test=False).mapped("users")
+ try:
+ for attendee in self:
+ is_public = attendee.sudo().with_context(active_test=False).partner_id.user_ids in public_users if public_users else False
+ if attendee.partner_id and not is_public:
+ attendee._message_add_suggested_recipient(recipients, partner=attendee.partner_id, reason=_('Customer'))
+ elif attendee.email:
+ attendee._message_add_suggested_recipient(recipients, email=attendee.email, reason=_('Customer Email'))
+ except AccessError: # no read access rights -> ignore suggested recipients
+ pass
+ return recipients
+
+ def _message_get_default_recipients(self):
+ # Prioritize registration email over partner_id, which may be shared when a single
+ # partner booked multiple seats
+ return {r.id: {
+ 'partner_ids': [],
+ 'email_to': r.email,
+ 'email_cc': False}
+ for r in self}
+
+ def _message_post_after_hook(self, message, msg_vals):
+ if self.email and not self.partner_id:
+ # we consider that posting a message with a specified recipient (not a follower, a specific one)
+ # on a document without customer means that it was created through the chatter using
+ # suggested recipients. This heuristic allows to avoid ugly hacks in JS.
+ new_partner = message.partner_ids.filtered(lambda partner: partner.email == self.email)
+ if new_partner:
+ self.search([
+ ('partner_id', '=', False),
+ ('email', '=', new_partner.email),
+ ('state', 'not in', ['cancel']),
+ ]).write({'partner_id': new_partner.id})
+ return super(EventRegistration, self)._message_post_after_hook(message, msg_vals)
+
+ def action_send_badge_email(self):
+ """ Open a window to compose an email, with the template - 'event_badge'
+ message loaded by default
+ """
+ self.ensure_one()
+ template = self.env.ref('event.event_registration_mail_template_badge')
+ compose_form = self.env.ref('mail.email_compose_message_wizard_form')
+ ctx = dict(
+ default_model='event.registration',
+ default_res_id=self.id,
+ default_use_template=bool(template),
+ default_template_id=template.id,
+ default_composition_mode='comment',
+ custom_layout="mail.mail_notification_light",
+ )
+ return {
+ 'name': _('Compose Email'),
+ 'type': 'ir.actions.act_window',
+ 'view_mode': 'form',
+ 'res_model': 'mail.compose.message',
+ 'views': [(compose_form.id, 'form')],
+ 'view_id': compose_form.id,
+ 'target': 'new',
+ 'context': ctx,
+ }
+
+ def get_date_range_str(self):
+ self.ensure_one()
+ today = fields.Datetime.now()
+ event_date = self.event_begin_date
+ diff = (event_date.date() - today.date())
+ if diff.days <= 0:
+ return _('today')
+ elif diff.days == 1:
+ return _('tomorrow')
+ elif (diff.days < 7):
+ return _('in %d days') % (diff.days, )
+ elif (diff.days < 14):
+ return _('next week')
+ elif event_date.month == (today + relativedelta(months=+1)).month:
+ return _('next month')
+ else:
+ return _('on ') + format_datetime(self.env, self.event_begin_date, tz=self.event_id.date_tz, dt_format='medium')
+
+ def summary(self):
+ self.ensure_one()
+ return {'information': []}
diff --git a/addons/event/models/event_stage.py b/addons/event/models/event_stage.py
index d4064e3f9d4..e985df62c68 100644
--- a/addons/event/models/event_stage.py
+++ b/addons/event/models/event_stage.py
@@ -16,7 +16,6 @@ class EventStage(models.Model):
pipe_end = fields.Boolean(
string='End Stage', default=False,
help='Events will be automatically be moved into this stage their are passed and event moved in the stage will be automatically greened')
-
legend_blocked = fields.Char(
'Red Kanban Label', default=lambda s: _('Blocked'), translate=True, required=True,
help='Override the default value displayed for the blocked state for kanban selection.')
diff --git a/addons/event/tests/test_event_internals.py b/addons/event/tests/test_event_internals.py
index 905222dcc5f..2a79086808a 100644
--- a/addons/event/tests/test_event_internals.py
+++ b/addons/event/tests/test_event_internals.py
@@ -16,7 +16,7 @@ class TestEventData(TestEventCommon):
@users('user_eventmanager')
def test_event_date_computation(self):
- self.patcher = patch('odoo.addons.event.models.event.fields.Datetime', wraps=FieldsDatetime)
+ self.patcher = patch('odoo.addons.event.models.event_event.fields.Datetime', wraps=FieldsDatetime)
self.mock_datetime = self.patcher.start()
self.mock_datetime.now.return_value = datetime(2020, 1, 31, 8, 0, 0)
diff --git a/addons/event_sale/models/__init__.py b/addons/event_sale/models/__init__.py
index 0eba1dce03b..7228262e083 100644
--- a/addons/event_sale/models/__init__.py
+++ b/addons/event_sale/models/__init__.py
@@ -1,6 +1,8 @@
# -*- coding: utf-8 -*-
-from . import account_invoice
+from . import account_move
+from . import event_event
+from . import event_registration
+from . import event_ticket
from . import sale_order
from . import product
-from . import event
diff --git a/addons/event_sale/models/account_invoice.py b/addons/event_sale/models/account_move.py
index 0f5b46f2748..0f5b46f2748 100644
--- a/addons/event_sale/models/account_invoice.py
+++ b/addons/event_sale/models/account_move.py
diff --git a/addons/event_sale/models/event.py b/addons/event_sale/models/event.py
deleted file mode 100644
index 0f55cf89e37..00000000000
--- a/addons/event_sale/models/event.py
+++ /dev/null
@@ -1,321 +0,0 @@
-# -*- coding: utf-8 -*-
-# Part of Odoo. See LICENSE file for full copyright and licensing details.
-
-import datetime
-
-from odoo import api, fields, models, _
-from odoo.exceptions import ValidationError, UserError
-
-from odoo.tools import float_is_zero
-
-
-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
- })
-
-
-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')
-
- sale_total_price = fields.Monetary(compute='_compute_sale_total_price')
- currency_id = fields.Many2one(
- 'res.currency', string='Currency',
- default=lambda self: self.env.company.currency_id.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('sale_order_lines_ids')
- def _compute_sale_total_price(self):
- for event in self:
- event.sale_total_price = sum([
- event.currency_id._convert(
- sale_order_line_id.price_reduce_taxexcl,
- sale_order_line_id.currency_id,
- sale_order_line_id.company_id,
- sale_order_line_id.order_id.date_order)
- 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()
-
- @api.depends('sale_order_lines_ids')
- def _compute_sale_total_price(self):
- for event in self:
- event.sale_total_price = sum([
- event.currency_id._convert(
- sale_order_line_id.price_reduce_taxexcl,
- sale_order_line_id.currency_id,
- sale_order_line_id.company_id,
- sale_order_line_id.order_id.date_order)
- for sale_order_line_id in event.sale_order_lines_ids
- ])
-
-
-class EventTicket(models.Model):
- _name = 'event.event.ticket'
- _description = 'Event 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_id = fields.Many2one('product.product', string='Product',
- required=True, domain=[("event_ok", "=", True)],
- default=_default_product_id)
- registration_ids = fields.One2many('event.registration', 'event_ticket_id', string='Registrations')
- price = fields.Float(string='Price', digits='Product Price')
- 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')
-
- 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')
- # 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
-
- 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.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)
-
- 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
-
- return name
-
- @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.'))
-
-
-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
- sale_order_id = fields.Many2one('sale.order', string='Source Sales Order', ondelete='cascade')
- sale_order_line_id = fields.Many2one('sale.order.line', string='Sales Order Line', ondelete='cascade')
- campaign_id = fields.Many2one('utm.campaign', 'Campaign', related="sale_order_id.campaign_id", store=True)
- 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:
- orders = self.env['sale.order'].search([('state', '=', 'draft'), ('id', 'in', self.mapped('sale_order_id').ids)], limit=1)
- if orders:
- res = False
- return res
-
- @api.model
- def create(self, vals):
- res = super(EventRegistration, self).create(vals)
- if res.origin or res.sale_order_id:
- res.message_post_with_view('mail.message_origin_link',
- values={'self': res, 'origin': res.sale_order_id},
- subtype_id=self.env.ref('mail.mt_note').id)
- return res
-
- @api.model
- def _prepare_attendee_values(self, registration):
- """ Override to add sale related stuff """
- line_id = registration.get('sale_order_line_id')
- if line_id:
- registration.setdefault('partner_id', line_id.order_id.partner_id)
- att_data = super(EventRegistration, self)._prepare_attendee_values(registration)
- if line_id and line_id.event_ticket_id.sale_available:
- att_data.update({
- 'event_id': line_id.event_id.id,
- 'event_ticket_id': line_id.event_ticket_id.id,
- 'origin': line_id.order_id.name,
- 'sale_order_id': line_id.order_id.id,
- 'sale_order_line_id': line_id.id,
- })
- return att_data
-
- def summary(self):
- res = super(EventRegistration, self).summary()
- if self.event_ticket_id.product_id.image_128:
- res['image'] = '/web/image/product.product/%s/image_128' % self.event_ticket_id.product_id.id
- information = res.setdefault('information', {})
- information.append((_('Name'), self.name))
- information.append((_('Ticket'), self.event_ticket_id.name or _('None')))
- order = self.sale_order_id.sudo()
- order_line = self.sale_order_line_id.sudo()
- if not order or float_is_zero(order_line.price_total, precision_digits=order.currency_id.rounding):
- payment_status = _('Free')
- elif not order.invoice_ids or any(invoice.state != 'paid' for invoice in order.invoice_ids):
- payment_status = _('To pay')
- res['alert'] = _('The registration must be paid')
- else:
- payment_status = _('Paid')
- information.append((_('Payment'), payment_status))
- return res
diff --git a/addons/event_sale/models/event_event.py b/addons/event_sale/models/event_event.py
new file mode 100644
index 00000000000..47e9febedb0
--- /dev/null
+++ b/addons/event_sale/models/event_event.py
@@ -0,0 +1,98 @@
+# -*- 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
+ })
+
+
+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')
+
+ sale_total_price = fields.Monetary(compute='_compute_sale_total_price')
+ currency_id = fields.Many2one(
+ 'res.currency', string='Currency',
+ default=lambda self: self.env.company.currency_id.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('sale_order_lines_ids')
+ def _compute_sale_total_price(self):
+ for event in self:
+ event.sale_total_price = sum([
+ event.currency_id._convert(
+ sale_order_line_id.price_reduce_taxexcl,
+ sale_order_line_id.currency_id,
+ sale_order_line_id.company_id,
+ sale_order_line_id.order_id.date_order)
+ 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()
+
+ @api.depends('sale_order_lines_ids')
+ def _compute_sale_total_price(self):
+ for event in self:
+ event.sale_total_price = sum([
+ event.currency_id._convert(
+ sale_order_line_id.price_reduce_taxexcl,
+ sale_order_line_id.currency_id,
+ sale_order_line_id.company_id,
+ sale_order_line_id.order_id.date_order)
+ for sale_order_line_id in event.sale_order_lines_ids
+ ])
diff --git a/addons/event_sale/models/event_registration.py b/addons/event_sale/models/event_registration.py
new file mode 100644
index 00000000000..c67d4d77788
--- /dev/null
+++ b/addons/event_sale/models/event_registration.py
@@ -0,0 +1,85 @@
+# -*- 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
+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
+ sale_order_id = fields.Many2one('sale.order', string='Source Sales Order', ondelete='cascade')
+ sale_order_line_id = fields.Many2one('sale.order.line', string='Sales Order Line', ondelete='cascade')
+ campaign_id = fields.Many2one('utm.campaign', 'Campaign', related="sale_order_id.campaign_id", store=True)
+ 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:
+ orders = self.env['sale.order'].search([('state', '=', 'draft'), ('id', 'in', self.mapped('sale_order_id').ids)], limit=1)
+ if orders:
+ res = False
+ return res
+
+ @api.model
+ def create(self, vals):
+ res = super(EventRegistration, self).create(vals)
+ if res.origin or res.sale_order_id:
+ res.message_post_with_view('mail.message_origin_link',
+ values={'self': res, 'origin': res.sale_order_id},
+ subtype_id=self.env.ref('mail.mt_note').id)
+ return res
+
+ @api.model
+ def _prepare_attendee_values(self, registration):
+ """ Override to add sale related stuff """
+ line_id = registration.get('sale_order_line_id')
+ if line_id:
+ registration.setdefault('partner_id', line_id.order_id.partner_id)
+ att_data = super(EventRegistration, self)._prepare_attendee_values(registration)
+ if line_id and line_id.event_ticket_id.sale_available:
+ att_data.update({
+ 'event_id': line_id.event_id.id,
+ 'event_ticket_id': line_id.event_ticket_id.id,
+ 'origin': line_id.order_id.name,
+ 'sale_order_id': line_id.order_id.id,
+ 'sale_order_line_id': line_id.id,
+ })
+ return att_data
+
+ def summary(self):
+ res = super(EventRegistration, self).summary()
+ if self.event_ticket_id.product_id.image_128:
+ res['image'] = '/web/image/product.product/%s/image_128' % self.event_ticket_id.product_id.id
+ information = res.setdefault('information', {})
+ information.append((_('Name'), self.name))
+ information.append((_('Ticket'), self.event_ticket_id.name or _('None')))
+ order = self.sale_order_id.sudo()
+ order_line = self.sale_order_line_id.sudo()
+ if not order or float_is_zero(order_line.price_total, precision_digits=order.currency_id.rounding):
+ payment_status = _('Free')
+ elif not order.invoice_ids or any(invoice.state != 'paid' for invoice in order.invoice_ids):
+ payment_status = _('To pay')
+ res['alert'] = _('The registration must be paid')
+ else:
+ payment_status = _('Paid')
+ information.append((_('Payment'), payment_status))
+ return res
diff --git a/addons/event_sale/models/event_ticket.py b/addons/event_sale/models/event_ticket.py
new file mode 100644
index 00000000000..f0622873da4
--- /dev/null
+++ b/addons/event_sale/models/event_ticket.py
@@ -0,0 +1,144 @@
+# -*- 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 EventTicket(models.Model):
+ _name = 'event.event.ticket'
+ _description = 'Event 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_id = fields.Many2one('product.product', string='Product',
+ required=True, domain=[("event_ok", "=", True)],
+ default=_default_product_id)
+ registration_ids = fields.One2many('event.registration', 'event_ticket_id', string='Registrations')
+ price = fields.Float(string='Price', digits='Product Price')
+ 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')
+
+ 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')
+ # 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
+
+ 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.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)
+
+ 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
+
+ return name
+
+ @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.'))
diff --git a/addons/event_sms/models/__init__.py b/addons/event_sms/models/__init__.py
index b8ced32a4d7..95c3bd06c29 100644
--- a/addons/event_sms/models/__init__.py
+++ b/addons/event_sms/models/__init__.py
@@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
-from . import event
from . import event_mail
+from . import event_registration
diff --git a/addons/event_sms/models/event.py b/addons/event_sms/models/event_registration.py
index d8be5a5390a..d8be5a5390a 100644
--- a/addons/event_sms/models/event.py
+++ b/addons/event_sms/models/event_registration.py
diff --git a/addons/website_event/models/__init__.py b/addons/website_event/models/__init__.py
index 5004a8f8411..e972cdb2a6f 100644
--- a/addons/website_event/models/__init__.py
+++ b/addons/website_event/models/__init__.py
@@ -1,3 +1,3 @@
# -*- coding: utf-8 -*-
-from . import event
+from . import event_event
diff --git a/addons/website_event/models/event.py b/addons/website_event/models/event_event.py
index 0fa062e5162..0fa062e5162 100644
--- a/addons/website_event/models/event.py
+++ b/addons/website_event/models/event_event.py
diff --git a/addons/website_event_questions/models/__init__.py b/addons/website_event_questions/models/__init__.py
index 5004a8f8411..14919447769 100644
--- a/addons/website_event_questions/models/__init__.py
+++ b/addons/website_event_questions/models/__init__.py
@@ -1,3 +1,5 @@
# -*- coding: utf-8 -*-
-from . import event
+from . import event_event
+from . import event_question
+from . import event_registration
diff --git a/addons/website_event_questions/models/event_event.py b/addons/website_event_questions/models/event_event.py
new file mode 100644
index 00000000000..c5c04f169f7
--- /dev/null
+++ b/addons/website_event_questions/models/event_event.py
@@ -0,0 +1,37 @@
+# -*- 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'
+
+ use_questions = fields.Boolean('Questions to Attendees')
+ question_ids = fields.One2many(
+ 'event.question', 'event_type_id',
+ string='Questions', copy=True)
+
+
+class EventEvent(models.Model):
+ """ Override Event model to add optional questions when buying tickets. """
+ _inherit = 'event.event'
+
+ question_ids = fields.One2many('event.question', 'event_id', 'Questions', copy=True)
+ general_question_ids = fields.One2many('event.question', 'event_id', 'General Questions',
+ domain=[('is_individual', '=', False)])
+ specific_question_ids = fields.One2many('event.question', 'event_id', 'Specific Questions',
+ domain=[('is_individual', '=', True)])
+
+ @api.onchange('event_type_id')
+ def _onchange_type(self):
+ super(EventEvent, self)._onchange_type()
+ if self.event_type_id.use_questions and self.event_type_id.question_ids:
+ self.question_ids = [(5, 0, 0)] + [
+ (0, 0, {
+ 'title': question.title,
+ 'sequence': question.sequence,
+ 'is_individual': question.is_individual,
+ })
+ for question in self.event_type_id.question_ids
+ ]
diff --git a/addons/website_event_questions/models/event.py b/addons/website_event_questions/models/event_question.py
index 43b33d1368f..f54fbdcc83b 100644
--- a/addons/website_event_questions/models/event.py
+++ b/addons/website_event_questions/models/event_question.py
@@ -5,60 +5,6 @@ from odoo import api, fields, models, _
from odoo.exceptions import UserError
-class EventType(models.Model):
- _inherit = 'event.type'
-
- use_questions = fields.Boolean('Questions to Attendees')
- question_ids = fields.One2many(
- 'event.question', 'event_type_id',
- string='Questions', copy=True)
-
-
-class EventEvent(models.Model):
- """ Override Event model to add optional questions when buying tickets. """
- _inherit = 'event.event'
-
- question_ids = fields.One2many('event.question', 'event_id', 'Questions', copy=True)
- general_question_ids = fields.One2many('event.question', 'event_id', 'General Questions',
- domain=[('is_individual', '=', False)])
- specific_question_ids = fields.One2many('event.question', 'event_id', 'Specific Questions',
- domain=[('is_individual', '=', True)])
-
- @api.onchange('event_type_id')
- def _onchange_type(self):
- super(EventEvent, self)._onchange_type()
- if self.event_type_id.use_questions and self.event_type_id.question_ids:
- self.question_ids = [(5, 0, 0)] + [
- (0, 0, {
- 'title': question.title,
- 'sequence': question.sequence,
- 'is_individual': question.is_individual,
- })
- for question in self.event_type_id.question_ids
- ]
-
-
-class EventRegistrationAnswer(models.Model):
- ''' This m2m table has to be explicitly instanciated as we need unique ids
- in the reporting view event.question.report.
-
- This model is purely technical. '''
-
- _name = 'event.registration.answer'
- _table = 'event_registration_answer'
- _description = 'Event Registration Answer'
-
- event_answer_id = fields.Many2one('event.answer', required=True, ondelete='cascade')
- event_registration_id = fields.Many2one('event.registration', required=True, ondelete='cascade')
-
-
-class EventRegistration(models.Model):
- """ Store answers on attendees. """
- _inherit = 'event.registration'
-
- answer_ids = fields.Many2many('event.answer', 'event_registration_answer', string='Answers')
-
-
class EventQuestion(models.Model):
_name = 'event.question'
_rec_name = 'title'
diff --git a/addons/website_event_questions/models/event_registration.py b/addons/website_event_questions/models/event_registration.py
new file mode 100644
index 00000000000..b8f23da99d7
--- /dev/null
+++ b/addons/website_event_questions/models/event_registration.py
@@ -0,0 +1,25 @@
+# -*- coding: utf-8 -*-
+# Part of Odoo. See LICENSE file for full copyright and licensing details.
+
+from odoo import fields, models
+
+
+class EventRegistrationAnswer(models.Model):
+ ''' This m2m table has to be explicitly instanciated as we need unique ids
+ in the reporting view event.question.report.
+
+ This model is purely technical. '''
+
+ _name = 'event.registration.answer'
+ _table = 'event_registration_answer'
+ _description = 'Event Registration Answer'
+
+ event_answer_id = fields.Many2one('event.answer', required=True, ondelete='cascade')
+ event_registration_id = fields.Many2one('event.registration', required=True, ondelete='cascade')
+
+
+class EventRegistration(models.Model):
+ """ Store answers on attendees. """
+ _inherit = 'event.registration'
+
+ answer_ids = fields.Many2many('event.answer', 'event_registration_answer', string='Answers')
diff --git a/addons/website_event_track/models/__init__.py b/addons/website_event_track/models/__init__.py
index 48beb002677..1a9f0aa1fe0 100644
--- a/addons/website_event_track/models/__init__.py
+++ b/addons/website_event_track/models/__init__.py
@@ -1,5 +1,7 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
+from . import event_event
+from . import event_sponsor
from . import event_track
-from . import event
+from . import event_track_stage
diff --git a/addons/website_event_track/models/event.py b/addons/website_event_track/models/event_event.py
index 1c75e41f857..1c75e41f857 100644
--- a/addons/website_event_track/models/event.py
+++ b/addons/website_event_track/models/event_event.py
diff --git a/addons/website_event_track/models/event_sponsor.py b/addons/website_event_track/models/event_sponsor.py
new file mode 100644
index 00000000000..53a587e1a2b
--- /dev/null
+++ b/addons/website_event_track/models/event_sponsor.py
@@ -0,0 +1,26 @@
+# -*- coding: utf-8 -*-
+# Part of Odoo. See LICENSE file for full copyright and licensing details.
+
+from odoo import fields, models
+
+
+class SponsorType(models.Model):
+ _name = "event.sponsor.type"
+ _description = 'Event Sponsor Type'
+ _order = "sequence"
+
+ name = fields.Char('Sponsor Type', required=True, translate=True)
+ sequence = fields.Integer('Sequence')
+
+
+class Sponsor(models.Model):
+ _name = "event.sponsor"
+ _description = 'Event Sponsor'
+ _order = "sequence"
+
+ event_id = fields.Many2one('event.event', 'Event', required=True)
+ sponsor_type_id = fields.Many2one('event.sponsor.type', 'Sponsoring Type', required=True)
+ partner_id = fields.Many2one('res.partner', 'Sponsor/Customer', required=True)
+ url = fields.Char('Sponsor Website')
+ sequence = fields.Integer('Sequence', store=True, related='sponsor_type_id.sequence', readonly=False)
+ image_128 = fields.Image(string="Logo", related='partner_id.image_128', store=True, readonly=False)
diff --git a/addons/website_event_track/models/event_track.py b/addons/website_event_track/models/event_track.py
index bbfdb93d369..7d7b20a72be 100644
--- a/addons/website_event_track/models/event_track.py
+++ b/addons/website_event_track/models/event_track.py
@@ -28,25 +28,6 @@ class TrackLocation(models.Model):
name = fields.Char('Location', required=True)
-class TrackStage(models.Model):
- _name = 'event.track.stage'
- _description = 'Event Track Stage'
- _order = 'sequence, id'
-
- name = fields.Char(string='Stage Name', required=True, translate=True)
- sequence = fields.Integer(string='Sequence', default=1)
- mail_template_id = fields.Many2one(
- 'mail.template', string='Email Template',
- domain=[('model', '=', 'event.track')],
- help="If set an email will be sent to the customer when the track reaches this step.")
- fold = fields.Boolean(
- string='Folded in Kanban',
- help='This stage is folded in the kanban view when there are no records in that stage to display.')
- is_done = fields.Boolean(string='Accepted Stage')
- is_cancel = fields.Boolean(string='Canceled Stage')
- color = fields.Integer(string='Color')
-
-
class Track(models.Model):
_name = "event.track"
_description = 'Event Track'
@@ -194,25 +175,3 @@ class Track(models.Model):
'view_id': False,
'type': 'ir.actions.act_window',
}
-
-
-class SponsorType(models.Model):
- _name = "event.sponsor.type"
- _description = 'Event Sponsor Type'
- _order = "sequence"
-
- name = fields.Char('Sponsor Type', required=True, translate=True)
- sequence = fields.Integer('Sequence')
-
-
-class Sponsor(models.Model):
- _name = "event.sponsor"
- _description = 'Event Sponsor'
- _order = "sequence"
-
- event_id = fields.Many2one('event.event', 'Event', required=True)
- sponsor_type_id = fields.Many2one('event.sponsor.type', 'Sponsoring Type', required=True)
- partner_id = fields.Many2one('res.partner', 'Sponsor/Customer', required=True)
- url = fields.Char('Sponsor Website')
- sequence = fields.Integer('Sequence', store=True, related='sponsor_type_id.sequence', readonly=False)
- image_128 = fields.Image(string="Logo", related='partner_id.image_128', store=True, readonly=False)
diff --git a/addons/website_event_track/models/event_track_stage.py b/addons/website_event_track/models/event_track_stage.py
new file mode 100644
index 00000000000..c019c32536d
--- /dev/null
+++ b/addons/website_event_track/models/event_track_stage.py
@@ -0,0 +1,23 @@
+# -*- coding: utf-8 -*-
+# Part of Odoo. See LICENSE file for full copyright and licensing details.
+
+from odoo import fields, models
+
+
+class TrackStage(models.Model):
+ _name = 'event.track.stage'
+ _description = 'Event Track Stage'
+ _order = 'sequence, id'
+
+ name = fields.Char(string='Stage Name', required=True, translate=True)
+ sequence = fields.Integer(string='Sequence', default=1)
+ mail_template_id = fields.Many2one(
+ 'mail.template', string='Email Template',
+ domain=[('model', '=', 'event.track')],
+ help="If set an email will be sent to the customer when the track reaches this step.")
+ fold = fields.Boolean(
+ string='Folded in Kanban',
+ help='This stage is folded in the kanban view when there are no records in that stage to display.')
+ is_done = fields.Boolean(string='Accepted Stage')
+ is_cancel = fields.Boolean(string='Canceled Stage')
+ color = fields.Integer(string='Color')