[MERGE] new v8 api by rco
[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 Avalaible 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.one
189     @api.depends('name', 'date_begin', 'date_end')
190     def _compute_display_name(self):
191         dates = [dt.split(' ')[0] for dt in [self.date_begin, self.date_end] if dt]
192         dates = sorted(set(dates))
193         self.display_name = '%s (%s)' % (self.name, ' - '.join(dates))
194
195     @api.one
196     @api.constrains('seats_max', 'seats_available')
197     def _check_seats_limit(self):
198         if self.seats_max and self.seats_available < 0:
199             raise Warning(_('No more available seats.'))
200
201     @api.one
202     @api.constrains('date_begin', 'date_end')
203     def _check_closing_date(self):
204         if self.date_end < self.date_begin:
205             raise Warning(_('Closing Date cannot be set before Beginning Date.'))
206
207     @api.one
208     def button_draft(self):
209         self.state = 'draft'
210
211     @api.one
212     def button_cancel(self):
213         for event_reg in self.registration_ids:
214             if event_reg.state == 'done':
215                 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."))
216         self.registration_ids.write({'state': 'cancel'})
217         self.state = 'cancel'                
218
219     @api.one
220     def button_done(self):
221         self.state = 'done'
222
223     @api.one
224     def confirm_event(self):
225         if self.email_confirmation_id:
226             # send reminder that will confirm the event for all the people that were already confirmed
227             regs = self.registration_ids.filtered(lambda reg: reg.state not in ('draft', 'cancel'))
228             regs.mail_user_confirm()
229         self.state = 'confirm'
230
231     @api.one
232     def button_confirm(self):
233         """ Confirm Event and send confirmation email to all register peoples """
234         self.confirm_event()
235
236     @api.one
237     def subscribe_to_event(self):
238         """ Subscribe the current user to a given event """
239         user = self.env.user
240         num_of_seats = int(self._context.get('ticket', 1))
241         regs = self.registration_ids.filtered(lambda reg: reg.user_id == user)
242         # the subscription is done as SUPERUSER_ID because in case we share the
243         # kanban view, we want anyone to be able to subscribe
244         if not regs:
245             regs = regs.sudo().create({
246                 'event_id': self.id,
247                 'email': user.email,
248                 'name':user.name,
249                 'user_id': user.id,
250                 'nb_register': num_of_seats,
251             })
252         else:
253             regs.write({'nb_register': num_of_seats})
254         regs.sudo().confirm_registration()
255
256     @api.one
257     def unsubscribe_to_event(self):
258         """ Unsubscribe the current user from a given event """
259         # the unsubscription is done as SUPERUSER_ID because in case we share
260         # the kanban view, we want anyone to be able to unsubscribe
261         user = self.env.user
262         regs = self.sudo().registration_ids.filtered(lambda reg: reg.user_id == user)
263         regs.button_reg_cancel()
264
265     @api.onchange('type')
266     def _onchange_type(self):
267         if self.type:
268             self.reply_to = self.type.default_reply_to
269             self.email_registration_id = self.type.default_email_registration
270             self.email_confirmation_id = self.type.default_email_event
271             self.seats_min = self.type.default_registration_min
272             self.seats_max = self.type.default_registration_max
273
274     @api.onchange('date_begin')
275     def _onchange_date_begin(self):
276         if self.date_begin and not self.date_end:
277             date_begin = fields.Datetime.from_string(self.date_begin)
278             self.date_end = fields.Datetime.to_string(date_begin + timedelta(hours=1))
279
280
281 class event_registration(models.Model):
282     """Event Registration"""
283     _name= 'event.registration'
284     _inherit = ['mail.thread', 'ir.needaction_mixin']
285     _order = 'name, create_date desc'
286
287     origin = fields.Char(string='Source Document', readonly=True,
288         help="Reference of the sales order which created the registration")
289     nb_register = fields.Integer(string='Number of Participants', required=True, default=1,
290         readonly=True, states={'draft': [('readonly', False)]})
291     event_id = fields.Many2one('event.event', string='Event', required=True,
292         readonly=True, states={'draft': [('readonly', False)]})
293     partner_id = fields.Many2one('res.partner', string='Partner',
294         states={'done': [('readonly', True)]})
295     date_open = fields.Datetime(string='Registration Date', readonly=True)
296     date_closed = fields.Datetime(string='Attended Date', readonly=True)
297     reply_to = fields.Char(string='Reply-to Email', related='event_id.reply_to',
298         readonly=True)
299     log_ids = fields.One2many('mail.message', 'res_id', string='Logs',
300         domain=[('model', '=', _name)])
301     event_begin_date = fields.Datetime(string="Event Start Date", related='event_id.date_begin',
302         readonly=True)
303     event_end_date = fields.Datetime(string="Event End Date", related='event_id.date_end',
304         readonly=True)
305     user_id = fields.Many2one('res.users', string='User', states={'done': [('readonly', True)]})
306     company_id = fields.Many2one('res.company', string='Company', related='event_id.company_id',
307         store=True, readonly=True, states={'draft':[('readonly', False)]})
308     state = fields.Selection([
309             ('draft', 'Unconfirmed'),
310             ('cancel', 'Cancelled'),
311             ('open', 'Confirmed'),
312             ('done', 'Attended'),
313         ], string='Status', default='draft', readonly=True, copy=False)
314     email = fields.Char(string='Email')
315     phone = fields.Char(string='Phone')
316     name = fields.Char(string='Name', select=True)
317
318     @api.one
319     @api.constrains('event_id', 'state', 'nb_register')
320     def _check_seats_limit(self):
321         if self.event_id.seats_max and \
322             self.event_id.seats_available < (self.nb_register if self.state == 'draft' else 0):
323                 raise Warning(_('No more available seats.'))
324
325     @api.one
326     def do_draft(self):
327         self.state = 'draft'
328
329     @api.one
330     def confirm_registration(self):
331         self.event_id.message_post(
332             body=_('New registration confirmed: %s.') % (self.name or ''),
333             subtype="event.mt_event_registration")
334         self.message_post(body=_('Event Registration confirmed.'))
335         self.state = 'open'
336
337     @api.one
338     def registration_open(self):
339         """ Open Registration """
340         self.confirm_registration()
341         self.mail_user()
342
343     @api.one
344     def button_reg_close(self):
345         """ Close Registration """
346         today = fields.Datetime.now()
347         if self.event_id.date_begin <= today:
348             self.write({'state': 'done', 'date_closed': today})
349         else:
350             raise Warning(_("You must wait for the starting day of the event to do this action."))
351
352     @api.one
353     def button_reg_cancel(self):
354         self.state = 'cancel'
355
356     @api.one
357     def mail_user(self):
358         """Send email to user with email_template when registration is done """
359         if self.event_id.state == 'confirm' and self.event_id.email_confirmation_id:
360             self.mail_user_confirm()
361         else:
362             template = self.event_id.email_registration_id
363             if template:
364                 mail_message = template.send_mail(self.id)
365
366     @api.one
367     def mail_user_confirm(self):
368         """Send email to user when the event is confirmed """
369         template = self.event_id.email_confirmation_id
370         if template:
371             mail_message = template.send_mail(self.id)
372
373     @api.onchange('partner_id')
374     def _onchange_partner(self):
375         if self.partner_id:
376             contact = self.partner_id.address_get().get('default', False)
377             if contact:
378                 self.name = contact.name
379                 self.email = contact.email
380                 self.phone = contact.phone
381
382 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: