diff options
| author | stephanchrst <stephanchrst@gmail.com> | 2022-05-10 21:51:50 +0700 |
|---|---|---|
| committer | stephanchrst <stephanchrst@gmail.com> | 2022-05-10 21:51:50 +0700 |
| commit | 3751379f1e9a4c215fb6eb898b4ccc67659b9ace (patch) | |
| tree | a44932296ef4a9b71d5f010906253d8c53727726 /addons/event/models/event_registration.py | |
| parent | 0a15094050bfde69a06d6eff798e9a8ddf2b8c21 (diff) | |
initial commit 2
Diffstat (limited to 'addons/event/models/event_registration.py')
| -rw-r--r-- | addons/event/models/event_registration.py | 305 |
1 files changed, 305 insertions, 0 deletions
diff --git a/addons/event/models/event_registration.py b/addons/event/models/event_registration.py new file mode 100644 index 00000000..5b14153b --- /dev/null +++ b/addons/event/models/event_registration.py @@ -0,0 +1,305 @@ +# -*- 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.sudo().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, + } |
