Avoid concatenation of translated strings & raw values and/or other translated values, and use the new _ API (by providing the formatting values to ensure the fallback on the english terms in case of wrong translation). By including the expected values inside the string to translate, we ensure that the formatted values are included at the right place in the resulting translated text. This also provides more information on the string to translate to the translators, s.t. the chosen terms for the translations can be more adapted to the translation context. closes odoo/odoo#54635 Related: odoo/enterprise#11925 Signed-off-by: Victor Feyens (vfe) <vfe@odoo.com>
281 lines
12 KiB
Python
281 lines
12 KiB
Python
# -*- 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
|
|
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, ondelete='restrict',
|
|
states={'draft': [('readonly', False)]})
|
|
# utm informations
|
|
utm_campaign_id = fields.Many2one('utm.campaign', 'Campaign', index=True, ondelete='set null')
|
|
utm_source_id = fields.Many2one('utm.source', 'Source', index=True, ondelete='set null')
|
|
utm_medium_id = fields.Many2one('utm.medium', 'Medium', index=True, ondelete='set null')
|
|
# attendee
|
|
partner_id = fields.Many2one(
|
|
'res.partner', string='Booked by',
|
|
states={'done': [('readonly', True)]})
|
|
name = fields.Char(
|
|
string='Attendee Name', index=True,
|
|
compute='_compute_contact_info', readonly=False, store=True, tracking=10)
|
|
email = fields.Char(string='Email', compute='_compute_contact_info', readonly=False, store=True, tracking=11)
|
|
phone = fields.Char(string='Phone', compute='_compute_contact_info', readonly=False, store=True, tracking=12)
|
|
mobile = fields.Char(string='Mobile', compute='_compute_contact_info', readonly=False, store=True, tracking=13)
|
|
# 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', compute='_compute_date_closed',
|
|
readonly=False, store=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.onchange('partner_id')
|
|
def _onchange_partner_id(self):
|
|
""" Keep an explicit onchange on partner_id. Rationale : if user explicitly
|
|
changes the partner in interface, he want to update the whole customer
|
|
information. If partner_id is updated in code (e.g. updating your personal
|
|
information after having registered in website_event_sale) fields with a
|
|
value should not be reset as we don't know which one is the right one.
|
|
|
|
In other words
|
|
* computed fields based on partner_id should only update missing
|
|
information. Indeed automated code cannot decide which information
|
|
is more accurate;
|
|
* interface should allow to update all customer related information
|
|
at once. We consider event users really want to update all fields
|
|
related to the partner;
|
|
"""
|
|
for registration in self:
|
|
if registration.partner_id:
|
|
registration.update(registration._synchronize_partner_values(registration.partner_id))
|
|
|
|
@api.depends('partner_id')
|
|
def _compute_contact_info(self):
|
|
for registration in self:
|
|
if registration.partner_id:
|
|
partner_vals = self._synchronize_partner_values(registration.partner_id)
|
|
registration.update(
|
|
dict((fname, fvalue)
|
|
for fname, fvalue in partner_vals.items()
|
|
if fvalue and not (registration[fname] or registration._origin[fname])
|
|
)
|
|
)
|
|
|
|
@api.depends('state')
|
|
def _compute_date_closed(self):
|
|
for registration in self:
|
|
if not registration.date_closed:
|
|
if registration.state == 'done':
|
|
registration.date_closed = fields.Datetime.now()
|
|
else:
|
|
registration.date_closed = False
|
|
|
|
@api.constrains('event_id', 'state')
|
|
def _check_seats_limit(self):
|
|
for registration in self:
|
|
if registration.event_id.seats_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.constrains('event_id', 'event_ticket_id')
|
|
def _check_event_ticket(self):
|
|
if any(registration.event_id != registration.event_ticket_id.event_id for registration in self if registration.event_ticket_id):
|
|
raise ValidationError(_('Invalid event / ticket choice'))
|
|
|
|
# ------------------------------------------------------------
|
|
# CRUD
|
|
# ------------------------------------------------------------
|
|
|
|
@api.model_create_multi
|
|
def create(self, vals_list):
|
|
registrations = super(EventRegistration, self).create(vals_list)
|
|
if registrations._check_auto_confirmation():
|
|
registrations.sudo().action_confirm()
|
|
|
|
return registrations
|
|
|
|
def write(self, vals):
|
|
ret = super(EventRegistration, self).write(vals)
|
|
|
|
if vals.get('state') == 'open':
|
|
# auto-trigger after_sub (on subscribe) mail schedulers, if needed
|
|
onsubscribe_schedulers = self.mapped('event_id.event_mail_ids').filtered(lambda s: s.interval_type == 'after_sub')
|
|
onsubscribe_schedulers.execute()
|
|
|
|
return ret
|
|
|
|
def name_get(self):
|
|
""" Custom name_get implementation to better differentiate registrations
|
|
linked to a given partner but with different name (one partner buying
|
|
several registrations)
|
|
|
|
* name, partner_id has no name -> take name
|
|
* partner_id has name, name void or same -> take partner name
|
|
* both have name: partner + name
|
|
"""
|
|
ret_list = []
|
|
for registration in self:
|
|
if registration.partner_id.name:
|
|
if registration.name and registration.name != registration.partner_id.name:
|
|
name = '%s, %s' % (registration.partner_id.name, registration.name)
|
|
else:
|
|
name = registration.partner_id.name
|
|
else:
|
|
name = registration.name
|
|
ret_list.append((registration.id, name))
|
|
return ret_list
|
|
|
|
def _check_auto_confirmation(self):
|
|
if any(not registration.event_id.auto_confirm or
|
|
(not registration.event_id.seats_available and registration.event_id.seats_limited) for registration in self):
|
|
return False
|
|
return True
|
|
|
|
def _synchronize_partner_values(self, partner):
|
|
if partner:
|
|
contact_id = partner.address_get().get('contact', False)
|
|
if contact_id:
|
|
contact = self.env['res.partner'].browse(contact_id)
|
|
return dict((fname, contact[fname]) for fname in ['name', 'email', 'phone', 'mobile'] if contact[fname])
|
|
return {}
|
|
|
|
# ------------------------------------------------------------
|
|
# ACTIONS / BUSINESS
|
|
# ------------------------------------------------------------
|
|
|
|
def action_set_draft(self):
|
|
self.write({'state': 'draft'})
|
|
|
|
def action_confirm(self):
|
|
self.write({'state': 'open'})
|
|
|
|
def action_set_done(self):
|
|
""" Close Registration """
|
|
self.write({'state': 'done'})
|
|
|
|
def action_cancel(self):
|
|
self.write({'state': 'cancel'})
|
|
|
|
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 %(date)s', date=format_datetime(self.env, self.event_begin_date, tz=self.event_id.date_tz, dt_format='medium'))
|
|
|
|
def _get_registration_summary(self):
|
|
self.ensure_one()
|
|
return {
|
|
'id': self.id,
|
|
'name': self.name,
|
|
'partner_id': self.partner_id.id,
|
|
'ticket_name': self.event_ticket_id.name or _('None'),
|
|
'event_id': self.event_id.id,
|
|
'event_display_name': self.event_id.display_name,
|
|
'company_name': self.event_id.company_id and self.event_id.company_id.name or False,
|
|
}
|