summaryrefslogtreecommitdiff
path: root/addons/event/models/event_registration.py
diff options
context:
space:
mode:
authorstephanchrst <stephanchrst@gmail.com>2022-05-10 21:51:50 +0700
committerstephanchrst <stephanchrst@gmail.com>2022-05-10 21:51:50 +0700
commit3751379f1e9a4c215fb6eb898b4ccc67659b9ace (patch)
treea44932296ef4a9b71d5f010906253d8c53727726 /addons/event/models/event_registration.py
parent0a15094050bfde69a06d6eff798e9a8ddf2b8c21 (diff)
initial commit 2
Diffstat (limited to 'addons/event/models/event_registration.py')
-rw-r--r--addons/event/models/event_registration.py305
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,
+ }