325682f86b31c67f55db4798c8977b2d95b67127
[odoo/odoo.git] / addons / event / event.py
1 # -*- coding: utf-8 -*-
2 ##############################################################################
3 #
4 #    OpenERP, Open Source Management Solution
5 #    Copyright (C) 2004-2010 Tiny SPRL (<http://tiny.be>).
6 #
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.
11 #
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.
16 #
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/>.
19 #
20 ##############################################################################
21 from datetime import timedelta
22
23 import pytz
24
25 from openerp import models, fields, api, _
26 from openerp.exceptions import Warning
27
28 class event_type(models.Model):
29     """ Event Type """
30     _name = 'event.type'
31
32     name = fields.Char(string='Event Type', required=True)
33     default_reply_to = fields.Char(string='Default Reply-To',
34         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.")
35     default_email_event = fields.Many2one('email.template', string='Event Confirmation Email',
36         help="It will select this default confirmation event mail value when you choose this event")
37     default_email_registration = fields.Many2one('email.template', string='Registration Confirmation Email',
38         help="It will select this default confirmation registration mail value when you choose this event")
39     default_registration_min = fields.Integer(string='Default Minimum Registration', default=0,
40         help="It will select this default minimum value when you choose this event")
41     default_registration_max = fields.Integer(string='Default Maximum Registration', default=0,
42         help="It will select this default maximum value when you choose this event")
43
44
45 class event_event(models.Model):
46     """Event"""
47     _name = 'event.event'
48     _inherit = ['mail.thread', 'ir.needaction_mixin']
49     _order = 'date_begin'
50
51     name = fields.Char(string='Event Name', translate=True, required=True,
52         readonly=False, states={'done': [('readonly', True)]})
53     user_id = fields.Many2one('res.users', string='Responsible User',
54         default=lambda self: self.env.user,
55         readonly=False, states={'done': [('readonly', True)]})
56     type = fields.Many2one('event.type', string='Type of Event',
57         readonly=False, states={'done': [('readonly', True)]})
58     seats_max = fields.Integer(string='Maximum Available Seats', oldname='register_max',
59         readonly=True, states={'draft': [('readonly', False)]},
60         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 )")
61     seats_min = fields.Integer(string='Minimum Reserved Seats', oldname='register_min',
62         readonly=True, states={'draft': [('readonly', False)]},
63         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 )")
64
65     seats_reserved = fields.Integer(oldname='register_current', string='Reserved Seats',
66         store=True, readonly=True, compute='_compute_seats')
67     seats_available = fields.Integer(oldname='register_avail', string='Available Seats',
68         store=True, readonly=True, compute='_compute_seats')
69     seats_unconfirmed = fields.Integer(oldname='register_prospect', string='Unconfirmed Seat Reservations',
70         store=True, readonly=True, compute='_compute_seats')
71     seats_used = fields.Integer(oldname='register_attended', string='Number of Participations',
72         store=True, readonly=True, compute='_compute_seats')
73
74     @api.multi
75     @api.depends('seats_max', 'registration_ids.state', 'registration_ids.nb_register')
76     def _compute_seats(self):
77         """ Determine reserved, available, reserved but unconfirmed and used seats. """
78         # initialize fields to 0
79         for event in self:
80             event.seats_unconfirmed = event.seats_reserved = event.seats_used = 0
81         # aggregate registrations by event and by state
82         if self.ids:
83             state_field = {
84                 'draft': 'seats_unconfirmed',
85                 'open':'seats_reserved',
86                 'done': 'seats_used',
87             }
88             query = """ SELECT event_id, state, sum(nb_register)
89                         FROM event_registration
90                         WHERE event_id IN %s AND state IN ('draft', 'open', 'done')
91                         GROUP BY event_id, state
92                     """
93             self._cr.execute(query, (tuple(self.ids),))
94             for event_id, state, num in self._cr.fetchall():
95                 event = self.browse(event_id)
96                 event[state_field[state]] += num
97         # compute seats_available
98         for event in self:
99             event.seats_available = \
100                 event.seats_max - (event.seats_reserved + event.seats_used) \
101                 if event.seats_max > 0 else 0
102
103     registration_ids = fields.One2many('event.registration', 'event_id', string='Registrations',
104         readonly=False, states={'done': [('readonly', True)]})
105     count_registrations = fields.Integer(string='Registrations',
106         compute='_count_registrations')
107
108     date_begin = fields.Datetime(string='Start Date', required=True,
109         readonly=True, states={'draft': [('readonly', False)]})
110     date_end = fields.Datetime(string='End Date', required=True,
111         readonly=True, states={'draft': [('readonly', False)]})
112
113     @api.model
114     def _tz_get(self):
115         return [(x, x) for x in pytz.all_timezones]
116
117     date_tz = fields.Selection('_tz_get', string='Timezone',
118                         default=lambda self: self._context.get('tz', 'UTC'))
119
120     @api.one
121     @api.depends('date_tz', 'date_begin')
122     def _compute_date_begin_tz(self):
123         if self.date_begin:
124             self_in_tz = self.with_context(tz=(self.date_tz or 'UTC'))
125             date_begin = fields.Datetime.from_string(self.date_begin)
126             self.date_begin_located = fields.Datetime.to_string(fields.Datetime.context_timestamp(self_in_tz, date_begin))
127         else:
128             self.date_begin_located = False
129
130     @api.one
131     @api.depends('date_tz', 'date_end')
132     def _compute_date_end_tz(self):
133         if self.date_end:
134             self_in_tz = self.with_context(tz=(self.date_tz or 'UTC'))
135             date_end = fields.Datetime.from_string(self.date_end)
136             self.date_end_located = fields.Datetime.to_string(fields.Datetime.context_timestamp(self_in_tz, date_end))
137         else:
138             self.date_end_located = False
139
140     date_begin_located = fields.Datetime(string='Start Date Located', compute='_compute_date_begin_tz')
141     date_end_located = fields.Datetime(string='End Date Located', compute='_compute_date_end_tz')
142
143     state = fields.Selection([
144             ('draft', 'Unconfirmed'),
145             ('cancel', 'Cancelled'),
146             ('confirm', 'Confirmed'),
147             ('done', 'Done')
148         ], string='Status', default='draft', readonly=True, required=True, copy=False,
149         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'.")
150     email_registration_id = fields.Many2one('email.template', string='Registration Confirmation Email',
151         help='This field contains the template of the mail that will be automatically sent each time a registration for this event is confirmed.')
152     email_confirmation_id = fields.Many2one('email.template', string='Event Confirmation Email',
153         help="If you set an email template, each participant will receive this email announcing the confirmation of the event.")
154     reply_to = fields.Char(string='Reply-To Email',
155         readonly=False, states={'done': [('readonly', True)]},
156         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.")
157     address_id = fields.Many2one('res.partner', string='Location',
158         default=lambda self: self.env.user.company_id.partner_id,
159         readonly=False, states={'done': [('readonly', True)]})
160     country_id = fields.Many2one('res.country', string='Country', related='address_id.country_id',
161         store=True, readonly=False, states={'done': [('readonly', True)]})
162     description = fields.Html(string='Description', oldname='note', translate=True,
163         readonly=False, states={'done': [('readonly', True)]})
164     company_id = fields.Many2one('res.company', string='Company', change_default=True,
165         default=lambda self: self.env['res.company']._company_default_get('event.event'),
166         required=False, readonly=False, states={'done': [('readonly', True)]})
167     organizer_id = fields.Many2one('res.partner', string='Organizer',
168         default=lambda self: self.env.user.company_id.partner_id)
169
170     is_subscribed = fields.Boolean(string='Subscribed',
171         compute='_compute_subscribe')
172
173     @api.one
174     @api.depends('registration_ids')
175     def _count_registrations(self):
176         self.count_registrations = len(self.registration_ids)
177
178     @api.one
179     @api.depends('registration_ids.user_id', 'registration_ids.state')
180     def _compute_subscribe(self):
181         """ Determine whether the current user is already subscribed to any event in `self` """
182         user = self.env.user
183         self.is_subscribed = any(
184             reg.user_id == user and reg.state in ('open', 'done')
185             for reg in self.registration_ids
186         )
187
188     @api.multi
189     @api.depends('name', 'date_begin', 'date_end')
190     def name_get(self):
191         result = []
192         for event in self:
193             dates = [dt.split(' ')[0] for dt in [event.date_begin, event.date_end] if dt]
194             dates = sorted(set(dates))
195             result.append((event.id, '%s (%s)' % (event.name, ' - '.join(dates))))
196         return result
197
198     @api.one
199     @api.constrains('seats_max', 'seats_available')
200     def _check_seats_limit(self):
201         if self.seats_max and self.seats_available < 0:
202             raise Warning(_('No more available seats.'))
203
204     @api.one
205     @api.constrains('date_begin', 'date_end')
206     def _check_closing_date(self):
207         if self.date_end < self.date_begin:
208             raise Warning(_('Closing Date cannot be set before Beginning Date.'))
209
210     @api.one
211     def button_draft(self):
212         self.state = 'draft'
213
214     @api.one
215     def button_cancel(self):
216         for event_reg in self.registration_ids:
217             if event_reg.state == 'done':
218                 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."))
219         self.registration_ids.write({'state': 'cancel'})
220         self.state = 'cancel'                
221
222     @api.one
223     def button_done(self):
224         self.state = 'done'
225
226     @api.one
227     def confirm_event(self):
228         if self.email_confirmation_id:
229             # send reminder that will confirm the event for all the people that were already confirmed
230             regs = self.registration_ids.filtered(lambda reg: reg.state not in ('draft', 'cancel'))
231             regs.mail_user_confirm()
232         self.state = 'confirm'
233
234     @api.one
235     def button_confirm(self):
236         """ Confirm Event and send confirmation email to all register peoples """
237         self.confirm_event()
238
239     @api.one
240     def subscribe_to_event(self):
241         """ Subscribe the current user to a given event """
242         user = self.env.user
243         num_of_seats = int(self._context.get('ticket', 1))
244         regs = self.registration_ids.filtered(lambda reg: reg.user_id == user)
245         # the subscription is done as SUPERUSER_ID because in case we share the
246         # kanban view, we want anyone to be able to subscribe
247         if not regs:
248             regs = regs.sudo().create({
249                 'event_id': self.id,
250                 'email': user.email,
251                 'name':user.name,
252                 'user_id': user.id,
253                 'nb_register': num_of_seats,
254             })
255         else:
256             regs.write({'nb_register': num_of_seats})
257         regs.sudo().confirm_registration()
258
259     @api.one
260     def unsubscribe_to_event(self):
261         """ Unsubscribe the current user from a given event """
262         # the unsubscription is done as SUPERUSER_ID because in case we share
263         # the kanban view, we want anyone to be able to unsubscribe
264         user = self.env.user
265         regs = self.sudo().registration_ids.filtered(lambda reg: reg.user_id == user)
266         regs.button_reg_cancel()
267
268     @api.onchange('type')
269     def _onchange_type(self):
270         if self.type:
271             self.reply_to = self.type.default_reply_to
272             self.email_registration_id = self.type.default_email_registration
273             self.email_confirmation_id = self.type.default_email_event
274             self.seats_min = self.type.default_registration_min
275             self.seats_max = self.type.default_registration_max
276
277     @api.onchange('date_begin')
278     def _onchange_date_begin(self):
279         if self.date_begin and not self.date_end:
280             date_begin = fields.Datetime.from_string(self.date_begin)
281             self.date_end = fields.Datetime.to_string(date_begin + timedelta(hours=1))
282
283
284 class event_registration(models.Model):
285     """Event Registration"""
286     _name= 'event.registration'
287     _inherit = ['mail.thread', 'ir.needaction_mixin']
288     _order = 'name, create_date desc'
289
290     origin = fields.Char(string='Source Document', readonly=True,
291         help="Reference of the sales order which created the registration")
292     nb_register = fields.Integer(string='Number of Participants', required=True, default=1,
293         readonly=True, states={'draft': [('readonly', False)]})
294     event_id = fields.Many2one('event.event', string='Event', required=True,
295         readonly=True, states={'draft': [('readonly', False)]})
296     partner_id = fields.Many2one('res.partner', string='Partner',
297         states={'done': [('readonly', True)]})
298     date_open = fields.Datetime(string='Registration Date', readonly=True)
299     date_closed = fields.Datetime(string='Attended Date', readonly=True)
300     reply_to = fields.Char(string='Reply-to Email', related='event_id.reply_to',
301         readonly=True)
302     log_ids = fields.One2many('mail.message', 'res_id', string='Logs',
303         domain=[('model', '=', _name)])
304     event_begin_date = fields.Datetime(string="Event Start Date", related='event_id.date_begin',
305         readonly=True)
306     event_end_date = fields.Datetime(string="Event End Date", related='event_id.date_end',
307         readonly=True)
308     user_id = fields.Many2one('res.users', string='User', states={'done': [('readonly', True)]})
309     company_id = fields.Many2one('res.company', string='Company', related='event_id.company_id',
310         store=True, readonly=True, states={'draft':[('readonly', False)]})
311     state = fields.Selection([
312             ('draft', 'Unconfirmed'),
313             ('cancel', 'Cancelled'),
314             ('open', 'Confirmed'),
315             ('done', 'Attended'),
316         ], string='Status', default='draft', readonly=True, copy=False)
317     email = fields.Char(string='Email')
318     phone = fields.Char(string='Phone')
319     name = fields.Char(string='Name', select=True)
320
321     @api.one
322     @api.constrains('event_id', 'state', 'nb_register')
323     def _check_seats_limit(self):
324         if self.event_id.seats_max and \
325             self.event_id.seats_available < (self.nb_register if self.state == 'draft' else 0):
326                 raise Warning(_('No more available seats.'))
327
328     @api.one
329     def do_draft(self):
330         self.state = 'draft'
331
332     @api.one
333     def confirm_registration(self):
334         self.event_id.message_post(
335             body=_('New registration confirmed: %s.') % (self.name or ''),
336             subtype="event.mt_event_registration")
337         self.message_post(body=_('Event Registration confirmed.'))
338         self.state = 'open'
339
340     @api.one
341     def registration_open(self):
342         """ Open Registration """
343         self.confirm_registration()
344         self.mail_user()
345
346     @api.one
347     def button_reg_close(self):
348         """ Close Registration """
349         today = fields.Datetime.now()
350         if self.event_id.date_begin <= today:
351             self.write({'state': 'done', 'date_closed': today})
352         else:
353             raise Warning(_("You must wait for the starting day of the event to do this action."))
354
355     @api.one
356     def button_reg_cancel(self):
357         self.state = 'cancel'
358
359     @api.one
360     def mail_user(self):
361         """Send email to user with email_template when registration is done """
362         if self.event_id.state == 'confirm' and self.event_id.email_confirmation_id:
363             self.mail_user_confirm()
364         else:
365             template = self.event_id.email_registration_id
366             if template:
367                 mail_message = template.send_mail(self.id)
368
369     @api.one
370     def mail_user_confirm(self):
371         """Send email to user when the event is confirmed """
372         template = self.event_id.email_confirmation_id
373         if template:
374             mail_message = template.send_mail(self.id)
375
376     @api.onchange('partner_id')
377     def _onchange_partner(self):
378         if self.partner_id:
379             contact = self.partner_id.address_get().get('default', False)
380             if contact:
381                 self.name = contact.name
382                 self.email = contact.email
383                 self.phone = contact.phone
384
385 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: