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