RATIONALE Stored editable fields receive their values either from compute either from user input. If a user input is given to create / write compute method is not called. If multiple fields are computed through the same method giving one field value discard call to compute method and other fields are not called. SPECIFICATIONS Split ``_compute_contact_info`` compute method so that partner related fields are independent. LINKS COM PR #65688 Task ID-2455165 X-original-commit odoo/odoo@dfc319f584 X-original-commit: c251048cfead27d12117557fe4b6601bef39e8e1
306 lines
14 KiB
Python
306 lines
14 KiB
Python
# -*- coding: utf-8 -*-
|
|
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
|
|
|
from dateutil.relativedelta import relativedelta
|
|
|
|
from odoo import _, api, fields, models
|
|
from odoo.tools import format_datetime
|
|
from odoo.exceptions import AccessError, ValidationError
|
|
|
|
|
|
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_name', readonly=False, store=True, tracking=10)
|
|
email = fields.Char(string='Email', compute='_compute_email', readonly=False, store=True, tracking=11)
|
|
phone = fields.Char(string='Phone', compute='_compute_phone', readonly=False, store=True, tracking=12)
|
|
mobile = fields.Char(string='Mobile', compute='_compute_mobile', 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_name(self):
|
|
for registration in self:
|
|
if not registration.name and registration.partner_id:
|
|
registration.name = registration._synchronize_partner_values(
|
|
registration.partner_id,
|
|
fnames=['name']
|
|
).get('name') or False
|
|
|
|
@api.depends('partner_id')
|
|
def _compute_email(self):
|
|
for registration in self:
|
|
if not registration.email and registration.partner_id:
|
|
registration.email = registration._synchronize_partner_values(
|
|
registration.partner_id,
|
|
fnames=['email']
|
|
).get('email') or False
|
|
|
|
@api.depends('partner_id')
|
|
def _compute_phone(self):
|
|
for registration in self:
|
|
if not registration.phone and registration.partner_id:
|
|
registration.phone = registration._synchronize_partner_values(
|
|
registration.partner_id,
|
|
fnames=['phone']
|
|
).get('phone') or False
|
|
|
|
@api.depends('partner_id')
|
|
def _compute_mobile(self):
|
|
for registration in self:
|
|
if not registration.mobile and registration.partner_id:
|
|
registration.mobile = registration._synchronize_partner_values(
|
|
registration.partner_id,
|
|
fnames=['mobile']
|
|
).get('mobile') or False
|
|
|
|
@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'))
|
|
|
|
def _synchronize_partner_values(self, partner, fnames=None):
|
|
if fnames is None:
|
|
fnames = ['name', 'email', 'phone', 'mobile']
|
|
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 fnames if contact[fname])
|
|
return {}
|
|
|
|
# ------------------------------------------------------------
|
|
# 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
|
|
|
|
# ------------------------------------------------------------
|
|
# 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,
|
|
}
|