1 # -*- coding: utf-8 -*-
2 ##############################################################################
4 # OpenERP, Open Source Management Solution
5 # Copyright (C) 2004-2010 Tiny SPRL (<http://tiny.be>).
7 # This program is free software: you can redistribute it and/or modify
8 # it under the terms of the GNU Affero General Public License as
9 # published by the Free Software Foundation, either version 3 of the
10 # License, or (at your option) any later version.
12 # This program is distributed in the hope that it will be useful,
13 # but WITHOUT ANY WARRANTY; without even the implied warranty of
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 # GNU Affero General Public License for more details.
17 # You should have received a copy of the GNU Affero General Public License
18 # along with this program. If not, see <http://www.gnu.org/licenses/>.
20 ##############################################################################
21 from datetime import timedelta
25 from openerp import models, fields, api, _
26 from openerp.exceptions import Warning
28 class event_type(models.Model):
31 _description = 'Event Type'
33 name = fields.Char(string='Event Type', required=True)
34 default_reply_to = fields.Char(string='Default Reply-To',
35 help="The email address of the organizer which is put in the 'Reply-To' of all emails sent automatically at event or registrations confirmation. You can also put your email address of your mail gateway if you use one.")
36 default_email_event = fields.Many2one('email.template', string='Event Confirmation Email',
37 help="It will select this default confirmation event mail value when you choose this event")
38 default_email_registration = fields.Many2one('email.template', string='Registration Confirmation Email',
39 help="It will select this default confirmation registration mail value when you choose this event")
40 default_registration_min = fields.Integer(string='Default Minimum Registration', default=0,
41 help="It will select this default minimum value when you choose this event")
42 default_registration_max = fields.Integer(string='Default Maximum Registration', default=0,
43 help="It will select this default maximum value when you choose this event")
46 class event_event(models.Model):
49 _description = 'Event'
50 _inherit = ['mail.thread', 'ir.needaction_mixin']
53 name = fields.Char(string='Event Name', translate=True, required=True,
54 readonly=False, states={'done': [('readonly', True)]})
55 user_id = fields.Many2one('res.users', string='Responsible User',
56 default=lambda self: self.env.user,
57 readonly=False, states={'done': [('readonly', True)]})
58 type = fields.Many2one('event.type', string='Type of Event',
59 readonly=False, states={'done': [('readonly', True)]})
60 seats_max = fields.Integer(string='Maximum Available Seats', oldname='register_max',
61 readonly=True, states={'draft': [('readonly', False)]},
62 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 )")
63 seats_min = fields.Integer(string='Minimum Reserved Seats', oldname='register_min',
64 readonly=True, states={'draft': [('readonly', False)]},
65 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 )")
67 seats_reserved = fields.Integer(oldname='register_current', string='Reserved Seats',
68 store=True, readonly=True, compute='_compute_seats')
69 seats_available = fields.Integer(oldname='register_avail', string='Available Seats',
70 store=True, readonly=True, compute='_compute_seats')
71 seats_unconfirmed = fields.Integer(oldname='register_prospect', string='Unconfirmed Seat Reservations',
72 store=True, readonly=True, compute='_compute_seats')
73 seats_used = fields.Integer(oldname='register_attended', string='Number of Participations',
74 store=True, readonly=True, compute='_compute_seats')
77 @api.depends('seats_max', 'registration_ids.state', 'registration_ids.nb_register')
78 def _compute_seats(self):
79 """ Determine reserved, available, reserved but unconfirmed and used seats. """
80 # initialize fields to 0
82 event.seats_unconfirmed = event.seats_reserved = event.seats_used = 0
83 # aggregate registrations by event and by state
86 'draft': 'seats_unconfirmed',
87 'open':'seats_reserved',
90 query = """ SELECT event_id, state, sum(nb_register)
91 FROM event_registration
92 WHERE event_id IN %s AND state IN ('draft', 'open', 'done')
93 GROUP BY event_id, state
95 self._cr.execute(query, (tuple(self.ids),))
96 for event_id, state, num in self._cr.fetchall():
97 event = self.browse(event_id)
98 event[state_field[state]] += num
99 # compute seats_available
101 event.seats_available = \
102 event.seats_max - (event.seats_reserved + event.seats_used) \
103 if event.seats_max > 0 else 0
105 registration_ids = fields.One2many('event.registration', 'event_id', string='Registrations',
106 readonly=False, states={'done': [('readonly', True)]})
107 count_registrations = fields.Integer(string='Registrations',
108 compute='_count_registrations')
110 date_begin = fields.Datetime(string='Start Date', required=True,
111 readonly=True, states={'draft': [('readonly', False)]})
112 date_end = fields.Datetime(string='End Date', required=True,
113 readonly=True, states={'draft': [('readonly', False)]})
117 return [(x, x) for x in pytz.all_timezones]
119 date_tz = fields.Selection('_tz_get', string='Timezone',
120 default=lambda self: self._context.get('tz', 'UTC'))
123 @api.depends('date_tz', 'date_begin')
124 def _compute_date_begin_tz(self):
126 self_in_tz = self.with_context(tz=(self.date_tz or 'UTC'))
127 date_begin = fields.Datetime.from_string(self.date_begin)
128 self.date_begin_located = fields.Datetime.to_string(fields.Datetime.context_timestamp(self_in_tz, date_begin))
130 self.date_begin_located = False
133 @api.depends('date_tz', 'date_end')
134 def _compute_date_end_tz(self):
136 self_in_tz = self.with_context(tz=(self.date_tz or 'UTC'))
137 date_end = fields.Datetime.from_string(self.date_end)
138 self.date_end_located = fields.Datetime.to_string(fields.Datetime.context_timestamp(self_in_tz, date_end))
140 self.date_end_located = False
143 @api.depends('address_id')
144 def _compute_country(self):
145 self.country_id = self.address_id.country_id
147 date_begin_located = fields.Datetime(string='Start Date Located', compute='_compute_date_begin_tz')
148 date_end_located = fields.Datetime(string='End Date Located', compute='_compute_date_end_tz')
150 state = fields.Selection([
151 ('draft', 'Unconfirmed'),
152 ('cancel', 'Cancelled'),
153 ('confirm', 'Confirmed'),
155 ], string='Status', default='draft', readonly=True, required=True, copy=False,
156 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'.")
157 email_registration_id = fields.Many2one(
158 'email.template', string='Registration Confirmation Email',
159 domain=[('model', '=', 'event.registration')],
160 help='This field contains the template of the mail that will be automatically sent each time a registration for this event is confirmed.')
161 email_confirmation_id = fields.Many2one(
162 'email.template', string='Event Confirmation Email',
163 domain=[('model', '=', 'event.registration')],
164 help="If you set an email template, each participant will receive this email announcing the confirmation of the event.")
165 reply_to = fields.Char(string='Reply-To Email',
166 readonly=False, states={'done': [('readonly', True)]},
167 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.")
168 address_id = fields.Many2one('res.partner', string='Location',
169 default=lambda self: self.env.user.company_id.partner_id,
170 readonly=False, states={'done': [('readonly', True)]})
171 country_id = fields.Many2one('res.country', string='Country',
172 store=True, compute='_compute_country')
173 description = fields.Html(string='Description', oldname='note', translate=True,
174 readonly=False, states={'done': [('readonly', True)]})
175 company_id = fields.Many2one('res.company', string='Company', change_default=True,
176 default=lambda self: self.env['res.company']._company_default_get('event.event'),
177 required=False, readonly=False, states={'done': [('readonly', True)]})
178 organizer_id = fields.Many2one('res.partner', string='Organizer',
179 default=lambda self: self.env.user.company_id.partner_id)
181 is_subscribed = fields.Boolean(string='Subscribed',
182 compute='_compute_subscribe')
185 @api.depends('registration_ids')
186 def _count_registrations(self):
187 self.count_registrations = len(self.registration_ids)
190 @api.depends('registration_ids.user_id', 'registration_ids.state')
191 def _compute_subscribe(self):
192 """ Determine whether the current user is already subscribed to any event in `self` """
194 self.is_subscribed = any(
195 reg.user_id == user and reg.state in ('open', 'done')
196 for reg in self.registration_ids
200 @api.depends('name', 'date_begin', 'date_end')
204 dates = [dt.split(' ')[0] for dt in [event.date_begin, event.date_end] if dt]
205 dates = sorted(set(dates))
206 result.append((event.id, '%s (%s)' % (event.name, ' - '.join(dates))))
210 @api.constrains('seats_max', 'seats_available')
211 def _check_seats_limit(self):
212 if self.seats_max and self.seats_available < 0:
213 raise Warning(_('No more available seats.'))
216 @api.constrains('date_begin', 'date_end')
217 def _check_closing_date(self):
218 if self.date_end < self.date_begin:
219 raise Warning(_('Closing Date cannot be set before Beginning Date.'))
222 def button_draft(self):
226 def button_cancel(self):
227 for event_reg in self.registration_ids:
228 if event_reg.state == 'done':
229 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."))
230 self.registration_ids.write({'state': 'cancel'})
231 self.state = 'cancel'
234 def button_done(self):
238 def confirm_event(self):
239 if self.email_confirmation_id:
240 # send reminder that will confirm the event for all the people that were already confirmed
241 regs = self.registration_ids.filtered(lambda reg: reg.state not in ('draft', 'cancel'))
242 regs.mail_user_confirm()
243 self.state = 'confirm'
246 def button_confirm(self):
247 """ Confirm Event and send confirmation email to all register peoples """
251 def subscribe_to_event(self):
252 """ Subscribe the current user to a given event """
254 num_of_seats = int(self._context.get('ticket', 1))
255 regs = self.registration_ids.filtered(lambda reg: reg.user_id == user)
256 # the subscription is done as SUPERUSER_ID because in case we share the
257 # kanban view, we want anyone to be able to subscribe
259 regs = regs.sudo().create({
264 'nb_register': num_of_seats,
267 regs.write({'nb_register': num_of_seats})
268 regs.sudo().confirm_registration()
271 def unsubscribe_to_event(self):
272 """ Unsubscribe the current user from a given event """
273 # the unsubscription is done as SUPERUSER_ID because in case we share
274 # the kanban view, we want anyone to be able to unsubscribe
276 regs = self.sudo().registration_ids.filtered(lambda reg: reg.user_id == user)
277 regs.button_reg_cancel()
279 @api.onchange('type')
280 def _onchange_type(self):
282 self.reply_to = self.type.default_reply_to
283 self.email_registration_id = self.type.default_email_registration
284 self.email_confirmation_id = self.type.default_email_event
285 self.seats_min = self.type.default_registration_min
286 self.seats_max = self.type.default_registration_max
288 @api.onchange('date_begin')
289 def _onchange_date_begin(self):
290 if self.date_begin and not self.date_end:
291 date_begin = fields.Datetime.from_string(self.date_begin)
292 self.date_end = fields.Datetime.to_string(date_begin + timedelta(hours=1))
295 class event_registration(models.Model):
296 _name = 'event.registration'
297 _description = 'Event Registration'
298 _inherit = ['mail.thread', 'ir.needaction_mixin']
299 _order = 'name, create_date desc'
301 origin = fields.Char(string='Source Document', readonly=True,
302 help="Reference of the sales order which created the registration")
303 nb_register = fields.Integer(string='Number of Participants', required=True, default=1,
304 readonly=True, states={'draft': [('readonly', False)]})
305 event_id = fields.Many2one('event.event', string='Event', required=True,
306 readonly=True, states={'draft': [('readonly', False)]})
307 partner_id = fields.Many2one('res.partner', string='Partner',
308 states={'done': [('readonly', True)]})
309 date_open = fields.Datetime(string='Registration Date', readonly=True)
310 date_closed = fields.Datetime(string='Attended Date', readonly=True)
311 reply_to = fields.Char(string='Reply-to Email', related='event_id.reply_to',
313 log_ids = fields.One2many('mail.message', 'res_id', string='Logs',
314 domain=[('model', '=', _name)])
315 event_begin_date = fields.Datetime(string="Event Start Date", related='event_id.date_begin',
317 event_end_date = fields.Datetime(string="Event End Date", related='event_id.date_end',
319 user_id = fields.Many2one('res.users', string='User', states={'done': [('readonly', True)]})
320 company_id = fields.Many2one('res.company', string='Company', related='event_id.company_id',
321 store=True, readonly=True, states={'draft':[('readonly', False)]})
322 state = fields.Selection([
323 ('draft', 'Unconfirmed'),
324 ('cancel', 'Cancelled'),
325 ('open', 'Confirmed'),
326 ('done', 'Attended'),
327 ], string='Status', default='draft', readonly=True, copy=False)
328 email = fields.Char(string='Email')
329 phone = fields.Char(string='Phone')
330 name = fields.Char(string='Name', select=True)
333 @api.constrains('event_id', 'state', 'nb_register')
334 def _check_seats_limit(self):
335 if self.event_id.seats_max and \
336 self.event_id.seats_available < (self.nb_register if self.state == 'draft' else 0):
337 raise Warning(_('No more available seats.'))
344 def confirm_registration(self):
345 self.event_id.message_post(
346 body=_('New registration confirmed: %s.') % (self.name or ''),
347 subtype="event.mt_event_registration")
348 self.message_post(body=_('Event Registration confirmed.'))
352 def registration_open(self):
353 """ Open Registration """
354 self.confirm_registration()
358 def button_reg_close(self):
359 """ Close Registration """
360 today = fields.Datetime.now()
361 if self.event_id.date_begin <= today:
362 self.write({'state': 'done', 'date_closed': today})
364 raise Warning(_("You must wait for the starting day of the event to do this action."))
367 def button_reg_cancel(self):
368 self.state = 'cancel'
372 """Send email to user with email_template when registration is done """
373 if self.event_id.state == 'confirm' and self.event_id.email_confirmation_id:
374 self.mail_user_confirm()
376 template = self.event_id.email_registration_id
378 mail_message = template.send_mail(self.id)
381 def mail_user_confirm(self):
382 """Send email to user when the event is confirmed """
383 template = self.event_id.email_confirmation_id
385 mail_message = template.send_mail(self.id)
387 @api.onchange('partner_id')
388 def _onchange_partner(self):
390 contact_id = self.partner_id.address_get().get('default', False)
392 contact = self.env['res.partner'].browse(contact_id)
393 self.name = contact.name
394 self.email = contact.email
395 self.phone = contact.phone
397 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: