[FIX]website_customer: group by country based on the result of the search, + do not...
[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
22 from datetime import datetime, timedelta
23 from openerp.osv import fields, osv
24 from openerp.tools.translate import _
25 from openerp import SUPERUSER_ID
26
27 class event_type(osv.osv):
28     """ Event Type """
29     _name = 'event.type'
30     _description = __doc__
31     _columns = {
32         'name': fields.char('Event Type', size=64, required=True),
33         'default_reply_to': fields.char('Default Reply-To', size=64,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." ),
34         'default_email_event': fields.many2one('email.template','Event Confirmation Email', help="It will select this default confirmation event mail value when you choose this event"),
35         'default_email_registration': fields.many2one('email.template','Registration Confirmation Email', help="It will select this default confirmation registration mail value when you choose this event"),
36         'default_registration_min': fields.integer('Default Minimum Registration', help="It will select this default minimum value when you choose this event"),
37         'default_registration_max': fields.integer('Default Maximum Registration', help="It will select this default maximum value when you choose this event"),
38     }
39     _defaults = {
40         'default_registration_min': 0,
41         'default_registration_max': 0,
42     }
43
44 class event_event(osv.osv):
45     """Event"""
46     _name = 'event.event'
47     _description = __doc__
48     _order = 'date_begin'
49     _inherit = ['mail.thread', 'ir.needaction_mixin']
50
51     def name_get(self, cr, uid, ids, context=None):
52         if not ids:
53             return []
54
55         if isinstance(ids, (long, int)):
56             ids = [ids]
57
58         res = []
59         for record in self.browse(cr, uid, ids, context=context):
60             date = record.date_begin.split(" ")[0]
61             date_end = record.date_end.split(" ")[0]
62             if date != date_end:
63                 date += ' - ' + date_end
64             display_name = record.name + ' (' + date + ')'
65             res.append((record['id'], display_name))
66         return res
67
68     def copy(self, cr, uid, id, default=None, context=None):
69         """ Reset the state and the registrations while copying an event
70         """
71         if not default:
72             default = {}
73         default.update({
74             'state': 'draft',
75             'registration_ids': False,
76         })
77         return super(event_event, self).copy(cr, uid, id, default=default, context=context)
78
79     def button_draft(self, cr, uid, ids, context=None):
80         return self.write(cr, uid, ids, {'state': 'draft'}, context=context)
81
82     def button_cancel(self, cr, uid, ids, context=None):
83         registration = self.pool.get('event.registration')
84         reg_ids = registration.search(cr, uid, [('event_id','in',ids)], context=context)
85         for event_reg in registration.browse(cr,uid,reg_ids,context=context):
86             if event_reg.state == 'done':
87                 raise osv.except_osv(_('Error!'),_("You have already set a registration for this event as 'Attended'. Please reset it to draft if you want to cancel this event.") )
88         registration.write(cr, uid, reg_ids, {'state': 'cancel'}, context=context)
89         return self.write(cr, uid, ids, {'state': 'cancel'}, context=context)
90
91     def button_done(self, cr, uid, ids, context=None):
92         return self.write(cr, uid, ids, {'state': 'done'}, context=context)
93
94     def confirm_event(self, cr, uid, ids, context=None):
95         register_pool = self.pool.get('event.registration')
96         for event in self.browse(cr, uid, ids, context=context):
97             if event.email_confirmation_id:
98             #send reminder that will confirm the event for all the people that were already confirmed
99                 reg_ids = register_pool.search(cr, uid, [
100                                    ('event_id', '=', event.id),
101                                    ('state', 'not in', ['draft', 'cancel'])], context=context)
102                 register_pool.mail_user_confirm(cr, uid, reg_ids)
103         return self.write(cr, uid, ids, {'state': 'confirm'}, context=context)
104
105     def button_confirm(self, cr, uid, ids, context=None):
106         """ Confirm Event and send confirmation email to all register peoples
107         """
108         return self.confirm_event(cr, uid, isinstance(ids, (int, long)) and [ids] or ids, context=context)
109
110     def _get_seats(self, cr, uid, ids, fields, args, context=None):
111         """Get reserved, available, reserved but unconfirmed and used seats.
112         @return: Dictionary of function field values.
113         """
114         res = dict([(id, {}) for id in ids])
115         for event in self.browse(cr, uid, ids, context=context):
116             res[event.id]['seats_reserved'] = sum(reg.nb_register for reg in event.registration_ids if reg.state == "open")
117             res[event.id]['seats_used'] = sum(reg.nb_register for reg in event.registration_ids if reg.state == "done")
118             res[event.id]['seats_unconfirmed'] = sum(reg.nb_register for reg in event.registration_ids if reg.state == "draft")
119             res[event.id]['seats_available'] = event.seats_max - \
120                 (res[event.id]['seats_reserved'] + res[event.id]['seats_used']) \
121                 if event.seats_max > 0 else None
122         return res
123
124     def _subscribe_fnc(self, cr, uid, ids, fields, args, context=None):
125         """This functional fields compute if the current user (uid) is already subscribed or not to the event passed in parameter (ids)
126         """
127         register_pool = self.pool.get('event.registration')
128         res = {}
129         for event in self.browse(cr, uid, ids, context=context):
130             res[event.id] = False
131             curr_reg_id = register_pool.search(cr, uid, [('user_id', '=', uid), ('event_id', '=' ,event.id)])
132             if curr_reg_id:
133                 for reg in register_pool.browse(cr, uid, curr_reg_id, context=context):
134                     if reg.state in ('open','done'):
135                         res[event.id]= True
136                         continue
137         return res
138
139     _columns = {
140         'name': fields.char('Event Name', size=64, required=True, translate=True, readonly=False, states={'done': [('readonly', True)]}),
141         'user_id': fields.many2one('res.users', 'Responsible User', readonly=False, states={'done': [('readonly', True)]}),
142         'type': fields.many2one('event.type', 'Type of Event', readonly=False, states={'done': [('readonly', True)]}),
143         'seats_max': fields.integer('Maximum Avalaible Seats', oldname='register_max', 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 )", readonly=True, states={'draft': [('readonly', False)]}),
144         'seats_min': fields.integer('Minimum Reserved Seats', oldname='register_min', 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 )", readonly=True, states={'draft': [('readonly', False)]}),
145         'seats_reserved': fields.function(_get_seats, oldname='register_current', string='Reserved Seats', type='integer', multi='seats_reserved'),
146         'seats_available': fields.function(_get_seats, oldname='register_avail', string='Available Seats', type='integer', multi='seats_reserved'),
147         'seats_unconfirmed': fields.function(_get_seats, oldname='register_prospect', string='Unconfirmed Seat Reservations', type='integer', multi='seats_reserved'),
148         'seats_used': fields.function(_get_seats, oldname='register_attended', string='Number of Participations', type='integer', multi='seats_reserved'),
149         'registration_ids': fields.one2many('event.registration', 'event_id', 'Registrations', readonly=False, states={'done': [('readonly', True)]}),
150         'date_begin': fields.datetime('Start Date', required=True, readonly=True, states={'draft': [('readonly', False)]}),
151         'date_end': fields.datetime('End Date', required=True, readonly=True, states={'draft': [('readonly', False)]}),
152         'state': fields.selection([
153             ('draft', 'Unconfirmed'),
154             ('cancel', 'Cancelled'),
155             ('confirm', 'Confirmed'),
156             ('done', 'Done')],
157             'Status', readonly=True, required=True,
158             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\'.'),
159         'email_registration_id' : fields.many2one('email.template','Registration Confirmation Email', help='This field contains the template of the mail that will be automatically sent each time a registration for this event is confirmed.'),
160         'email_confirmation_id' : fields.many2one('email.template','Event Confirmation Email', help="If you set an email template, each participant will receive this email announcing the confirmation of the event."),
161         'reply_to': fields.char('Reply-To Email', size=64, readonly=False, states={'done': [('readonly', True)]}, 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."),
162         'address_id': fields.many2one('res.partner','Location', readonly=False, states={'done': [('readonly', True)]}),
163         'country_id': fields.related('address_id', 'country_id',
164                     type='many2one', relation='res.country', string='Country', readonly=False, states={'done': [('readonly', True)]}, store=True),
165         'description': fields.html(
166             'Description', readonly=False,
167             states={'done': [('readonly', True)]},
168             oldname='note'),
169         'company_id': fields.many2one('res.company', 'Company', required=False, change_default=True, readonly=False, states={'done': [('readonly', True)]}),
170         'is_subscribed' : fields.function(_subscribe_fnc, type="boolean", string='Subscribed'),
171         'organizer_id': fields.many2one('res.partner', "Organizer"),
172     }
173     _defaults = {
174         'state': 'draft',
175         'company_id': lambda self,cr,uid,c: self.pool.get('res.company')._company_default_get(cr, uid, 'event.event', context=c),
176         'user_id': lambda obj, cr, uid, context: uid,
177         'organizer_id': lambda self, cr, uid, c: self.pool.get('res.users').browse(cr, uid, uid, context=c).company_id.partner_id.id,
178         'address_id': lambda self, cr, uid, c: self.pool.get('res.users').browse(cr, uid, uid, context=c).company_id.partner_id.id
179     }
180
181     def _check_seats_limit(self, cr, uid, ids, context=None):
182         print "event _check_seats_limit"
183         for event in self.browse(cr, uid, ids, context=context):
184             if event.seats_max and event.seats_available < 0:
185                 return False
186         return True
187
188     _constraints = [
189         (_check_seats_limit, 'No more available seats.', ['registration_ids','seats_max']),
190     ]
191
192     def subscribe_to_event(self, cr, uid, ids, context=None):
193         register_pool = self.pool.get('event.registration')
194         user_pool = self.pool.get('res.users')
195         num_of_seats = int(context.get('ticket', 1))
196         user = user_pool.browse(cr, uid, uid, context=context)
197         curr_reg_ids = register_pool.search(cr, uid, [('user_id', '=', user.id), ('event_id', '=' , ids[0])])
198         #the subscription is done with SUPERUSER_ID because in case we share the kanban view, we want anyone to be able to subscribe
199         if not curr_reg_ids:
200             curr_reg_ids = [register_pool.create(cr, SUPERUSER_ID, {'event_id': ids[0] ,'email': user.email, 'name':user.name, 'user_id': user.id, 'nb_register': num_of_seats})]
201         else:
202             register_pool.write(cr, uid, curr_reg_ids, {'nb_register': num_of_seats}, context=context)
203         return register_pool.confirm_registration(cr, SUPERUSER_ID, curr_reg_ids, context=context)
204
205     def unsubscribe_to_event(self, cr, uid, ids, context=None):
206         register_pool = self.pool.get('event.registration')
207         #the unsubscription is done with SUPERUSER_ID because in case we share the kanban view, we want anyone to be able to unsubscribe
208         curr_reg_ids = register_pool.search(cr, SUPERUSER_ID, [('user_id', '=', uid), ('event_id', '=', ids[0])])
209         return register_pool.button_reg_cancel(cr, SUPERUSER_ID, curr_reg_ids, context=context)
210
211     def _check_closing_date(self, cr, uid, ids, context=None):
212         for event in self.browse(cr, uid, ids, context=context):
213             if event.date_end < event.date_begin:
214                 return False
215         return True
216
217     _constraints = [
218         (_check_closing_date, 'Error ! Closing Date cannot be set before Beginning Date.', ['date_end']),
219     ]
220
221     def onchange_event_type(self, cr, uid, ids, type_event, context=None):
222         if type_event:
223             type_info =  self.pool.get('event.type').browse(cr,uid,type_event,context)
224             dic ={
225               'reply_to': type_info.default_reply_to,
226               'email_registration_id': type_info.default_email_registration.id,
227               'email_confirmation_id': type_info.default_email_event.id,
228               'seats_min': type_info.default_registration_min,
229               'seats_max': type_info.default_registration_max,
230             }
231             return {'value': dic}
232
233     def onchange_start_date(self, cr, uid, ids, date_begin=False, date_end=False, context=None):
234         res = {'value':{}}
235         if date_end:
236             return res
237         if date_begin and isinstance(date_begin, str):
238             date_begin = datetime.strptime(date_begin, "%Y-%m-%d %H:%M:%S")
239             date_end = date_begin + timedelta(hours=1)
240             res['value'] = {'date_end': date_end.strftime("%Y-%m-%d %H:%M:%S")}
241         return res
242
243
244 class event_registration(osv.osv):
245     """Event Registration"""
246     _name= 'event.registration'
247     _description = __doc__
248     _inherit = ['mail.thread', 'ir.needaction_mixin']
249     _columns = {
250         'id': fields.integer('ID'),
251         'origin': fields.char('Source Document', size=124,readonly=True,help="Reference of the sales order which created the registration"),
252         'nb_register': fields.integer('Number of Participants', required=True, readonly=True, states={'draft': [('readonly', False)]}),
253         'event_id': fields.many2one('event.event', 'Event', required=True, readonly=True, states={'draft': [('readonly', False)]}),
254         'partner_id': fields.many2one('res.partner', 'Partner', states={'done': [('readonly', True)]}),
255         'create_date': fields.datetime('Creation Date' , readonly=True),
256         'date_closed': fields.datetime('Attended Date', readonly=True),
257         'date_open': fields.datetime('Registration Date', readonly=True),
258         'reply_to': fields.related('event_id','reply_to',string='Reply-to Email', type='char', size=128, readonly=True,),
259         'log_ids': fields.one2many('mail.message', 'res_id', 'Logs', domain=[('model','=',_name)]),
260         'event_end_date': fields.related('event_id','date_end', type='datetime', string="Event End Date", readonly=True),
261         'event_begin_date': fields.related('event_id', 'date_begin', type='datetime', string="Event Start Date", readonly=True),
262         'user_id': fields.many2one('res.users', 'User', states={'done': [('readonly', True)]}),
263         'company_id': fields.related('event_id', 'company_id', type='many2one', relation='res.company', string='Company', store=True, readonly=True, states={'draft':[('readonly',False)]}),
264         'state': fields.selection([('draft', 'Unconfirmed'),
265                                     ('cancel', 'Cancelled'),
266                                     ('open', 'Confirmed'),
267                                     ('done', 'Attended')], 'Status',
268                                     size=16, readonly=True),
269         'email': fields.char('Email', size=64),
270         'phone': fields.char('Phone', size=64),
271         'name': fields.char('Name', size=128, select=True),
272     }
273     _defaults = {
274         'nb_register': 1,
275         'state': 'draft',
276     }
277     _order = 'name, create_date desc'
278
279
280     def _check_seats_limit(self, cr, uid, ids, context=None):
281         for registration in self.browse(cr, uid, ids, context=context):
282             if registration.event_id.seats_max and \
283                 registration.event_id.seats_available < (registration.state == 'draft' and registration.nb_register or 0):
284                 return False
285         return True
286
287     _constraints = [
288         (_check_seats_limit, 'No more available seats.', ['event_id','nb_register','state']),
289     ]
290
291     def do_draft(self, cr, uid, ids, context=None):
292         return self.write(cr, uid, ids, {'state': 'draft'}, context=context)
293
294     def confirm_registration(self, cr, uid, ids, context=None):
295         for reg in self.browse(cr, uid, ids, context=context or {}):
296             self.pool.get('event.event').message_post(cr, uid, [reg.event_id.id], body=_('New registration confirmed: %s.') % (reg.name or '', ),subtype="event.mt_event_registration", context=context)
297         return self.write(cr, uid, ids, {'state': 'open'}, context=context)
298
299     def registration_open(self, cr, uid, ids, context=None):
300         """ Open Registration
301         """
302         res = self.confirm_registration(cr, uid, ids, context=context)
303         self.mail_user(cr, uid, ids, context=context)
304         return res
305
306     def button_reg_close(self, cr, uid, ids, context=None):
307         """ Close Registration
308         """
309         if context is None:
310             context = {}
311         today = fields.datetime.now()
312         for registration in self.browse(cr, uid, ids, context=context):
313             if today >= registration.event_id.date_begin:
314                 values = {'state': 'done', 'date_closed': today}
315                 self.write(cr, uid, ids, values)
316             else:
317                 raise osv.except_osv(_('Error!'), _("You must wait for the starting day of the event to do this action."))
318         return True
319
320     def button_reg_cancel(self, cr, uid, ids, context=None, *args):
321         return self.write(cr, uid, ids, {'state': 'cancel'})
322
323     def mail_user(self, cr, uid, ids, context=None):
324         """
325         Send email to user with email_template when registration is done
326         """
327         for registration in self.browse(cr, uid, ids, context=context):
328             if registration.event_id.state == 'confirm' and registration.event_id.email_confirmation_id.id:
329                 self.mail_user_confirm(cr, uid, ids, context=context)
330             else:
331                 template_id = registration.event_id.email_registration_id.id
332                 if template_id:
333                     mail_message = self.pool.get('email.template').send_mail(cr,uid,template_id,registration.id)
334         return True
335
336     def mail_user_confirm(self, cr, uid, ids, context=None):
337         """
338         Send email to user when the event is confirmed
339         """
340         for registration in self.browse(cr, uid, ids, context=context):
341             template_id = registration.event_id.email_confirmation_id.id
342             if template_id:
343                 mail_message = self.pool.get('email.template').send_mail(cr,uid,template_id,registration.id)
344         return True
345
346     def onchange_contact_id(self, cr, uid, ids, contact, partner, context=None):
347         if not contact:
348             return {}
349         addr_obj = self.pool.get('res.partner')
350         contact_id =  addr_obj.browse(cr, uid, contact, context=context)
351         return {'value': {
352             'email':contact_id.email,
353             'name':contact_id.name,
354             'phone':contact_id.phone,
355             }}
356
357     def onchange_partner_id(self, cr, uid, ids, part, context=None):
358         res_obj = self.pool.get('res.partner')
359         data = {}
360         if not part:
361             return {'value': data}
362         addr = res_obj.address_get(cr, uid, [part]).get('default', False)
363         if addr:
364             d = self.onchange_contact_id(cr, uid, ids, addr, part, context)
365             data.update(d['value'])
366         return {'value': data}
367
368 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: