1 # -*- coding: utf-8 -*-
5 from openerp import _, api, fields, models
6 from openerp.exceptions import Warning
9 class event_type(models.Model):
12 _description = 'Event Type'
14 name = fields.Char('Event Type', required=True)
15 default_reply_to = fields.Char('Reply To')
16 default_registration_min = fields.Integer(
17 'Default Minimum Registration', default=0,
18 help="It will select this default minimum value when you choose this event")
19 default_registration_max = fields.Integer(
20 'Default Maximum Registration', default=0,
21 help="It will select this default maximum value when you choose this event")
24 class event_event(models.Model):
27 _description = 'Event'
28 _inherit = ['mail.thread', 'ir.needaction_mixin']
32 string='Name', translate=True, required=True,
33 readonly=False, states={'done': [('readonly', True)]})
34 user_id = fields.Many2one(
35 'res.users', string='Responsible',
36 default=lambda self: self.env.user,
37 readonly=False, states={'done': [('readonly', True)]})
38 company_id = fields.Many2one(
39 'res.company', string='Company', change_default=True,
40 default=lambda self: self.env['res.company']._company_default_get('event.event'),
41 required=False, readonly=False, states={'done': [('readonly', True)]})
42 organizer_id = fields.Many2one(
43 'res.partner', string='Organizer',
44 default=lambda self: self.env.user.company_id.partner_id)
45 type = fields.Many2one(
46 'event.type', string='Category',
47 readonly=False, states={'done': [('readonly', True)]})
48 color = fields.Integer('Kanban Color Index')
49 event_mail_ids = fields.One2many('event.mail', 'event_id', string='Mail Schedule', default=lambda self: self._default_event_mail_ids())
52 def _default_event_mail_ids(self):
54 'interval_unit': 'now',
55 'interval_type': 'after_sub',
56 'template_id': self.env['ir.model.data'].xmlid_to_res_id('event.event_subscription')
59 # Seats and computation
60 seats_max = fields.Integer(
61 string='Maximum Available Seats', oldname='register_max',
62 readonly=True, states={'draft': [('readonly', False)]},
63 help="You can for each event define a maximum registration level. If you have too much registrations you are not able to confirm your event. (put 0 to ignore this rule )")
64 seats_availability = fields.Selection(
65 [('limited', 'Limited'), ('unlimited', 'Unlimited')],
66 'Available Seat', required=True, default='unlimited')
67 seats_min = fields.Integer(
68 string='Minimum Reserved Seats', oldname='register_min',
69 readonly=True, states={'draft': [('readonly', False)]},
70 help="You can for each event define a minimum registration level. If you do not enough registrations you are not able to confirm your event. (put 0 to ignore this rule )")
71 seats_reserved = fields.Integer(
72 oldname='register_current', string='Reserved Seats',
73 store=True, readonly=True, compute='_compute_seats')
74 seats_available = fields.Integer(
75 oldname='register_avail', string='Available Seats',
76 store=True, readonly=True, compute='_compute_seats')
77 seats_unconfirmed = fields.Integer(
78 oldname='register_prospect', string='Unconfirmed Seat Reservations',
79 store=True, readonly=True, compute='_compute_seats')
80 seats_used = fields.Integer(
81 oldname='register_attended', string='Number of Participations',
82 store=True, readonly=True, compute='_compute_seats')
85 @api.depends('seats_max', 'registration_ids.state')
86 def _compute_seats(self):
87 """ Determine reserved, available, reserved but unconfirmed and used seats. """
88 # initialize fields to 0
90 event.seats_unconfirmed = event.seats_reserved = event.seats_used = event.seats_available = 0
91 # aggregate registrations by event and by state
94 'draft': 'seats_unconfirmed',
95 'open': 'seats_reserved',
98 query = """ SELECT event_id, state, count(event_id)
99 FROM event_registration
100 WHERE event_id IN %s AND state IN ('draft', 'open', 'done')
101 GROUP BY event_id, state
103 self._cr.execute(query, (tuple(self.ids),))
104 for event_id, state, num in self._cr.fetchall():
105 event = self.browse(event_id)
106 event[state_field[state]] += num
107 # compute seats_available
109 if event.seats_max > 0:
110 event.seats_available = event.seats_max - (event.seats_reserved + event.seats_used)
112 # Registration fields
113 registration_ids = fields.One2many(
114 'event.registration', 'event_id', string='Attendees',
115 readonly=False, states={'done': [('readonly', True)]})
117 date_tz = fields.Selection('_tz_get', string='Timezone', default=lambda self: self.env.user.tz)
118 date_begin = fields.Datetime(
119 string='Start Date', required=True,
120 readonly=True, states={'draft': [('readonly', False)]})
121 date_end = fields.Datetime(
122 string='End Date', required=True,
123 readonly=True, states={'draft': [('readonly', False)]})
124 date_begin_located = fields.Datetime(string='Start Date Located', compute='_compute_date_begin_tz')
125 date_end_located = fields.Datetime(string='End Date Located', compute='_compute_date_end_tz')
129 return [(x, x) for x in pytz.all_timezones]
132 @api.depends('date_tz', 'date_begin')
133 def _compute_date_begin_tz(self):
135 self_in_tz = self.with_context(tz=(self.date_tz or 'UTC'))
136 date_begin = fields.Datetime.from_string(self.date_begin)
137 self.date_begin_located = fields.Datetime.to_string(fields.Datetime.context_timestamp(self_in_tz, date_begin))
139 self.date_begin_located = False
142 @api.depends('date_tz', 'date_end')
143 def _compute_date_end_tz(self):
145 self_in_tz = self.with_context(tz=(self.date_tz or 'UTC'))
146 date_end = fields.Datetime.from_string(self.date_end)
147 self.date_end_located = fields.Datetime.to_string(fields.Datetime.context_timestamp(self_in_tz, date_end))
149 self.date_end_located = False
151 state = fields.Selection([
152 ('draft', 'Unconfirmed'), ('cancel', 'Cancelled'),
153 ('confirm', 'Confirmed'), ('done', 'Done')],
154 string='Status', default='draft', readonly=True, required=True, copy=False,
155 help="If event is created, the status is 'Draft'. If event is confirmed for the particular dates the status is set to 'Confirmed'. If the event is over, the status is set to 'Done'. If event is cancelled the status is set to 'Cancelled'.")
156 auto_confirm = fields.Boolean(string='Auto Confirmation Activated', compute='_compute_auto_confirm')
159 def _compute_auto_confirm(self):
160 self.auto_confirm = self.env['ir.values'].get_default('marketing.config.settings', 'auto_confirmation')
162 reply_to = fields.Char(
163 'Reply-To Email', readonly=False, states={'done': [('readonly', True)]},
164 help="The email address of the organizer is likely to be put here, with the effect to be in the 'Reply-To' of the mails sent automatically at event or registrations confirmation. You can also put the email address of your mail gateway if you use one.")
165 address_id = fields.Many2one(
166 'res.partner', string='Location', default=lambda self: self.env.user.company_id.partner_id,
167 readonly=False, states={'done': [('readonly', True)]})
168 country_id = fields.Many2one('res.country', 'Country', related='address_id.country_id', store=True)
169 description = fields.Html(
170 string='Description', oldname='note', translate=True,
171 readonly=False, states={'done': [('readonly', True)]})
174 @api.depends('name', 'date_begin', 'date_end')
178 dates = [dt.split(' ')[0] for dt in [event.date_begin, event.date_end] if dt]
179 dates = sorted(set(dates))
180 result.append((event.id, '%s (%s)' % (event.name, ' - '.join(dates))))
184 @api.constrains('seats_max', 'seats_available')
185 def _check_seats_limit(self):
186 if self.seats_max and self.seats_available < 0:
187 raise Warning(_('No more available seats.'))
190 @api.constrains('date_begin', 'date_end')
191 def _check_closing_date(self):
192 if self.date_end < self.date_begin:
193 raise Warning(_('Closing Date cannot be set before Beginning Date.'))
196 def create(self, vals):
197 res = super(event_event, self).create(vals)
203 def button_draft(self):
207 def button_cancel(self):
208 for event_reg in self.registration_ids:
209 if event_reg.state == 'done':
210 raise Warning(_("You have already set a registration for this event as 'Attended'. Please reset it to draft if you want to cancel this event."))
211 self.registration_ids.write({'state': 'cancel'})
212 self.state = 'cancel'
215 def button_done(self):
219 def button_confirm(self):
220 self.state = 'confirm'
222 @api.onchange('type')
223 def _onchange_type(self):
225 self.seats_min = self.type.default_registration_min
226 self.seats_max = self.type.default_registration_max
227 self.reply_to = self.type.default_reply_to
230 def action_event_registration_report(self):
231 res = self.env['ir.actions.act_window'].for_xml_id('event', 'action_report_event_registration')
233 "search_default_event_id": self.id,
234 "group_by": ['create_date:day'],
239 def mail_attendees(self, template_id, force_send=False, filter_func=lambda self: True):
240 for attendee in self.registration_ids.filtered(filter_func):
241 self.env['email.template'].browse(template_id).send_mail(attendee.id, force_send=force_send)
244 class event_registration(models.Model):
245 _name = 'event.registration'
246 _description = 'Attendee'
247 _inherit = ['mail.thread', 'ir.needaction_mixin']
248 _order = 'name, create_date desc'
250 origin = fields.Char(
251 string='Source Document', readonly=True,
252 help="Reference of the document that created the registration, for example a sale order")
253 event_id = fields.Many2one(
254 'event.event', string='Event', required=True,
255 readonly=True, states={'draft': [('readonly', False)]})
256 partner_id = fields.Many2one(
257 'res.partner', string='Contact',
258 states={'done': [('readonly', True)]})
259 date_open = fields.Datetime(string='Registration Date', readonly=True, default=lambda self: fields.datetime.now()) # weird crash is directly now
260 date_closed = fields.Datetime(string='Attended Date', readonly=True)
261 event_begin_date = fields.Datetime(string="Event Start Date", related='event_id.date_begin', readonly=True)
262 event_end_date = fields.Datetime(string="Event End Date", related='event_id.date_end', readonly=True)
263 company_id = fields.Many2one(
264 'res.company', string='Company', related='event_id.company_id',
265 store=True, readonly=True, states={'draft': [('readonly', False)]})
266 state = fields.Selection([
267 ('draft', 'Unconfirmed'), ('cancel', 'Cancelled'),
268 ('open', 'Confirmed'), ('done', 'Attended')],
269 string='Status', default='draft', readonly=True, copy=False, track_visibility='onchange')
270 email = fields.Char(string='Email')
271 phone = fields.Char(string='Phone')
272 name = fields.Char(string='Attendee Name', select=True)
275 @api.constrains('event_id', 'state')
276 def _check_seats_limit(self):
277 if self.event_id.seats_max and self.event_id.seats_available < (1 if self.state == 'draft' else 0):
278 raise Warning(_('No more seats available for this event.'))
281 def _check_auto_confirmation(self):
282 if self._context.get('registration_force_draft'):
284 if self.event_id and self.event_id.state == 'confirm' and self.event_id.auto_confirm and self.event_id.seats_available:
289 def create(self, vals):
290 registration = super(event_registration, self).create(vals)
291 if registration._check_auto_confirmation():
292 registration.sudo().confirm_registration()
300 def confirm_registration(self):
301 self.event_id.message_post(
302 body=_('New registration confirmed: %s.') % (self.name or ''),
303 subtype="event.mt_event_registration")
307 def button_reg_close(self):
308 """ Close Registration """
309 today = fields.Datetime.now()
310 if self.event_id.date_begin <= today:
311 self.write({'state': 'done', 'date_closed': today})
313 raise Warning(_("You must wait for the starting day of the event to do this action."))
316 def button_reg_cancel(self):
317 self.state = 'cancel'
319 @api.onchange('partner_id')
320 def _onchange_partner(self):
322 contact_id = self.partner_id.address_get().get('default', False)
324 contact = self.env['res.partner'].browse(contact_id)
325 self.name = self.name or contact.name
326 self.email = self.email or contact.email
327 self.phone = self.phone or contact.phone