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 ##############################################################################
25 from openerp import models, fields, api, _
26 from openerp.exceptions import Warning
29 class event_type(models.Model):
32 _description = 'Event Type'
34 name = fields.Char(string='Event Type', required=True)
35 default_reply_to = fields.Char(string='Default Reply-To',
36 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.")
37 default_email_event = fields.Many2one('email.template', string='Event Confirmation Email',
38 help="It will select this default confirmation event mail value when you choose this event")
39 default_email_registration = fields.Many2one('email.template', string='Registration Confirmation Email',
40 help="It will select this default confirmation registration mail value when you choose this event")
41 default_registration_min = fields.Integer(string='Default Minimum Registration', default=0,
42 help="It will select this default minimum value when you choose this event")
43 default_registration_max = fields.Integer(string='Default Maximum Registration', default=0,
44 help="It will select this default maximum value when you choose this event")
47 class event_event(models.Model):
50 _description = 'Event'
51 _inherit = ['mail.thread', 'ir.needaction_mixin']
54 name = fields.Char(string='Event Name', translate=True, required=True,
55 readonly=False, states={'done': [('readonly', True)]})
56 user_id = fields.Many2one('res.users', string='Responsible User',
57 default=lambda self: self.env.user,
58 readonly=False, states={'done': [('readonly', True)]})
59 type = fields.Many2one('event.type', string='Type of Event',
60 readonly=False, states={'done': [('readonly', True)]})
61 color = fields.Integer('Kanban Color Index')
62 seats_max = fields.Integer(string='Maximum Available Seats', oldname='register_max',
63 readonly=True, states={'draft': [('readonly', False)]},
64 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 )")
65 seats_min = fields.Integer(string='Minimum Reserved Seats', oldname='register_min',
66 readonly=True, states={'draft': [('readonly', False)]},
67 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 )")
69 seats_reserved = fields.Integer(oldname='register_current', string='Reserved Seats',
70 store=True, readonly=True, compute='_compute_seats')
71 seats_available = fields.Integer(oldname='register_avail', string='Available Seats',
72 store=True, readonly=True, compute='_compute_seats')
73 seats_unconfirmed = fields.Integer(oldname='register_prospect', string='Unconfirmed Seat Reservations',
74 store=True, readonly=True, compute='_compute_seats')
75 seats_used = fields.Integer(oldname='register_attended', string='Number of Participations',
76 store=True, readonly=True, compute='_compute_seats')
79 @api.depends('seats_max', 'registration_ids.state', 'registration_ids.nb_register')
80 def _compute_seats(self):
81 """ Determine reserved, available, reserved but unconfirmed and used seats. """
82 # initialize fields to 0
84 event.seats_unconfirmed = event.seats_reserved = event.seats_used = 0
85 # aggregate registrations by event and by state
88 'draft': 'seats_unconfirmed',
89 'open':'seats_reserved',
92 query = """ SELECT event_id, state, sum(nb_register)
93 FROM event_registration
94 WHERE event_id IN %s AND state IN ('draft', 'open', 'done')
95 GROUP BY event_id, state
97 self._cr.execute(query, (tuple(self.ids),))
98 for event_id, state, num in self._cr.fetchall():
99 event = self.browse(event_id)
100 event[state_field[state]] += num
101 # compute seats_available
103 event.seats_available = \
104 event.seats_max - (event.seats_reserved + event.seats_used) \
105 if event.seats_max > 0 else 0
107 registration_ids = fields.One2many('event.registration', 'event_id', string='Registrations',
108 readonly=False, states={'done': [('readonly', True)]})
109 count_registrations = fields.Integer(string='Registrations',
110 compute='_count_registrations')
112 date_begin = fields.Datetime(string='Start Date', required=True,
113 readonly=True, states={'draft': [('readonly', False)]})
114 date_end = fields.Datetime(string='End Date', required=True,
115 readonly=True, states={'draft': [('readonly', False)]})
119 return [(x, x) for x in pytz.all_timezones]
121 date_tz = fields.Selection('_tz_get', string='Timezone',
122 default=lambda self: self._context.get('tz', 'UTC'))
125 @api.depends('date_tz', 'date_begin')
126 def _compute_date_begin_tz(self):
128 self_in_tz = self.with_context(tz=(self.date_tz or 'UTC'))
129 date_begin = fields.Datetime.from_string(self.date_begin)
130 self.date_begin_located = fields.Datetime.to_string(fields.Datetime.context_timestamp(self_in_tz, date_begin))
132 self.date_begin_located = False
135 @api.depends('date_tz', 'date_end')
136 def _compute_date_end_tz(self):
138 self_in_tz = self.with_context(tz=(self.date_tz or 'UTC'))
139 date_end = fields.Datetime.from_string(self.date_end)
140 self.date_end_located = fields.Datetime.to_string(fields.Datetime.context_timestamp(self_in_tz, date_end))
142 self.date_end_located = False
144 date_begin_located = fields.Datetime(string='Start Date Located', compute='_compute_date_begin_tz')
145 date_end_located = fields.Datetime(string='End Date Located', compute='_compute_date_end_tz')
147 state = fields.Selection([
148 ('draft', 'Unconfirmed'),
149 ('cancel', 'Cancelled'),
150 ('confirm', 'Confirmed'),
152 ], string='Status', default='draft', readonly=True, required=True, copy=False,
153 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'.")
154 email_registration_id = fields.Many2one(
155 'email.template', string='Registration Confirmation Email',
156 domain=[('model', '=', 'event.registration')],
157 help='This field contains the template of the mail that will be automatically sent each time a registration for this event is confirmed.')
158 email_confirmation_id = fields.Many2one(
159 'email.template', string='Event Confirmation Email',
160 domain=[('model', '=', 'event.registration')],
161 help="If you set an email template, each participant will receive this email announcing the confirmation of the event.")
162 reply_to = fields.Char(string='Reply-To Email',
163 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('res.partner', string='Location',
166 default=lambda self: self.env.user.company_id.partner_id,
167 readonly=False, states={'done': [('readonly', True)]})
168 country_id = fields.Many2one('res.country', string='Country', related='address_id.country_id',
169 store=True, readonly=False, states={'done': [('readonly', True)]})
170 description = fields.Html(string='Description', oldname='note', translate=True,
171 readonly=False, states={'done': [('readonly', True)]})
172 company_id = fields.Many2one('res.company', string='Company', change_default=True,
173 default=lambda self: self.env['res.company']._company_default_get('event.event'),
174 required=False, readonly=False, states={'done': [('readonly', True)]})
175 organizer_id = fields.Many2one('res.partner', string='Organizer',
176 default=lambda self: self.env.user.company_id.partner_id)
178 is_subscribed = fields.Boolean(string='Subscribed',
179 compute='_compute_subscribe')
182 @api.depends('registration_ids')
183 def _count_registrations(self):
184 self.count_registrations = len(self.registration_ids)
187 @api.depends('registration_ids.user_id', 'registration_ids.state')
188 def _compute_subscribe(self):
189 """ Determine whether the current user is already subscribed to any event in `self` """
191 self.is_subscribed = any(
192 reg.user_id == user and reg.state in ('open', 'done')
193 for reg in self.registration_ids
197 @api.depends('name', 'date_begin', 'date_end')
201 dates = [dt.split(' ')[0] for dt in [event.date_begin, event.date_end] if dt]
202 dates = sorted(set(dates))
203 result.append((event.id, '%s (%s)' % (event.name, ' - '.join(dates))))
207 @api.constrains('seats_max', 'seats_available')
208 def _check_seats_limit(self):
209 if self.seats_max and self.seats_available < 0:
210 raise Warning(_('No more available seats.'))
213 @api.constrains('date_begin', 'date_end')
214 def _check_closing_date(self):
215 if self.date_end < self.date_begin:
216 raise Warning(_('Closing Date cannot be set before Beginning Date.'))
219 def button_draft(self):
223 def button_cancel(self):
224 for event_reg in self.registration_ids:
225 if event_reg.state == 'done':
226 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."))
227 self.registration_ids.write({'state': 'cancel'})
228 self.state = 'cancel'
231 def button_done(self):
235 def confirm_event(self):
236 if self.email_confirmation_id:
237 # send reminder that will confirm the event for all the people that were already confirmed
238 regs = self.registration_ids.filtered(lambda reg: reg.state not in ('draft', 'cancel'))
239 regs.mail_user_confirm()
240 self.state = 'confirm'
243 def button_confirm(self):
244 """ Confirm Event and send confirmation email to all register peoples """
248 def subscribe_to_event(self):
249 """ Subscribe the current user to a given event """
251 num_of_seats = int(self._context.get('ticket', 1))
252 regs = self.registration_ids.filtered(lambda reg: reg.user_id == user)
253 # the subscription is done as SUPERUSER_ID because in case we share the
254 # kanban view, we want anyone to be able to subscribe
256 regs = regs.sudo().create({
261 'nb_register': num_of_seats,
264 regs.write({'nb_register': num_of_seats})
265 regs.sudo().confirm_registration()
268 def unsubscribe_to_event(self):
269 """ Unsubscribe the current user from a given event """
270 # the unsubscription is done as SUPERUSER_ID because in case we share
271 # the kanban view, we want anyone to be able to unsubscribe
273 regs = self.sudo().registration_ids.filtered(lambda reg: reg.user_id == user)
274 regs.button_reg_cancel()
276 @api.onchange('type')
277 def _onchange_type(self):
279 self.reply_to = self.type.default_reply_to
280 self.email_registration_id = self.type.default_email_registration
281 self.email_confirmation_id = self.type.default_email_event
282 self.seats_min = self.type.default_registration_min
283 self.seats_max = self.type.default_registration_max
286 def action_event_registration_report(self):
287 res = self.env['ir.actions.act_window'].for_xml_id('event', 'action_report_event_registration')
289 "search_default_event_id": self.id,
290 "group_by": ['event_date:day'],
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: