[IMP] improves the reporting view Expenses Analysis (add some measures to the table...
[odoo/odoo.git] / addons / survey / survey.py
1 # -*- encoding: utf-8 -*-
2 ##############################################################################
3 #
4 #    OpenERP, Open Source Management Solution
5 #    Copyright (C) 2004-TODAY OpenERP S.A. <http://www.openerp.com>
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 import copy
23 from datetime import datetime
24 from dateutil.relativedelta import relativedelta
25 from time import strftime
26 import os
27
28 from openerp import tools
29 from openerp.osv import fields, osv
30 from openerp.tools.translate import _
31
32 class survey_type(osv.osv):
33     _name = 'survey.type'
34     _description = 'Survey Type'
35     _columns = {
36         'name': fields.char("Name", size=128, required=1, translate=True),
37         'code': fields.char("Code", size=64),
38     }
39
40 class survey(osv.osv):
41     _name = 'survey'
42     _description = 'Survey'
43     _rec_name = 'title'
44
45     def default_get(self, cr, uid, fields, context=None):
46         data = super(survey, self).default_get(cr, uid, fields, context)
47         return data
48
49     _columns = {
50         'id': fields.integer('ID'),
51         'title': fields.char('Survey Title', size=128, required=1),
52         'page_ids': fields.one2many('survey.page', 'survey_id', 'Page'),
53         'date_open': fields.datetime('Survey Open Date', readonly=1),
54         'date_close': fields.datetime('Survey Close Date', readonly=1),
55         'max_response_limit': fields.integer('Maximum Answer Limit',
56                      help="Set to one if survey is answerable only once"),
57         'response_user': fields.integer('Maximum Answer per User',
58                      help="Set to one if  you require only one Answer per user"),
59         'state': fields.selection([('open', 'Open'), ('cancel', 'Cancelled'),('close', 'Closed') ], 'Status', readonly=True),
60         'responsible_id': fields.many2one('res.users', 'Responsible', help="User responsible for survey"),
61         'tot_start_survey': fields.integer("Total Started Survey", readonly=1),
62         'tot_comp_survey': fields.integer("Total Completed Survey", readonly=1),
63         'note': fields.text('Description', size=128),
64         'history': fields.one2many('survey.history', 'survey_id', 'History Lines', readonly=True),
65         'users': fields.many2many('res.users', 'survey_users_rel', 'sid', 'uid', 'Users'),
66         'send_response': fields.boolean('Email Notification on Answer'),
67         'type': fields.many2one('survey.type', 'Type'),
68         'color': fields.integer('Color Index'),
69         'invited_user_ids': fields.many2many('res.users', 'survey_invited_user_rel', 'sid', 'uid', 'Invited User'),
70     }
71     _defaults = {
72         'state': lambda * a: "open",
73         'tot_start_survey': lambda * a: 0,
74         'tot_comp_survey': lambda * a: 0,
75         'send_response': lambda * a: 1,
76         'response_user': lambda * a:1,
77         'date_open': fields.datetime.now,
78     }
79
80     def survey_open(self, cr, uid, ids, arg):
81         self.write(cr, uid, ids, {'state': 'open', 'date_open': strftime("%Y-%m-%d %H:%M:%S")})
82         return True
83
84     def survey_close(self, cr, uid, ids, arg):
85         self.write(cr, uid, ids, {'state': 'close', 'date_close': strftime("%Y-%m-%d %H:%M:%S") })
86         return True
87
88     def survey_cancel(self, cr, uid, ids, arg):
89         self.write(cr, uid, ids, {'state': 'cancel' })
90         return True
91
92     def copy(self, cr, uid, ids, default=None, context=None):
93         vals = {}
94         current_rec = self.read(cr, uid, ids, context=context)
95         title = _("%s (copy)") % (current_rec.get('title'))
96         vals.update({'title':title})
97         vals.update({'history':[],'tot_start_survey':0,'tot_comp_survey':0})
98         return super(survey, self).copy(cr, uid, ids, vals, context=context)
99
100     def action_print_survey(self, cr, uid, ids, context=None):
101         """
102         If response is available then print this response otherwise print survey form(print template of the survey).
103         @param self: The object pointer
104         @param cr: the current row, from the database cursor,
105         @param uid: the current user’s ID for security checks,
106         @param ids: List of Survey IDs
107         @param context: A standard dictionary for contextual values
108         @return : Dictionary value for print survey form.
109         """
110         if context is None:
111             context = {}
112         datas = {}
113         if 'response_id' in context:
114             response_id = context.get('response_id', 0)
115             datas['ids'] = [context.get('survey_id', 0)]
116         else:
117             response_id = self.pool.get('survey.response').search(cr, uid, [('survey_id','=', ids)], context=context)
118             datas['ids'] = ids
119         page_setting = {'orientation': 'vertical', 'without_pagebreak': 0, 'paper_size': 'letter', 'page_number': 1, 'survey_title': 1}
120         report = {}
121         if response_id and response_id[0]:
122             context.update({'survey_id': datas['ids']})
123             datas['form'] = page_setting
124             datas['model'] = 'survey.print.answer'
125             report = {
126                 'type': 'ir.actions.report.xml',
127                 'report_name': 'survey.browse.response',
128                 'datas': datas,
129                 'context' : context,
130                 'nodestroy':True,
131             }
132         else:
133
134             datas['form'] = page_setting
135             datas['model'] = 'survey.print'
136             report = {
137                 'type': 'ir.actions.report.xml',
138                 'report_name': 'survey.form',
139                 'datas': datas,
140                 'context' : context,
141                 'nodestroy':True,
142             }
143         return report
144
145     def fill_survey(self, cr, uid, ids, context=None):
146         sur_obj = self.read(cr, uid, ids,['title', 'page_ids'], context=context)
147         for sur in sur_obj:
148             name = sur['title']
149             pages = sur['page_ids']
150             if not pages:
151                 raise osv.except_osv(_('Warning!'), _('This survey has no question defined. Please define the questions and answers first.'))
152             context.update({'active':False,'survey_id': ids[0]})
153         return {
154             'view_type': 'form',
155             'view_mode': 'form',
156             'res_model': 'survey.question.wiz',
157             'type': 'ir.actions.act_window',
158             'target': 'new',
159             'name': name,
160             'context': context
161         }
162     def test_survey(self, cr, uid, ids, context=None):
163         sur_obj = self.read(cr, uid, ids,['title','page_ids'], context=context)
164         for sur in sur_obj:
165             name = sur['title']
166             pages = sur['page_ids']
167             if not pages:
168                 raise osv.except_osv(_('Warning!'), _('This survey has no pages defined. Please define pages first.'))
169             context.update({'active':False,'survey_id': ids[0]})
170         return {
171             'view_type': 'form',
172             'view_mode': 'form',
173             'res_model': 'survey.question.wiz',
174             'type': 'ir.actions.act_window',
175             'target': 'new',
176             'name': name,
177             'context': context
178         }
179
180     def edit_survey(self, cr, uid, ids, context=None):
181         sur_obj = self.read(cr, uid, ids,['title','page_ids'], context=context)
182         for sur in sur_obj:
183             name = sur['title']
184             pages = sur['page_ids']
185             if not pages:
186                 raise osv.except_osv(_('Warning!'), _('This survey has no question defined. Please define the questions and answers first.'))
187             context.update({'survey_id': ids[0]})
188         return {
189             'view_type': 'form',
190             'view_mode': 'form',
191             'res_model': 'survey.question.wiz',
192             'type': 'ir.actions.act_window',
193             'target': 'new',
194             'name': name,
195             'context': context
196         }
197
198
199 class survey_history(osv.osv):
200     _name = 'survey.history'
201     _description = 'Survey History'
202     _rec_name = 'date'
203     _columns = {
204         'survey_id': fields.many2one('survey', 'Survey'),
205         'user_id': fields.many2one('res.users', 'User', readonly=True),
206         'date': fields.datetime('Date started', readonly=1),
207     }
208     _defaults = {
209          'date': lambda * a: datetime.datetime.now()
210     }
211
212 class survey_page(osv.osv):
213     _name = 'survey.page'
214     _description = 'Survey Pages'
215     _rec_name = 'title'
216     _order = 'sequence'
217     _columns = {
218         'title': fields.char('Page Title', size=128, required=1),
219         'survey_id': fields.many2one('survey', 'Survey', ondelete='cascade'),
220         'question_ids': fields.one2many('survey.question', 'page_id', 'Questions'),
221         'sequence': fields.integer('Page Nr'),
222         'note': fields.text('Description'),
223     }
224     _defaults = {
225         'sequence': lambda * a: 1
226     }
227
228     def default_get(self, cr, uid, fields, context=None):
229         if context is None:
230             context = {}
231         data = super(survey_page, self).default_get(cr, uid, fields, context)
232         if context.has_key('survey_id'):
233             data['survey_id'] = context.get('survey_id', False)
234         return data
235
236     def survey_save(self, cr, uid, ids, context=None):
237         if context is None:
238             context = {}
239         search_obj = self.pool.get('ir.ui.view')
240         search_id = search_obj.search(cr,uid,[('model','=','survey.question.wiz'),('name','=','Survey Search')])
241         surv_name_wiz = self.pool.get('survey.name.wiz')
242         surv_name_wiz.write(cr, uid, [context.get('sur_name_id',False)], {'transfer':True, 'page_no' : context.get('page_number',0) })
243         return {
244             'view_type': 'form',
245             'view_mode': 'form',
246             'res_model': 'survey.question.wiz',
247             'type': 'ir.actions.act_window',
248             'target': 'new',
249             'search_view_id': search_id[0],
250             'context': context
251         }
252
253     def copy(self, cr, uid, ids, default=None, context=None):
254         vals = {}
255         current_rec = self.read(cr, uid, ids, context=context)
256         title = _("%s (copy)") % (current_rec.get('title'))
257         vals.update({'title':title})
258         return super(survey_page, self).copy(cr, uid, ids, vals, context=context)
259
260
261 class survey_question(osv.osv):
262     _name = 'survey.question'
263     _description = 'Survey Question'
264     _rec_name = 'question'
265     _order = 'sequence'
266
267     def _calc_response(self, cr, uid, ids, field_name, arg, context=None):
268         if len(ids) == 0:
269             return {}
270         val = {}
271         cr.execute("select question_id, count(id) as Total_response from \
272                 survey_response_line where state='done' and question_id IN %s\
273                  group by question_id" ,(tuple(ids),))
274         ids1 = copy.deepcopy(ids)
275         for rec in  cr.fetchall():
276             ids1.remove(rec[0])
277             val[rec[0]] = int(rec[1])
278         for id in ids1:
279             val[id] = 0
280         return val
281
282     _columns = {
283         'page_id': fields.many2one('survey.page', 'Survey Page', ondelete='cascade', required=1),
284         'question':  fields.char('Question', size=128, required=1),
285         'answer_choice_ids': fields.one2many('survey.answer', 'question_id', 'Answer'),
286         'is_require_answer': fields.boolean('Require Answer to Question'),
287         'required_type': fields.selection([('all','All'), ('at least','At Least'), ('at most','At Most'), ('exactly','Exactly'), ('a range','A Range')], 'Respondent must answer'),
288         'req_ans': fields.integer('#Required Answer'),
289         'maximum_req_ans': fields.integer('Maximum Required Answer'),
290         'minimum_req_ans': fields.integer('Minimum Required Answer'),
291         'req_error_msg': fields.text('Error Message'),
292         'allow_comment': fields.boolean('Allow Comment Field'),
293         'sequence': fields.integer('Sequence'),
294         'tot_resp': fields.function(_calc_response, string="Total Answer"),
295         'survey': fields.related('page_id', 'survey_id', type='many2one', relation='survey', string='Survey'),
296         'descriptive_text': fields.text('Descriptive Text', size=255),
297         'column_heading_ids': fields.one2many('survey.question.column.heading', 'question_id',' Column heading'),
298         'type': fields.selection([('multiple_choice_only_one_ans','Multiple Choice (Only One Answer)'),
299              ('multiple_choice_multiple_ans','Multiple Choice (Multiple Answer)'),
300              ('matrix_of_choices_only_one_ans','Matrix of Choices (Only One Answers Per Row)'),
301              ('matrix_of_choices_only_multi_ans','Matrix of Choices (Multiple Answers Per Row)'),
302              ('matrix_of_drop_down_menus','Matrix of Drop-down Menus'),
303              ('rating_scale','Rating Scale'),('single_textbox','Single Textbox'),
304              ('multiple_textboxes','Multiple Textboxes'),
305              ('multiple_textboxes_diff_type','Multiple Textboxes With Different Type'),
306              ('comment','Comment/Essay Box'),
307              ('numerical_textboxes','Numerical Textboxes'),('date','Date'),
308              ('date_and_time','Date and Time'),('descriptive_text','Descriptive Text'),
309              ('table','Table'),
310             ], 'Question Type',  required=1,),
311         'is_comment_require': fields.boolean('Add Comment Field'),
312         'comment_label': fields.char('Field Label', size = 255),
313         'comment_field_type': fields.selection([('char', 'Single Line Of Text'), ('text', 'Paragraph of Text')], 'Comment Field Type'),
314         'comment_valid_type': fields.selection([('do_not_validate', '''Don't Validate Comment Text.'''),
315              ('must_be_specific_length', 'Must Be Specific Length'),
316              ('must_be_whole_number', 'Must Be A Whole Number'),
317              ('must_be_decimal_number', 'Must Be A Decimal Number'),
318              ('must_be_date', 'Must Be A Date'),
319              ('must_be_email_address', 'Must Be An Email Address'),
320              ], 'Text Validation'),
321         'comment_minimum_no': fields.integer('Minimum number'),
322         'comment_maximum_no': fields.integer('Maximum number'),
323         'comment_minimum_float': fields.float('Minimum decimal number'),
324         'comment_maximum_float': fields.float('Maximum decimal number'),
325         'comment_minimum_date': fields.date('Minimum date'),
326         'comment_maximum_date': fields.date('Maximum date'),
327         'comment_valid_err_msg': fields.text('Error message'),
328         'make_comment_field': fields.boolean('Make Comment Field an Answer Choice'),
329         'make_comment_field_err_msg': fields.text('Error message'),
330         'is_validation_require': fields.boolean('Validate Text'),
331         'validation_type': fields.selection([('do_not_validate', '''Don't Validate Comment Text.'''),\
332              ('must_be_specific_length', 'Must Be Specific Length'),\
333              ('must_be_whole_number', 'Must Be A Whole Number'),\
334              ('must_be_decimal_number', 'Must Be A Decimal Number'),\
335              ('must_be_date', 'Must Be A Date'),\
336              ('must_be_email_address', 'Must Be An Email Address')\
337              ], 'Text Validation'),
338         'validation_minimum_no': fields.integer('Minimum number'),
339         'validation_maximum_no': fields.integer('Maximum number'),
340         'validation_minimum_float': fields.float('Minimum decimal number'),
341         'validation_maximum_float': fields.float('Maximum decimal number'),
342         'validation_minimum_date': fields.date('Minimum date'),
343         'validation_maximum_date': fields.date('Maximum date'),
344         'validation_valid_err_msg': fields.text('Error message'),
345         'numeric_required_sum': fields.integer('Sum of all choices'),
346         'numeric_required_sum_err_msg': fields.text('Error message'),
347         'rating_allow_one_column_require': fields.boolean('Allow Only One Answer per Column (Forced Ranking)'),
348         'in_visible_rating_weight': fields.boolean('Is Rating Scale Invisible?'),
349         'in_visible_menu_choice': fields.boolean('Is Menu Choice Invisible?'),
350         'in_visible_answer_type': fields.boolean('Is Answer Type Invisible?'),
351         'comment_column': fields.boolean('Add comment column in matrix'),
352         'column_name': fields.char('Column Name',size=256),
353         'no_of_rows': fields.integer('No of Rows'),
354     }
355     _defaults = {
356          'sequence': lambda * a: 1,
357          'type': lambda * a: 'multiple_choice_multiple_ans',
358          'req_error_msg': lambda * a: 'This question requires an answer.',
359          'required_type': lambda * a: 'at least',
360          'req_ans': lambda * a: 1,
361          'comment_field_type': lambda * a: 'char',
362          'comment_label': lambda * a: 'Other (please specify)',
363          'comment_valid_type': lambda * a: 'do_not_validate',
364          'comment_valid_err_msg': lambda * a : 'The comment you entered is in an invalid format.',
365          'validation_type': lambda * a: 'do_not_validate',
366          'validation_valid_err_msg': lambda * a : 'The comment you entered is in an invalid format.',
367          'numeric_required_sum_err_msg': lambda * a :'The choices need to add up to [enter sum here].',
368          'make_comment_field_err_msg': lambda * a : 'Please enter a comment.',
369          'in_visible_answer_type': lambda * a: 1
370     }
371
372     def on_change_type(self, cr, uid, ids, type, context=None):
373         val = {}
374         val['is_require_answer'] = False
375         val['is_comment_require'] = False
376         val['is_validation_require'] = False
377         val['comment_column'] = False
378
379         if type in ['multiple_textboxes_diff_type']:
380             val['in_visible_answer_type'] = False
381             return {'value': val}
382
383         if type in ['rating_scale']:
384             val.update({'in_visible_rating_weight':False, 'in_visible_menu_choice':True})
385             return {'value': val}
386
387         elif type in ['matrix_of_drop_down_menus']:
388             val.update({'in_visible_rating_weight':True, 'in_visible_menu_choice':False})
389             return {'value': val}
390
391         elif type in ['single_textbox']:
392             val.update({'in_visible_rating_weight':True, 'in_visible_menu_choice':True})
393             return {'value': val}
394
395         else:
396             val.update({'in_visible_rating_weight':True, 'in_visible_menu_choice':True,\
397                          'in_visible_answer_type':True})
398             return {'value': val}
399
400     def write(self, cr, uid, ids, vals, context=None):
401         questions = self.read(cr,uid, ids, ['answer_choice_ids', 'type', 'required_type',\
402                         'req_ans', 'minimum_req_ans', 'maximum_req_ans', 'column_heading_ids', 'page_id', 'question'])
403         for question in questions:
404             col_len = len(question['column_heading_ids'])
405             if vals.has_key('column_heading_ids'):
406                 for col in vals['column_heading_ids']:
407                     if type(col[2]) == type({}):
408                         col_len += 1
409                     else:
410                         col_len -= 1
411
412             if vals.has_key('type'):
413                 que_type = vals['type']
414             else:
415                 que_type = question['type']
416
417             if que_type in ['matrix_of_choices_only_one_ans', 'matrix_of_choices_only_multi_ans',\
418                              'matrix_of_drop_down_menus', 'rating_scale']:
419                 if not col_len:
420                     raise osv.except_osv(_('Warning!'),_('You must enter one or more column headings for question "%s" of page %s.') % (question['question'], question['page_id'][1]))
421             ans_len = len(question['answer_choice_ids'])
422
423             if vals.has_key('answer_choice_ids'):
424                 for ans in vals['answer_choice_ids']:
425                     if type(ans[2]) == type({}):
426                         ans_len += 1
427                     else:
428                         ans_len -= 1
429
430             if que_type not in ['descriptive_text', 'single_textbox', 'comment','table']:
431                 if not ans_len:
432                     raise osv.except_osv(_('Warning!'),_('You must enter one or more Answers for question "%s" of page %s.') % (question['question'], question['page_id'][1]))
433             req_type = ""
434
435             if vals.has_key('required_type'):
436                 req_type = vals['required_type']
437             else:
438                 req_type = question['required_type']
439
440             if que_type in ['multiple_choice_multiple_ans','matrix_of_choices_only_one_ans', \
441                         'matrix_of_choices_only_multi_ans', 'matrix_of_drop_down_menus',\
442                          'rating_scale','multiple_textboxes','numerical_textboxes','date','date_and_time']:
443                 if req_type in ['at least', 'at most', 'exactly']:
444                     if vals.has_key('req_ans'):
445                         if not vals['req_ans'] or  vals['req_ans'] > ans_len:
446                             raise osv.except_osv(_('Warning!'),_("#Required Answer you entered \
447                                     is greater than the number of answer. \
448                                     Please use a number that is smaller than %d.") % (ans_len + 1))
449                     else:
450                         if not question['req_ans'] or  question['req_ans'] > ans_len:
451                             raise osv.except_osv(_('Warning!'),_("#Required Answer you entered is \
452                                     greater than the number of answer.\
453                                     Please use a number that is smaller than %d.") % (ans_len + 1))
454
455                 if req_type == 'a range':
456                     minimum_ans = 0
457                     maximum_ans = 0
458                     if vals.has_key('minimum_req_ans'):
459                         minimum_ans = vals['minimum_req_ans']
460                         if not vals['minimum_req_ans'] or  vals['minimum_req_ans'] > ans_len:
461                             raise osv.except_osv(_('Warning!'),_("Minimum Required Answer\
462                                      you entered is greater than the number of answer.\
463                                     Please use a number that is smaller than %d.") % (ans_len + 1))
464                     else:
465                         minimum_ans = question['minimum_req_ans']
466                         if not question['minimum_req_ans'] or  question['minimum_req_ans'] > ans_len:
467                             raise osv.except_osv(_('Warning!'),_("Minimum Required Answer you\
468                                      entered is greater than the number of answer. \
469                                      Please use a number that is smaller than %d.") % (ans_len + 1))
470                     if vals.has_key('maximum_req_ans'):
471                         maximum_ans = vals['maximum_req_ans']
472                         if not vals['maximum_req_ans'] or vals['maximum_req_ans'] > ans_len:
473                             raise osv.except_osv(_('Warning!'),_("Maximum Required Answer you \
474                                     entered for your maximum is greater than the number of answer.\
475                                      Please use a number that is smaller than %d.") % (ans_len + 1))
476                     else:
477                         maximum_ans = question['maximum_req_ans']
478                         if not question['maximum_req_ans'] or question['maximum_req_ans'] > ans_len:
479                             raise osv.except_osv(_('Warning!'),_("Maximum Required Answer you\
480                                      entered for your maximum is greater than the number of answer.\
481                                       Please use a number that is smaller than %d.") % (ans_len + 1))
482                     if maximum_ans <= minimum_ans:
483                         raise osv.except_osv(_('Warning!'),_("Maximum Required Answer is greater \
484                                     than Minimum Required Answer"))
485
486             if question['type'] ==  'matrix_of_drop_down_menus' and vals.has_key('column_heading_ids'):
487                 for col in vals['column_heading_ids']:
488                     if not col[2] or not col[2].has_key('menu_choice') or not col[2]['menu_choice']:
489                         raise osv.except_osv(_('Warning!'),_("You must enter one or more menu choices\
490                                  in column heading."))
491                     elif not col[2] or not col[2].has_key('menu_choice') or\
492                              col[2]['menu_choice'].strip() == '':
493                         raise osv.except_osv(_('Warning!'),_("You must enter one or more menu \
494                                 choices in column heading (white spaces not allowed)."))
495
496         return super(survey_question, self).write(cr, uid, ids, vals, context=context)
497
498     def create(self, cr, uid, vals, context=None):
499         minimum_ans = 0
500         maximum_ans = 0
501         page = self.pool.get('survey.page').browse(cr, uid, int(vals.get('page_id', 0)), context=context).title
502         if vals.has_key('answer_choice_ids') and  not len(vals['answer_choice_ids']):
503             if vals.has_key('type') and vals['type'] not in ['descriptive_text', 'single_textbox', 'comment','table']:
504                 raise osv.except_osv(_('Warning!'),_('You must enter one or more answers for question "%s" of page %s .') % (vals['question'], page))
505
506         if vals.has_key('column_heading_ids') and  not len(vals['column_heading_ids']):
507             if vals.has_key('type') and vals['type'] in ['matrix_of_choices_only_one_ans', 'matrix_of_choices_only_multi_ans', 'matrix_of_drop_down_menus', 'rating_scale']:
508                 raise osv.except_osv(_('Warning!'),_('You must enter one or more column headings for question "%s" of page %s.')% (vals['question'], page))
509
510         if vals['type'] in ['multiple_choice_multiple_ans','matrix_of_choices_only_one_ans', 'matrix_of_choices_only_multi_ans', 'matrix_of_drop_down_menus', 'rating_scale','multiple_textboxes','numerical_textboxes','date','date_and_time']:
511             if vals.has_key('is_require_answer') and vals.has_key('required_type') and vals['required_type'] in ['at least', 'at most', 'exactly']:
512                 if vals.has_key('answer_choice_ids') and vals['req_ans'] > len(vals['answer_choice_ids']) or not vals['req_ans']:
513                     raise osv.except_osv(_('Warning!'),_("#Required Answer you entered is greater than the number of answer. Please use a number that is smaller than %d.") % (len(vals['answer_choice_ids'])+1))
514
515             if vals.has_key('is_require_answer') and vals.has_key('required_type') and vals['required_type'] == 'a range':
516                 minimum_ans = vals['minimum_req_ans']
517                 maximum_ans = vals['maximum_req_ans']
518                 if vals.has_key('answer_choice_ids') or vals['minimum_req_ans'] > len(vals['answer_choice_ids']) or not vals['minimum_req_ans']:
519                     raise osv.except_osv(_('Warning!'),_("Minimum Required Answer you entered is greater than the number of answer. Please use a number that is smaller than %d.") % (len(vals['answer_choice_ids'])+1))
520                 if vals.has_key('answer_choice_ids') or vals['maximum_req_ans'] > len(vals['answer_choice_ids']) or not vals['maximum_req_ans']:
521                     raise osv.except_osv(_('Warning!'),_("Maximum Required Answer you entered for your maximum is greater than the number of answer. Please use a number that is smaller than %d.") % (len(vals['answer_choice_ids'])+1))
522                 if maximum_ans <= minimum_ans:
523                     raise osv.except_osv(_('Warning!'),_("Maximum Required Answer is greater than Minimum Required Answer."))
524
525         if vals['type'] ==  'matrix_of_drop_down_menus':
526             for col in vals['column_heading_ids']:
527                 if not col[2] or not col[2].has_key('menu_choice') or not col[2]['menu_choice']:
528                     raise osv.except_osv(_('Warning!'),_("You must enter one or more menu choices in column heading."))
529                 elif not col[2] or not col[2].has_key('menu_choice') or col[2]['menu_choice'].strip() == '':
530                     raise osv.except_osv(_('Warning!'),_("You must enter one or more menu choices in column heading (white spaces not allowed)."))
531
532         res = super(survey_question, self).create(cr, uid, vals, context)
533         return res
534
535     def survey_save(self, cr, uid, ids, context=None):
536         if context is None:
537             context = {}
538         search_obj = self.pool.get('ir.ui.view')
539         search_id = search_obj.search(cr,uid,[('model','=','survey.question.wiz'),('name','=','Survey Search')])
540         surv_name_wiz = self.pool.get('survey.name.wiz')
541         surv_name_wiz.write(cr, uid, [context.get('sur_name_id',False)], {'transfer':True, 'page_no' : context.get('page_number',False) })
542         return {
543             'view_type': 'form',
544             'view_mode': 'form',
545             'res_model': 'survey.question.wiz',
546             'type': 'ir.actions.act_window',
547             'target': 'new',
548             'search_view_id': search_id[0],
549             'context': context
550         }
551
552     def default_get(self, cr, uid, fields, context=None):
553         if context is None:
554             context = {}
555         data = super(survey_question, self).default_get(cr, uid, fields, context)
556         if context.has_key('page_id'):
557             data['page_id']= context.get('page_id', False)
558         return data
559
560
561
562 class survey_question_column_heading(osv.osv):
563     _name = 'survey.question.column.heading'
564     _description = 'Survey Question Column Heading'
565     _rec_name = 'title'
566
567     def _get_in_visible_rating_weight(self, cr, uid, context=None):
568         if context is None:
569             context = {}
570         if context.get('in_visible_rating_weight', False):
571             return context['in_visible_rating_weight']
572         return False
573     def _get_in_visible_menu_choice(self,cr, uid, context=None):
574         if context is None:
575             context = {}
576         if context.get('in_visible_menu_choice', False):
577             return context['in_visible_menu_choice']
578         return False
579
580     _columns = {
581         'title': fields.char('Column Heading', size=128, required=1),
582         'menu_choice': fields.text('Menu Choice'),
583         'rating_weight': fields.integer('Weight'),
584         'question_id': fields.many2one('survey.question', 'Question', ondelete='cascade'),
585         'in_visible_rating_weight': fields.boolean('Is Rating Scale Invisible ??'),
586         'in_visible_menu_choice': fields.boolean('Is Menu Choice Invisible??')
587     }
588     _defaults={
589        'in_visible_rating_weight': _get_in_visible_rating_weight,
590        'in_visible_menu_choice': _get_in_visible_menu_choice,
591     }
592
593 class survey_answer(osv.osv):
594     _name = 'survey.answer'
595     _description = 'Survey Answer'
596     _rec_name = 'answer'
597     _order = 'sequence'
598
599     def _calc_response_avg(self, cr, uid, ids, field_name, arg, context=None):
600         val = {}
601         for rec in self.browse(cr, uid, ids, context=context):
602             cr.execute("select count(question_id) ,(select count(answer_id) \
603                 from survey_response_answer sra, survey_response_line sa \
604                 where sra.response_id = sa.id and sra.answer_id = %d \
605                 and sa.state='done') as tot_ans from survey_response_line \
606                 where question_id = %d and state = 'done'"\
607                      % (rec.id, rec.question_id.id))
608             res = cr.fetchone()
609             if res[0]:
610                 avg = float(res[1]) * 100 / res[0]
611             else:
612                 avg = 0.0
613             val[rec.id] = {
614                 'response': res[1],
615                 'average': round(avg, 2),
616             }
617         return val
618
619     def _get_in_visible_answer_type(self, cr, uid, context=None):
620         if context is None:
621             context = {}
622         return context.get('in_visible_answer_type', False)
623
624     _columns = {
625         'question_id': fields.many2one('survey.question', 'Question', ondelete='cascade'),
626         'answer': fields.char('Answer', size=128, required=1),
627         'sequence': fields.integer('Sequence'),
628         'response': fields.function(_calc_response_avg, string="#Answer", multi='sums'),
629         'average': fields.function(_calc_response_avg, string="#Avg", multi='sums'),
630         'type': fields.selection([('char','Character'),('date','Date'),('datetime','Date & Time'),\
631             ('integer','Integer'),('float','Float'),('selection','Selection'),\
632             ('email','Email')], "Type of Answer",required=1),
633         'menu_choice': fields.text('Menu Choices'),
634         'in_visible_answer_type': fields.boolean('Is Answer Type Invisible??')
635     }
636     _defaults = {
637 #         'sequence' : lambda * a: 1,
638          'type' : lambda * a: 'char',
639          'in_visible_answer_type':_get_in_visible_answer_type,
640     }
641
642     def default_get(self, cr, uid, fields, context=None):
643         if context is None:
644             context = {}
645         data = super(survey_answer, self).default_get(cr, uid, fields, context)
646         return data
647
648
649 class survey_response(osv.osv):
650     _name = "survey.response"
651     _rec_name = 'date_create'
652     _columns = {
653         'survey_id' : fields.many2one('survey', 'Survey', required=1, ondelete='cascade'),
654         'date_create' : fields.datetime('Create Date', required=1),
655         'user_id' : fields.many2one('res.users', 'User'),
656         'response_type' : fields.selection([('manually', 'Manually'), ('link', 'Link')], \
657                                     'Answer Type', required=1, readonly=1),
658         'question_ids' : fields.one2many('survey.response.line', 'response_id', 'Answer'),
659         'state' : fields.selection([('done', 'Finished '),('skip', 'Not Finished')], \
660                             'Status', readonly=True),
661     }
662     _defaults = {
663         'state' : lambda * a: "skip",
664         'response_type' : lambda * a: "manually",
665     }
666
667     def name_get(self, cr, uid, ids, context=None):
668         if not len(ids):
669             return []
670         reads = self.read(cr, uid, ids, ['user_id','date_create'], context=context)
671         res = []
672         for record in reads:
673             name = (record['user_id'] and record['user_id'][1] or '' )+ ' (' + record['date_create'].split('.')[0] + ')'
674             res.append((record['id'], name))
675         return res
676
677     def copy(self, cr, uid, id, default=None, context=None):
678         raise osv.except_osv(_('Warning!'),_('You cannot duplicate the resource!'))
679
680
681 class survey_response_line(osv.osv):
682     _name = 'survey.response.line'
683     _description = 'Survey Response Line'
684     _rec_name = 'date_create'
685     _columns = {
686         'response_id': fields.many2one('survey.response', 'Answer', ondelete='cascade'),
687         'date_create': fields.datetime('Create Date', required=1),
688         'state': fields.selection([('draft', 'Draft'), ('done', 'Answered'),('skip', 'Skiped')],\
689                                    'Status', readonly=True),
690         'question_id': fields.many2one('survey.question', 'Question'),
691         'page_id': fields.related('question_id', 'page_id', type='many2one', \
692                                   relation='survey.page', string='Page'),
693         'response_answer_ids': fields.one2many('survey.response.answer', 'response_id', 'Answer'),
694         'response_table_ids': fields.one2many('survey.tbl.column.heading', \
695                                     'response_table_id', 'Answer'),
696         'comment': fields.text('Notes'),
697         'single_text': fields.char('Text', size=255),
698     }
699     _defaults = {
700         'state' : lambda * a: "draft",
701     }
702
703
704 class survey_tbl_column_heading(osv.osv):
705     _name = 'survey.tbl.column.heading'
706     _order = 'name'
707     _columns = {
708         'name': fields.integer('Row Number'),
709         'column_id': fields.many2one('survey.question.column.heading', 'Column'),
710         'value': fields.char('Value', size = 255),
711         'response_table_id': fields.many2one('survey.response.line', 'Answer', ondelete='cascade'),
712     }
713
714
715 class survey_response_answer(osv.osv):
716     _name = 'survey.response.answer'
717     _description = 'Survey Answer'
718     _rec_name = 'response_id'
719     _columns = {
720         'response_id': fields.many2one('survey.response.line', 'Answer', ondelete='cascade'),
721         'answer_id': fields.many2one('survey.answer', 'Answer', required=1, ondelete='cascade'),
722         'column_id': fields.many2one('survey.question.column.heading','Column'),
723         'answer': fields.char('Value', size =255),
724         'value_choice': fields.char('Value Choice', size =255),
725         'comment': fields.text('Notes'),
726         'comment_field': fields.char('Comment', size = 255)
727     }
728
729
730 class res_users(osv.osv):
731     _inherit = "res.users"
732     _name = "res.users"
733     _columns = {
734         'survey_id': fields.many2many('survey', 'survey_users_rel', 'uid', 'sid', 'Groups'),
735     }
736
737
738 class survey_request(osv.osv):
739     _name = "survey.request"
740     _order = 'date_deadline'
741     _rec_name = 'date_deadline'
742     _columns = {
743         'date_deadline': fields.date("Deadline date"),
744         'user_id': fields.many2one("res.users", "User"),
745         'email': fields.char("Email", size=64),
746         'survey_id': fields.many2one("survey", "Survey", required=1, ondelete='cascade'),
747         'response': fields.many2one('survey.response', 'Answer'),
748         'state': fields.selection([('draft','Draft'),('cancel', 'Cancelled'),('waiting_answer', 'Waiting Answer'),('done', 'Done')], 'Status', readonly=1)
749     }
750     _defaults = {
751         'state': lambda * a: 'draft',
752 #        'date_deadline': lambda * a :  (datetime.now() + relativedelta(months=+1)).strftime("%Y-%m-%d %H:%M:%S")
753     }
754     def survey_req_waiting_answer(self, cr, uid, ids, arg):
755         self.write(cr, uid, ids, { 'state' : 'waiting_answer'})
756         return True
757
758     def survey_req_draft(self, cr, uid, ids, arg):
759         self.write(cr, uid, ids, { 'state' : 'draft'})
760         return True
761
762     def survey_req_done(self, cr, uid, ids, arg):
763         self.write(cr, uid, ids, { 'state' : 'done'})
764         return True
765
766     def survey_req_cancel(self, cr, uid, ids, arg):
767         self.write(cr, uid, ids, { 'state' : 'cancel'})
768         return True
769
770     def on_change_user(self, cr, uid, ids, user_id, context=None):
771         if user_id:
772             user_obj = self.pool.get('res.users')
773             user = user_obj.browse(cr, uid, user_id, context=context)
774             return {'value': {'email': user.email}}
775         return {}
776
777
778 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: