[REF+IMP] event, membership:
[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 crm import crm
23 from osv import fields, osv
24 from tools.translate import _
25 import netsvc
26 import pooler
27 import time
28 import tools
29
30
31 class event_type(osv.osv):    
32     """ Event Type """    
33     _name = 'event.type'
34     _description = __doc__
35     _columns = {
36         'name': fields.char('Event type', size=64, required=True), 
37     }
38     
39 event_type()
40
41 class event_event(osv.osv):    
42     """Event"""    
43     _name = 'event.event'
44     _description = __doc__
45     _inherit = 'crm.case.section'
46     _order = 'date_begin'
47
48     def copy(self, cr, uid, id, default=None, context=None):        
49         """ Copy record of Given id       
50         @param id: Id of Event Registration type record.
51         @param context: A standard dictionary for contextual values
52         """
53         if not default:
54             default = {}
55         default.update({
56             'code': self.pool.get('ir.sequence').get(cr, uid, 'event.event'), 
57             'state': 'draft'
58         })    
59         return super(event_event, self).copy(cr, uid, id, default=default, context=context)
60
61     def button_draft(self, cr, uid, ids, context=None):
62         return self.write(cr, uid, ids, {'state': 'draft'}, context=context)
63
64     def button_cancel(self, cr, uid, ids, context=None):
65         return self.write(cr, uid, ids, {'state': 'cancel'}, context=context)
66
67     def button_done(self, cr, uid, ids, context=None):
68         return self.write(cr, uid, ids, {'state': 'done'}, context=context)
69
70     def button_confirm(self, cr, uid, ids, context=None):
71         register_pool = self.pool.get('event.registration')
72         for event in self.browse(cr, uid, ids, context=context):
73             if event.mail_auto_confirm:
74                 #send reminder that will confirm the event for all the people that were already confirmed
75                 reg_ids = register_pool.search(cr, uid, [
76                                ('event_id', '=', event.id), 
77                                ('state', 'not in', ['draft', 'cancel'])])
78                 register_pool.mail_user_confirm(cr, uid, reg_ids)
79                     
80         return self.write(cr, uid, ids, {'state': 'confirm'})
81
82
83     def _get_register(self, cr, uid, ids, fields, args, context=None):        
84         """
85         Get Confirm or uncofirm register value.       
86         @param ids: List of Event registration type's id
87         @param fields: List of function fields(register_current and register_prospect).
88         @param context: A standard dictionary for contextual values
89         @return: Dictionary of function fields value. 
90         """
91         register_pool = self.pool.get('event.registration')
92         res = {}
93         for event in self.browse(cr, uid, ids, context):
94             res[event.id] = {}
95             for field in fields:
96                 res[event.id][field] = False
97             state = []
98             if 'register_current' in fields:
99                 state.append('open')
100             if 'register_prospect' in fields: 
101                 state.append('draft')
102             
103             reg_ids = register_pool.search(cr, uid, [
104                        ('event_id', '=', event.id), 
105                        ('state', 'in', state)])
106             if 'register_current' in fields:
107                 res[event.id]['register_current'] = len(reg_ids)
108             if 'register_prospect' in fields: 
109                 res[event.id]['register_prospect'] = len(reg_ids)
110             
111                
112         return res
113
114     def write(self, cr, uid, ids, vals, context=None):
115         """
116         Writes values in one or several fields.
117         @param ids: List of Event registration type's IDs
118         @param vals: dictionary with values to update.
119         @return: True
120         """
121         register_pool = self.pool.get('event.registration')
122         res = super(event_event, self).write(cr, uid, ids, vals, context=context)
123         if vals.get('date_begin', False) or vals.get('mail_auto_confirm', False) or vals.get('mail_confirm', False):
124             for event in self.browse(cr, uid, ids, context=context):
125                 #change the deadlines of the registration linked to this event
126                 register_values = {}
127                 if vals.get('date_begin', False):
128                     register_values['date_deadline'] = vals['date_begin']
129
130                 #change the description of the registration linked to this event
131                 if vals.get('mail_auto_confirm', False):
132                     if vals['mail_auto_confirm']:
133                         if 'mail_confirm' not in vals:
134                             vals['mail_confirm'] = event.mail_confirm
135                     else:
136                         vals['mail_confirm'] = False
137                 if 'mail_confirm' in vals:
138                     register_values['description'] = vals['mail_confirm']
139
140                 if register_values:
141                     reg_ids = register_pool.search(cr, uid, [('event_id', '=', event.id)])
142                     register_pool.write(cr, uid, reg_ids, register_values)
143         return res
144
145     _columns = {
146         'type': fields.many2one('event.type', 'Type', help="Type of Event like Seminar, Exhibition, Conference, Training."), 
147         'register_max': fields.integer('Maximum Registrations', help="Provide Maximun Number of Registrations"), 
148         'register_min': fields.integer('Minimum Registrations', help="Providee Minimum Number of Registrations"), 
149         'register_current': fields.function(_get_register, method=True, string='Confirmed Registrations', multi='register_current', help="Total of Open Registrations"), 
150         'register_prospect': fields.function(_get_register, method=True, string='Unconfirmed Registrations', multi='register_prospect', help="Total of Prospect Registrations"), 
151         'date_begin': fields.datetime('Beginning date', required=True, help="Beginning Date of Event"), 
152         'date_end': fields.datetime('Closing date', required=True, help="Closing Date of Event"), 
153         'state': fields.selection([('draft', 'Draft'), ('confirm', 'Confirmed'), ('done', 'Done'), ('cancel', 'Cancelled')], 'State', readonly=True, required=True, help='If event is created, the state is \'Draft\'.\n If event is confirmed for the particular dates the state is set to \'Confirmed\'.\
154                                   \nIf the event is over, the state is set to \'Done\'.\n If event is cancelled the state is set to \'Cancelled\'.'), 
155         'mail_auto_registr': fields.boolean('Mail Auto Register', help='Check this box if you want to use the automatic mailing for new registration'), 
156         'mail_auto_confirm': fields.boolean('Mail Auto Confirm', help='Check this box if you want ot use the automatic confirmation emailing or the reminder'), 
157         'mail_registr': fields.text('Registration Email', help='This email will be sent when someone subscribes to the event.'), 
158         'mail_confirm': fields.text('Confirmation Email', help="This email will be sent when the event gets confimed or when someone subscribes to a confirmed event. This is also the email sent to remind someone about the event."), 
159         'product_id': fields.many2one('product.product', 'Product', required=True, help="Product which is provided cost of event. Invoice of event will be created with this Product."),
160         'note': fields.text('Notes', help="Description or Summary of Event")
161     }
162
163     _defaults = {
164         'state': 'draft', 
165         'code': lambda obj, cr, uid, context: obj.pool.get('ir.sequence').get(cr, uid, 'event.event'), 
166         'user_id': lambda obj, cr, uid, context: uid, 
167     }
168
169 event_event()
170
171 class event_registration(osv.osv):    
172     """Event Registration"""   
173
174     _name= 'event.registration'
175     _description = __doc__
176     _inherit = 'crm.meeting'
177
178     _columns = {
179         'email_cc': fields.text('CC', size=252 , help="These \
180 people will receive a copy of the future communication between partner \
181 and users by email"), 
182         'nb_register': fields.integer('Number of Registration', readonly=True, states={'draft': [('readonly', False)]}), 
183         'event_id': fields.many2one('event.event', 'Event Related', required=True), 
184         "partner_invoice_id": fields.many2one('res.partner', 'Partner Invoiced'), 
185         "contact_id": fields.many2one('res.partner.contact', 'Partner Contact'), #TODO: filter only the contacts that have a function into the selected partner_id
186         "unit_price": fields.float('Unit Price'), 
187         "badge_title": fields.char('Badge Title', size=128), 
188         "badge_name": fields.char('Badge Name', size=128), 
189         "badge_partner": fields.char('Badge Partner', size=128), 
190         "event_product": fields.char("Product Name", size=128, required=True), 
191         "tobe_invoiced": fields.boolean("To be Invoiced"), 
192         "invoice_id": fields.many2one("account.invoice", "Invoice"), 
193         'date_closed': fields.datetime('Closed', readonly=True), 
194         'ref': fields.reference('Reference', selection=crm._links_get, size=128), 
195         'ref2': fields.reference('Reference 2', selection=crm._links_get, size=128), 
196     }
197     
198     _defaults = {
199         'nb_register': 1, 
200         'tobe_invoiced':  True, 
201         'name': 'Registration', 
202     }
203
204     def _make_invoice(self, cr, uid, reg, lines, context=None):
205         """ Create Invoice from Invoice lines
206         @param reg : Object of event.registration
207         @param lines: ids of Invoice lines 
208         """
209         if context is None:
210             context = {}
211         inv_pool = self.pool.get('account.invoice')
212         inv_lines_pool = self.pool.get('account.invoice.line')
213         
214         val_invoice = inv_pool.onchange_partner_id(cr, uid, [], 'out_invoice', reg.partner_invoice_id.id, False, False)            
215         val_invoice['value'].update({'partner_id': reg.partner_invoice_id.id})
216         partner_address_id = val_invoice['value']['address_invoice_id']
217
218         value = inv_lines_pool.product_id_change(cr, uid, [], reg.event_id.product_id.id, uom =False, partner_id=reg.partner_invoice_id.id, fposition_id=reg.partner_invoice_id.property_account_position.id)
219         
220         l = inv_lines_pool.read(cr, uid, lines)
221         
222         val_invoice['value'].update({
223                 'origin': reg.event_product, 
224                 'reference': False, 
225                 'invoice_line': [(6, 0, lines)], 
226                 'comment': "", 
227             })
228         inv_id = inv_pool.create(cr, uid, val_invoice['value'])   
229         inv_pool.button_compute(cr, uid, [inv_id])
230         self._history(cr, uid, [reg], _('Invoiced'))
231         return inv_id
232
233     def action_invoice_create(self, cr, uid, ids, grouped=False, date_inv = False, context=None):
234         """ Action of Create Invoice """
235         res = False
236         invoices = {}
237         tax_ids=[]
238         
239         inv_lines_pool = self.pool.get('account.invoice.line')
240         inv_pool = self.pool.get('account.invoice')
241         product_pool = self.pool.get('product.product')
242         contact_pool = self.pool.get('res.partner.contact')
243         if not context:
244             context = {}
245         # If date was specified, use it as date invoiced, usefull when invoices are generated this month and put the
246         # last day of the last month as invoice date
247         if date_inv:
248             context['date_inv'] = date_inv
249
250         for reg in self.browse(cr, uid, ids, context=context):
251             
252             val_invoice = inv_pool.onchange_partner_id(cr, uid, [], 'out_invoice', reg.partner_invoice_id.id, False, False)
253             
254             val_invoice['value'].update({'partner_id': reg.partner_invoice_id.id})
255             partner_address_id = val_invoice['value']['address_invoice_id']
256                 
257             if not partner_address_id:
258                raise osv.except_osv(_('Error !'),
259                         _("Registered partner doesn't have an address to make the invoice."))
260                                 
261             value = inv_lines_pool.product_id_change(cr, uid, [], reg.event_id.product_id.id, uom =False, partner_id=reg.partner_invoice_id.id, fposition_id=reg.partner_invoice_id.property_account_position.id)
262             product = product_pool.browse(cr, uid, reg.event_id.product_id.id, context=context)
263             for tax in product.taxes_id:
264                 tax_ids.append(tax.id)
265
266             vals = value['value']
267             c_name = reg.contact_id and ('-' + contact_pool.name_get(cr, uid, [reg.contact_id.id])[0][1]) or ''
268             vals.update({
269                 'name': reg.event_product + '-' + c_name, 
270                 'price_unit': reg.unit_price, 
271                 'quantity': reg.nb_register, 
272                 'product_id':reg.event_id.product_id.id, 
273                 'invoice_line_tax_id': [(6, 0, tax_ids)], 
274             })
275             inv_line_ids = self._create_invoice_lines(cr, uid, [reg.id], vals)
276             invoices.setdefault(reg.partner_id.id, []).append((reg, inv_line_ids))
277            
278         for val in invoices.values():
279             if grouped:
280                 res = self._make_invoice(cr, uid, val[0][0], [v for k , v in val], context=context)
281                 
282                 for k , v in val:
283                     self.write(cr, uid, [k.id], {'state': 'done', 'invoice_id': res}, context=context)
284                     
285             else:
286                for k , v in val:
287                    res = self._make_invoice(cr, uid, k, [v], context=context)
288                    self.write(cr, uid, [k.id], {'state': 'done', 'invoice_id': res}, context=context)
289         return res
290
291     def check_confirm(self, cr, uid, ids, context=None):
292         """
293         Check confirm event register on given id.
294         @param ids: List of Event registration's IDs
295         @param context: A standard dictionary for contextual values
296         @return: Dictionary value which open Confirm registration form.
297         """
298         data_pool = self.pool.get('ir.model.data')
299         unconfirmed_ids = []
300         for registration in self.browse(cr, uid, ids, context=context):
301             total_confirmed = registration.event_id.register_current + registration.nb_register
302             if total_confirmed <= registration.event_id.register_max or registration.event_id.register_max == 0:
303                 self.write(cr, uid, [registration.id], {'state': 'open'}, context=context)
304                 self.mail_user(cr, uid, [registration.id])           
305                 self._history(cr, uid, [registration.id], _('Open')) 
306             else:
307                 unconfirmed_ids.append(registration.id)
308         if unconfirmed_ids:
309             view_id = data_pool._get_id(cr, uid, 'event', 'view_event_confirm_registration')
310             view_data = data_pool.browse(cr, uid, view_id)
311             view_id = view_data.res_id
312             context['registration_ids'] = unconfirmed_ids
313             return {
314                 'name': _('Confirm Registration'), 
315                 'context': context, 
316                 'view_type': 'form', 
317                 'view_mode': 'tree,form', 
318                 'res_model': 'event.confirm.registration', 
319                 'views': [(view_id, 'form')],                     
320                 'type': 'ir.actions.act_window', 
321                 'target': 'new', 
322                 'context': context,
323                 'nodestroy': True
324             }
325         return True    
326
327     def button_reg_close(self, cr, uid, ids, *args):        
328         registrations = self.browse(cr, uid, ids) 
329         self._history(cr, uid, registrations, _('Done'))
330         self.write(cr, uid, ids, {'state': 'done', 'date_closed': time.strftime('%Y-%m-%d %H:%M:%S')})
331         return True
332     
333     def button_reg_cancel(self, cr, uid, ids, *args):        
334         registrations = self.browse(cr, uid, ids)
335         self._history(cr, uid, registrations, _('Cancel'))
336         self.write(cr, uid, ids, {'state': 'cancel'})
337         return True
338
339     def create(self, cr, uid, values, context=None):
340         """ Overrides orm create method.
341         """
342         event = self.pool.get('event.event').browse(cr, uid, values['event_id'], context=context)
343         
344         values['date_deadline']= event.date_begin
345         values['description']= event.mail_confirm
346         res = super(event_registration, self).create(cr, uid, values, context=context)
347         registrations = self.browse(cr, uid, [res], context=context)
348         self._history(cr, uid, registrations, _('Created'))
349         return res
350
351     def write(self, cr, uid, ids, values, context=None):    
352         if 'event_id' in values:
353             event = self.pool.get('event.event').browse(cr, uid, values['event_id'], context=context)
354             values['date_deadline']= event.date_begin
355             values['description']= event.mail_confirm
356         return super(event_registration, self).write(cr, uid, ids, values, context=context)
357     
358
359     def mail_user(self, cr, uid, ids, confirm=False, context=None):
360         """
361         Send email to user 
362         """
363         if not context:
364             context = {}
365         
366         for reg_id in self.browse(cr, uid, ids):
367             src = reg_id.event_id.reply_to or False
368             dest = []
369             if reg_id.email_from:
370                 dest += [reg_id.email_from]
371             if reg_id.email_cc:
372                 dest += [reg_id.email_cc]
373             if dest and src:
374                 if confirm:
375                    tools.email_send(src, dest, 
376                         _('Auto Confirmation: [%s] %s') %(reg_id.id, reg_id.name),
377                         reg_id.event_id.mail_confirm, 
378                         openobject_id = reg_id.id)
379                 elif reg_id.event_id.mail_auto_confirm or reg_id.event_id.mail_auto_registr:
380                     if reg_id.event_id.state in ['draft', 'fixed', 'open', 'confirm', 'running'] and reg_id.event_id.mail_auto_registr:
381                         tools.email_send(src, dest, 
382                             _('Auto Registration: [%s] %s') %(reg_id.id, reg_id.name),
383                              reg_id.event_id.mail_registr, openobject_id = reg_id.id)
384                     if (reg_id.event_id.state in ['confirm', 'running']) and reg_id.event_id.mail_auto_confirm:
385                         tools.email_send(src, dest, 
386                             _('Auto Confirmation: [%s] %s') %(reg_id.id, reg_id.name), 
387                             reg_id.event_id.mail_confirm, openobject_id = reg_id.id)
388                     
389             if not src:
390                 raise osv.except_osv(_('Error!'), _('You must define a reply-to address in order to mail the participant. You can do this in the Mailing tab of your event. Note that this is also the place where you can configure your event to not send emails automaticly while registering'))
391
392         return True
393
394     def mail_user_confirm(self, cr, uid, ids, context=None):
395         """
396         Send email to user 
397         """
398         return self.mail_user(cr, uid, ids, confirm=True, context=context)
399
400     def _create_invoice_lines(self, cr, uid, ids, vals):
401         """ Create account Invoice line for Registration Id.
402         """
403         return self.pool.get('account.invoice.line').create(cr, uid, vals)
404
405     def onchange_badge_name(self, cr, uid, ids, badge_name):
406         
407         data ={}
408         if not badge_name:
409             return data
410         data['name'] = 'Registration: ' + badge_name
411         return {'value': data}
412
413     def onchange_contact_id(self, cr, uid, ids, contact, partner):
414         
415         data ={}
416         if not contact:
417             return data
418
419         contact_id = self.pool.get('res.partner.contact').browse(cr, uid, contact)
420         data['badge_name'] = contact_id.name
421         data['badge_title'] = contact_id.title
422         if partner:
423             partner_addresses = self.pool.get('res.partner.address').search(cr, uid, [('partner_id', '=', partner)])
424             job_ids = self.pool.get('res.partner.job').search(cr, uid, [('contact_id', '=', contact), ('address_id', 'in', partner_addresses)])
425             if job_ids:
426                 data['email_from'] = self.pool.get('res.partner.job').browse(cr, uid, job_ids[0]).email
427         d = self.onchange_badge_name(cr, uid, ids, data['badge_name'])
428         data.update(d['value'])
429         return {'value': data}
430
431     def onchange_event(self, cr, uid, ids, event_id, partner_invoice_id):
432         context={}
433         if not event_id:
434             return {'value': {'unit_price': False, 'event_product': False}}
435         data_event =  self.pool.get('event.event').browse(cr, uid, event_id)
436         
437         if data_event.product_id:
438             if not partner_invoice_id:
439                 unit_price=self.pool.get('product.product').price_get(cr, uid, [data_event.product_id.id], context=context)[data_event.product_id.id]
440                 return {'value': {'unit_price': unit_price, 'event_product': data_event.product_id.name}}
441             data_partner = self.pool.get('res.partner').browse(cr, uid, partner_invoice_id)
442             context.update({'partner_id': data_partner})
443             unit_price = self.pool.get('product.product')._product_price(cr, uid, [data_event.product_id.id], False, False, {'pricelist': data_partner.property_product_pricelist.id})[data_event.product_id.id]
444             return {'value': {'unit_price': unit_price, 'event_product': data_event.product_id.name}}
445         
446         return {'value': {'unit_price': False, 'event_product': False}}
447
448     def onchange_partner_id(self, cr, uid, ids, part, event_id, email=False):
449         
450         data={}
451         data['badge_partner'] = data['contact_id'] = data['partner_invoice_id'] = data['email_from'] = data['badge_title'] = data['badge_name'] = False
452         if not part:
453             return {'value': data}
454         data['partner_invoice_id']=part
455         # this calls onchange_partner_invoice_id
456         d = self.onchange_partner_invoice_id(cr, uid, ids, event_id, part)
457         # this updates the dictionary
458         data.update(d['value'])
459         addr = self.pool.get('res.partner').address_get(cr, uid, [part])
460         if addr:
461             if addr.has_key('default'):
462                 job_ids = self.pool.get('res.partner.job').search(cr, uid, [('address_id', '=', addr['default'])])
463                 if job_ids:
464                     data['contact_id'] = self.pool.get('res.partner.job').browse(cr, uid, job_ids[0]).contact_id.id
465                     d = self.onchange_contact_id(cr, uid, ids, data['contact_id'], part)
466                     data.update(d['value'])
467         partner_data = self.pool.get('res.partner').browse(cr, uid, part)
468         data['badge_partner'] = partner_data.name
469         return {'value': data}
470
471     def onchange_partner_invoice_id(self, cr, uid, ids, event_id, partner_invoice_id):
472         
473         data={}
474         context={}
475         data['unit_price']=False
476         if not event_id:
477             return {'value': data}
478         data_event =  self.pool.get('event.event').browse(cr, uid, event_id)
479
480         if data_event.product_id:
481             if not partner_invoice_id:
482                 data['unit_price']=self.pool.get('product.product').price_get(cr, uid, [data_event.product_id.id], context=context)[data_event.product_id.id]
483                 return {'value': data}
484             data_partner = self.pool.get('res.partner').browse(cr, uid, partner_invoice_id)
485             context.update({'partner_id': data_partner})
486             data['unit_price'] = self.pool.get('product.product')._product_price(cr, uid, [data_event.product_id.id], False, False, {'pricelist': data_partner.property_product_pricelist.id})[data_event.product_id.id]
487             return {'value': data}
488         return {'value': data}
489
490 event_registration()
491
492
493 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
494