~andorrax/school-base-openerp-module/xavi

« back to all changes in this revision

Viewing changes to school_fet/school_assign_teachers.py

  • Committer: Pere Ramon Erro Mas
  • Date: 2011-07-25 16:59:02 UTC
  • Revision ID: pererem@perestriker-20110725165902-hn2lhpmb6m8x6r0h
[IMP] The beginning for the teachers to iwl assignation wizard... 
[IMP] Teachers are being sinchronized with employee and partner.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
import bisect
 
2
from datetime import datetime
 
3
 
 
4
from osv import fields
 
5
from osv import orm
 
6
from osv import osv
 
7
from tools.translate import _
 
8
 
 
9
class school_teacher(osv.osv):
 
10
    _name = 'school.teacher'
 
11
    _inherit = 'school.teacher'
 
12
 
 
13
    def _compute_assigned_hours(self, cr, uid, ids, field_name, arg, context=None):
 
14
        ret = {}
 
15
        for teacher in self.browse(cr, uid, ids):
 
16
            ret[teacher.id] = 0
 
17
            for teacher_data in teacher.teacher_data_ids:
 
18
                ret[teacher.id] += teacher_data.iwl_id.duration
 
19
        return ret
 
20
 
 
21
    def _get_teachers_from_iwls(self, cr, uid, ids, context=None):
 
22
        ret = set()
 
23
        for iwl in self.browse(cr, uid, ids):
 
24
            for teacher_data in iwl.teachers:
 
25
                ret.add(teacher_data.teacher_id.id)
 
26
        return list(ret)
 
27
 
 
28
    def _get_teachers_from_tds(self, cr, uid, ids, context=None):
 
29
        ret = set()
 
30
        for teacher_data in self.browse(cr, uid, ids):
 
31
            ret.add(teacher_data.teacher_id.id)
 
32
        return list(ret)
 
33
    
 
34
    _columns = {
 
35
        'max_week_hours': fields.float('Max hours', help="Max. week hours to help assign "),
 
36
        'min_week_hours': fields.float('Min hours', help="Min. week hours to help assign "),
 
37
        'teacher_data_ids': fields.one2many('school.teacher_data', 'teacher_id', string='Teacher datas', ),
 
38
        'assigned_hours': fields.function(_compute_assigned_hours, type='float', method=True, string='Assigned Hours',
 
39
                                          store={'school.impartition_week_line': (_get_teachers_from_iwls, ['duration'], 10),
 
40
                                          'school.teacher_data': (_get_teachers_from_tds, ['iwl_id', 'teacher_id'], 15)}),
 
41
    }
 
42
 
 
43
    _sql_constraints = [
 
44
        ('max_greater_or_equal_than_min', '(max_week_hours >= min_week_hours)', "Max hours should be greater or equal than min hours"),
 
45
        ('min_hours_not_negative', '(min_hours >= 0)', "Max hours should be not negative"),
 
46
        ]
 
47
 
 
48
school_teacher()
 
49
 
 
50
class school_teacher_course_suitability(osv.osv):
 
51
    _name = 'school.teacher_course_suitability'
 
52
 
 
53
    def _compute_assigned_hours(self, cr, uid, ids, field_name, arg, context=None):
 
54
        ret = {}
 
55
        for teacher_cs in self.browse(cr, uid, ids):
 
56
            ret[teacher_cs.id] = 0
 
57
            teacher = teacher_cs.teacher_id
 
58
            for teacher_data in teacher.teacher_data_ids:
 
59
                iwl = teacher_data.iwl_id
 
60
                if iwl.classe_id.course_id.id == teacher_cs.course_id.id:
 
61
                    ret[teacher_cs.id] += teacher_data.iwl_id.duration
 
62
        return ret
 
63
 
 
64
    def _get_teacher_cs_from_iwls(self, cr, uid, ids, context=None):
 
65
        ret = set()
 
66
        for iwl in self.browse(cr, uid, ids):
 
67
            course_id = iwl.classe_id.course_id.id
 
68
            for teacher_data in iwl.teachers:
 
69
                ret.add((teacher_data.teacher_id.id, course_id))
 
70
        ret2 = []
 
71
        for (teacher_id, course_id) in ret:
 
72
            ret2 += self.pool.get('school.teacher_course_suitability').search(cr, uid, [('teacher_id', '=', teacher_id), ('course_id', '=', course_id)])
 
73
        return ret2
 
74
 
 
75
    def _get_teacher_cs_from_tds(self, cr, uid, ids, context=None):
 
76
        ret = set()
 
77
        for teacher_data in self.browse(cr, uid, ids):
 
78
            course_id = teacher_data.iwl_id.classe_id.course_id.id
 
79
            teacher_id = teacher_data.teacher_id.id
 
80
            ret.add((teacher_id, course_id))
 
81
        ret2 = []
 
82
        for (teacher_id, course_id) in ret:
 
83
            ret2 += self.pool.get('school.teacher_course_suitability').search(cr, uid, [('teacher_id', '=', teacher_id), ('course_id', '=', course_id)])
 
84
        return ret2
 
85
 
 
86
    _columns = {
 
87
        'teacher_id': fields.many2one('school.teacher', 'Teacher', ondelete='cascade', ),
 
88
        'course_id': fields.many2one('school.course', 'Course', ondelete='cascade', ),
 
89
        'percentage': fields.float('Percentage', help='Percentage suitability', ),
 
90
        'max_week_hours': fields.float('Max hours', help="Max. week hours to help assign "),
 
91
        'min_week_hours': fields.float('Min hours', help="Min. week hours to help assign "),
 
92
        'assigned_hours': fields.function(_compute_assigned_hours, type='float', method=True, string='Assigned Hours',
 
93
                                          store={'school.impartition_week_line': (_get_teacher_cs_from_iwls, ['duration'], 10),
 
94
                                          'school.teacher_data': (_get_teacher_cs_from_tds, ['iwl_id', 'teacher_id'], 15)}),
 
95
        'total_max_hours': fields.related('teacher_id', 'max_week_hours', type='float', string="Total max", ),
 
96
        'total_min_hours': fields.related('teacher_id', 'min_week_hours', type='float', string="Total min", ),
 
97
        'total_assigned_hours': fields.related('teacher_id', 'assigned_hours', type='float', string="Total assigned", ),
 
98
    }
 
99
 
 
100
    _sql_constraints = [
 
101
        ('teacher_course_unique', 'UNIQUE(teacher_id,course_id)', 'Only one register for the same teacher and course'),
 
102
        ('max_greater_or_equal_than_min', 'CHECK(max_week_hours >= min_week_hours)', "Max hours should be greater or equal than min hours"),
 
103
        ('min_hours_not_negative', 'CHECK(min_hours >= 0)', "Max hours should be not negative"),
 
104
        ('percentage_between_0_and_100', 'CHECK(percentage BETWEEN 0 AND 100)', "Percentage should be between 0 and 100"),
 
105
        ]
 
106
 
 
107
school_teacher_course_suitability()
 
108
 
 
109
class school_impartition_week_line(osv.osv):
 
110
    _name = 'school.impartition_week_line'
 
111
    _inherit = 'school.impartition_week_line'
 
112
    
 
113
    def _compute_blocked(self, cr, uid, ids, field_name, arg, context=None):
 
114
        ret = {}
 
115
        for iwl in self.browse(cr, uid, ids):
 
116
            blocked = True
 
117
            ret[iwl.id] = {'needed_complete': (iwl.teachers_needed <= len(iwl.teachers)), }
 
118
            for td in iwl.teachers:
 
119
                blocked &= td.blocked
 
120
                if not blocked: break
 
121
            ret[iwl.id]['teachers_blocked'] = blocked
 
122
        return ret
 
123
 
 
124
    def _get_iwl_ids_from_td_ids(self, cr, uid, ids, context=None):
 
125
        ret = set()
 
126
        for td in self.browse(cr, uid, ids):
 
127
            ret.add(td.iwl_id.id)
 
128
        return list(ret)
 
129
            
 
130
    
 
131
    _columns = {
 
132
        'teachers_blocked': fields.function(_compute_blocked, type='boolean', method=True, string='Blocked', multi='teachers', help='No teachers changes allowed',
 
133
                                            store={'school.teacher_data': (_get_iwl_ids_from_td_ids, ['blocked'], 10)},
 
134
                                            ),
 
135
        'iwl_group': fields.char('IWL group', size=30, help="Group name for IWL's with same teacher", ),
 
136
        'teachers_needed': fields.integer('Teachers needed', ),
 
137
        'teachers_recommended': fields.integer('Teachers recommended', ),
 
138
        'needed_completed': fields.function(_compute_blocked, type='boolean', method=True, string='Teachers completed', multi='teachers', 
 
139
                                            store={'school.teacher_data': (_get_iwl_ids_from_td_ids, ['iwl_id', ], 10)},
 
140
                                            ),
 
141
    }
 
142
    
 
143
    _defaults = {
 
144
        'teachers_needed': lambda * a: 1,
 
145
        'teachers_recommended': lambda * a: 1,
 
146
    }
 
147
    
 
148
    _sql_constraints = [
 
149
        ('needed_and_recommended_check', '(teachers_needed <= teachers_recommended)', "The number required should be smaller than the recommended"),
 
150
        ('needed_not_negative', '(teachers_needed >= 0)', "The number required should be not negative"),
 
151
        ]
 
152
 
 
153
school_impartition_week_line()
 
154
 
 
155
class school_teacher_data(osv.osv):
 
156
    _name = 'school.teacher_data'
 
157
    _inherit = 'school.teacher_data'
 
158
 
 
159
    _columns = {
 
160
        'blocked': fields.boolean('Blocked', help='No teachers changes allowed',),
 
161
    }
 
162
 
 
163
school_teacher_data()
 
164
 
 
165
class school_teachers_solution(osv.osv):
 
166
    _name = 'school.teachers_solution'
 
167
 
 
168
school_teachers_solution()
 
169
 
 
170
class school_teacher_iwl_solution(osv.osv):
 
171
    _name = 'school.teacher_iwl_solution'
 
172
    
 
173
    _columns = {
 
174
        'solution_id': fields.many2one('school.teachers_solution', 'Solution', ondelete='cascade', required=True, ),
 
175
        'iwl_id': fields.many2one('school.impartition_week_line', 'IWL', ondelete='cascade', required=True, ),
 
176
        'teacher_id': fields.many2one('school.teacher', 'Teacher', ondelete='cascade', required=True, ),
 
177
        'value': fields.float('Value', ),
 
178
    }
 
179
    
 
180
school_teacher_iwl_solution()
 
181
 
 
182
class school_teachers_solution(osv.osv):
 
183
    _name = 'school.teachers_solution'
 
184
    
 
185
    def _compute(self, cr, uid, ids, field_name, arg, context=None):
 
186
        return sum([x.percentage for x in self.browse(cr, uid, ids, context=context)]) / len(ids)
 
187
        
 
188
    _columns = {
 
189
        'name': fields.char('Name', size=100, ),
 
190
        'created': fields.datetime('Created', ),
 
191
        'total_hours': fields.float('Total hours', ),
 
192
        'total_assigned': fields.float('Total assigned', ),
 
193
        'teacher_iwl_ids': fields.one2many('school.teacher_iwl_solution', 'solution_id', 'Solution', ),
 
194
        'value': fields.float('Value', ),
 
195
    }
 
196
    
 
197
    _defaults = {
 
198
        'created': lambda * a: datetime.today().strftime('%Y-%m-%d %H:%M:%S'),
 
199
    }
 
200
school_teachers_solution()
 
201
 
 
202
def agrupa_cursos_i_professors(teacher_data_by_course):
 
203
    cursos = {}
 
204
    professors = {}
 
205
    ret = []
 
206
 
 
207
    for (curs, _dicci) in teacher_data_by_course.items():
 
208
        for _dicci2 in _dicci['list']:
 
209
            professor = _dicci2['id']
 
210
            if curs not in cursos:
 
211
                if professor not in professors:
 
212
                    dicci = {'courses': set([curs]), 'professors': set([professor]), }
 
213
                    cursos[curs] = len(ret)
 
214
                    professors[professor] = len(ret)
 
215
                    ret.append(dicci)
 
216
                else:
 
217
                    dicci = ret[professors[professor]]
 
218
                    dicci['courses'].add(curs)
 
219
                    cursos[curs] = professors[professor]
 
220
            else:
 
221
                if professor not in professors:
 
222
                    dicci = ret[cursos[curs]]
 
223
                    dicci['professors'].add(professor)
 
224
                    professors[professor] = cursos[curs]
 
225
                else:
 
226
                    if cursos[curs] == professors[professor]:
 
227
                        continue
 
228
                    dicci = ret[cursos[curs]]
 
229
                    dicci2 = ret[professors[professor]]
 
230
                    dicci['courses'].update(dicci2['courses'])
 
231
                    dicci['professors'].update(dicci2['professors'])
 
232
                    for curs2 in dicci2['courses']:
 
233
                        cursos[curs2] = cursos[curs]
 
234
                    for professor2 in dicci2['professors']:
 
235
                        professors[professor2] = cursos[curs]
 
236
                    dicci2['courses'] = False
 
237
                    dicci2['professors'] = False
 
238
    return [x for x in ret if x['courses']]
 
239
 
 
240
class school_solution_data_by_course(osv.osv_memory):
 
241
    _name = 'school.solution.data_by_course'
 
242
    
 
243
    _columns = {
 
244
        'solution_id' : fields.many2one('school.create_solutions','Solution'),
 
245
        'course_id' : fields.many2one('school.course','Course',),
 
246
        'classes_hours' : fields.float('Classes Hours'),
 
247
        'max_hours' : fields.float('Max hours'),
 
248
        'min_hours' : fields.float('Min hours'),
 
249
    }
 
250
    
 
251
school_solution_data_by_course()
 
252
 
 
253
class school_create_solutions_wizard(osv.osv_memory):
 
254
    _name = 'school.create_solutions'
 
255
    
 
256
    _columns = {
 
257
        'number_max_solutions': fields.integer('Max. solutions'),
 
258
        'total_classes_hours': fields.float('Total classes hours', readonly=True, ),
 
259
        'total_max_hours': fields.float('Max teachers hours', readonly=True, ),
 
260
        'total_min_hours': fields.float('Min teachers hours', readonly=True, ),
 
261
        'data_by_course' : fields.one2many('school.solution.data_by_course', 'solution_id', readonly=True, )
 
262
    }
 
263
 
 
264
    def _default_total_classes_hours(self, cr, uid, context=None):
 
265
        (total_classes_hours, classes_data_by_course) = self.get_classes_data_by_course(cr, uid)
 
266
        return total_classes_hours
 
267
 
 
268
    def _default_total_max_hours(self, cr, uid, context=None):
 
269
        (total_classes_hours, classes_data_by_course) = self.get_classes_data_by_course(cr, uid)
 
270
        (total_teachers_hours, data_by_teacher, teachers_data_by_course) = self.get_teachers_data_by_course(cr, uid, classes_data_by_course.keys())
 
271
        return total_teachers_hours['pool_max']
 
272
 
 
273
    def _default_total_min_hours(self, cr, uid, context=None):
 
274
        (total_classes_hours, classes_data_by_course) = self.get_classes_data_by_course(cr, uid)
 
275
        (total_teachers_hours, data_by_teacher, teachers_data_by_course) = self.get_teachers_data_by_course(cr, uid, classes_data_by_course.keys())
 
276
        return total_teachers_hours['pool_min']
 
277
 
 
278
    def _default_data_by_course(self, cr, uid, context=None):
 
279
        (total_classes_hours, classes_data_by_course) = self.get_classes_data_by_course(cr, uid)
 
280
        (total_teachers_hours, data_by_teacher, teachers_data_by_course) = self.get_teachers_data_by_course(cr, uid, classes_data_by_course.keys())
 
281
        ret = []
 
282
        for (course_id, classes_data) in classes_data_by_course.items():
 
283
            ret.append({0: -1,'course_id': course_id,
 
284
                        'classes_hours': classes_data['duration'],
 
285
                        'max_hours': teachers_data_by_course[course_id]['pool_max'],
 
286
                        'min_hours': teachers_data_by_course[course_id]['pool_min'],})
 
287
 
 
288
        return ret
 
289
            
 
290
    _defaults = {
 
291
        'total_classes_hours': _default_total_classes_hours,
 
292
        'total_max_hours': _default_total_max_hours,
 
293
        'total_min_hours': _default_total_min_hours,
 
294
        'data_by_course' : _default_data_by_course,
 
295
    }
 
296
 
 
297
    def get_teachers_hours_blocked(self, cr, uid):
 
298
        obj_td = self.pool.get('school.teacher_data')
 
299
        total_hours = 0
 
300
        hours_by_teacher = {}
 
301
        hours_by_course_and_teacher = {}
 
302
        
 
303
        td_ids = obj_td.search(cr, uid, [('blocked', '=', True)])
 
304
        for td in obj_td.browse(cr, uid, td_ids):
 
305
            total_hours += td.iwl_id.duration
 
306
            if td.teacher_id.id not in hours_by_teacher:
 
307
                hours_by_teacher[td.teacher_id.id] = 0
 
308
            hours_by_teacher[td.teacher_id.id] += td.iwl_id.duration
 
309
            course_id = td.iwl_id.classe_id.course_id.id
 
310
            key = (course_id, td.teacher_id.id)
 
311
            if key not in hours_by_course_and_teacher:
 
312
                hours_by_course_and_teacher[key] = 0
 
313
            hours_by_course_and_teacher[key] += td.iwl_id.duration
 
314
        return (total_hours, hours_by_teacher, hours_by_course_and_teacher)
 
315
        
 
316
    
 
317
    
 
318
    def get_classes_data_by_course(self, cr, uid):
 
319
        obj_iwl = self.pool.get('school.impartition_week_line')
 
320
        iwl_ids = obj_iwl.search(cr, uid, [('teachers_blocked', '=', False), ])
 
321
        total_hours = 0
 
322
        ret = {}
 
323
        classes = {}
 
324
        for iwl in obj_iwl.browse(cr, uid, iwl_ids):
 
325
            classe_id = iwl.classe_id.id
 
326
            course_id = iwl.classe_id.course_id.id
 
327
            if not course_id in ret:
 
328
                ret[course_id] = {'duration': 0, 'classes': []}
 
329
                classes[course_id] = {}
 
330
            if (classe_id, iwl.iwl_group) not in classes[course_id]:
 
331
                classes[course_id][(classe_id, iwl.iwl_group)] = {'duration': 0, 'iwls': []}
 
332
            classes[course_id][(classe_id, iwl.iwl_group)]['duration'] += iwl.duration
 
333
            classes[course_id][(classe_id, iwl.iwl_group)]['iwls'].append(iwl.id)
 
334
            ret[course_id]['duration'] += iwl.duration
 
335
            total_hours += iwl.duration
 
336
        for course_id in ret.keys():
 
337
            ret[course_id]['classes'] = list(classes[course_id].values())
 
338
        return (total_hours, ret)
 
339
 
 
340
    
 
341
 
 
342
    def get_teachers_data_by_course(self, cr, uid, courses):
 
343
        total_hours = {'pool_max': 0, 'pool_min': 0}
 
344
        (total_blocked_hours, blocked_hours_by_teacher, blocked_hours_by_course_and_teacher) = self.get_teachers_hours_blocked(cr, uid)
 
345
        
 
346
        obj_tcs = self.pool.get('school.teacher_course_suitability')
 
347
        teachers_suitability_ids = obj_tcs.search(cr, uid, [('course_id', 'in', list(courses))])
 
348
 
 
349
        teacher_ids = set()
 
350
        teachers_data_by_course = {}
 
351
        for item in obj_tcs.browse(cr, uid, teachers_suitability_ids):
 
352
            teacher_ids.add(item.teacher_id.id)
 
353
            key = (item.course_id.id, item.teacher_id.id)
 
354
            if item.course_id.id not in teachers_data_by_course:
 
355
                teachers_data_by_course[item.course_id.id] = {'pool_max': 0, 'pool_min': 0, 'list': [], }
 
356
            max_hours = max(0, item.max_week_hours - blocked_hours_by_course_and_teacher.get(key, 0))
 
357
            min_hours = max(0, item.min_week_hours - blocked_hours_by_course_and_teacher.get(key, 0))
 
358
            teachers_data_by_course[item.course_id.id]['list'].append ({
 
359
                                                                       'percentage': item.percentage, 
 
360
                                                                       'id': item.teacher_id.id,
 
361
                                                                       'pool_max': max_hours,
 
362
                                                                       'pool_min': min_hours, })
 
363
            teachers_data_by_course[item.course_id.id]['pool_max'] += max_hours
 
364
            teachers_data_by_course[item.course_id.id]['pool_min'] += min_hours
 
365
 
 
366
        obj_t = self.pool.get('school.teacher')
 
367
        data_by_teacher = {}
 
368
        for x in obj_t.browse(cr, uid, obj_t.search(cr, uid, [('id','in',list(teacher_ids))])):
 
369
            max_hours = max(0, x.max_week_hours - blocked_hours_by_teacher.get(x.id, 0))
 
370
            min_hours = max(0, x.min_week_hours - blocked_hours_by_teacher.get(x.id, 0))
 
371
            if max_hours > 0:
 
372
                data_by_teacher[x.id] = {
 
373
                    'pool_max': max_hours,
 
374
                    'pool_min': min_hours,
 
375
                }
 
376
                total_hours['pool_max'] += max_hours
 
377
                total_hours['pool_min'] += min_hours
 
378
        
 
379
        return (total_hours, data_by_teacher, teachers_data_by_course)
 
380
 
 
381
    def act_cancel(self, cr, uid, ids, context=None):
 
382
        return {'type': 'ir.actions.act_window_close'}
 
383
 
 
384
    def create_solutions(self, cr, uid, ids, context=None):
 
385
        
 
386
        sols_from_wizard = []
 
387
        for create_solutions_wizard in self.browse(cr, uid, ids):
 
388
            number_max_solutions = create_solutions_wizard.number_max_solutions
 
389
            (total_classes_hours, classes_data_by_course) = self.get_classes_data_by_course(cr, uid)
 
390
            (total_teachers_hours, data_by_teacher, teachers_data_by_course) = self.get_teachers_data_by_course(cr, uid, classes_data_by_course.keys())
 
391
            
 
392
            errors = []
 
393
            if total_classes_hours > total_teachers_hours['pool_max']: errors.append( _('Total hours to assign exceeds teachers disponibility.') )
 
394
            if total_classes_hours < total_teachers_hours['pool_min']: errors.append( _('No classe hours for minimum requires') )
 
395
            for (course_id, classe_data) in classes_data_by_course.items():
 
396
                if classe_data['duration'] > teachers_data_by_course[course_id]['pool_max']:
 
397
                    errors.append( _('Total hours to assign exceeds teachers disponibility by course with id %s.') % (course_id,) )
 
398
                if classe_data['duration'] < teachers_data_by_course[course_id]['pool_min']:
 
399
                    errors.append( _('No classe hours for minimum requires by course with id %s.') % (course_id,) )
 
400
            if errors:
 
401
                raise orm.except_orm('Error!', '\n'.join(errors))
 
402
                                
 
403
            for agrupacio in agrupa_cursos_i_professors(teachers_data_by_course):
 
404
                def assigna_classes(classes, data_by_teacher, teacher_course_data_list, total_hours, total_hours2, total_min_hours, total_min_hours2, max_solutions, classe_cursor=0, afinament = 2):
 
405
                    llista_de_solucions_a_retornar = []
 
406
                    classe_duration = classes[classe_cursor]['duration']                    
 
407
 
 
408
                    tcd_list_ordered = sorted(teacher_course_data_list, key=lambda x: (x['pool_min'],data_by_teacher[x['id']]['pool_min'],x['percentage'],x['pool_max']), reverse = True)
 
409
                    new_list = []
 
410
                    max_hours = 0
 
411
                    for tcd in tcd_list_ordered:
 
412
                        # Reduim la llista de professors
 
413
                        # Deixem els que tenen un minim d'hores per aquest curs
 
414
                        # i els que necessiten aquest curs per cumplir el minim d'hores generals
 
415
                        # completem amb els restants fins a tenir un nombre d'hores mes que suficient
 
416
                        if tcd['pool_min'] > 0 or max_hours < total_hours * afinament:
 
417
                            new_list.append(tcd)
 
418
                            max_hours += tcd['pool_max']
 
419
                        else:
 
420
                            break
 
421
                    for punter_teacher in range(len(new_list)):
 
422
                        teacher = new_list[punter_teacher]
 
423
                        if teacher['id'] not in data_by_teacher: continue
 
424
                        check_max_hours = teacher['pool_max'] > classe_duration
 
425
                        check_max_hours2 = data_by_teacher[teacher['id']]['pool_max'] > classe_duration
 
426
                        check_min_hours = (total_min_hours - teacher['pool_min'] <= total_hours - classe_duration)
 
427
                        check_min_hours2 = (total_min_hours2 - data_by_teacher[teacher['id']]['pool_min'] <= total_hours2 - classe_duration)
 
428
                        if check_max_hours and check_min_hours and check_max_hours2 and check_min_hours2:
 
429
                            # TODO : Mirar en FET
 
430
                            # classes[classe_cursor]['teacher'] = teacher['id']
 
431
                            # mira en FET
 
432
                            
 
433
                            # Apuntem la solucio actual
 
434
                            if len(llista_de_solucions_a_retornar) < max_solutions or llista_de_solucions_a_retornar[-1] < teacher['percentage']:
 
435
                                new_max = (teacher['percentage'], [teacher['id']])
 
436
                                bisect.insort(llista_de_solucions_a_retornar, new_max)
 
437
                                if len(llista_de_solucions_a_retornar) > max_solutions:
 
438
                                    llista_de_solucions_a_retornar = llista_de_solucions_a_retornar[0:max_solutions]
 
439
 
 
440
                            # Si es la darrera classe no cal fer mes recursivitat
 
441
                            if classe_cursor + 1 >= len(classes):
 
442
                                continue
 
443
 
 
444
                            # Actualitzem les variables de les estructures de dades
 
445
                            teacher['pool_max'] -= classe_duration
 
446
                            data_by_teacher[teacher['id']]['pool_max'] -= classe_duration
 
447
                            min_to_reduce = min(classe_duration, teacher['pool_min'])
 
448
                            teacher['pool_min'] -= min_to_reduce
 
449
                            min_to_reduce2 = min(classe_duration, data_by_teacher[teacher['id']]['pool_min'])
 
450
                            data_by_teacher[teacher['id']]['pool_min'] -= min_to_reduce2
 
451
 
 
452
                            # Executem la recursio
 
453
                            value_assignacio_list = assigna_classes(classes, data_by_teacher, teacher_course_data_list, total_hours - classe_duration, total_hours2 - classe_duration, total_min_hours - min_to_reduce, total_min_hours2 - min_to_reduce2, max_solutions, classe_cursor = classe_cursor + 1)
 
454
 
 
455
                            # Deixem les variables de les estructures de dades com estaven
 
456
                            teacher['pool_max'] += classe_duration
 
457
                            teacher['pool_min'] += min_to_reduce
 
458
                            data_by_teacher[teacher['id']]['pool_max'] += classe_duration
 
459
                            data_by_teacher[teacher['id']]['pool_min'] += min_to_reduce2
 
460
 
 
461
                            # Apuntem els resultats de la recursio
 
462
                            for (value, assignacio) in value_assignacio_list:
 
463
                                if len(llista_de_solucions_a_retornar) < max_solutions or llista_de_solucions_a_retornar[max_solutions-1][0] < value + teacher['percentage']:
 
464
                                    new_max = (value + teacher['percentage'], [teacher['id']] + assignacio)
 
465
                                    llista_de_solucions_a_retornar.reverse()
 
466
                                    bisect.insort(llista_de_solucions_a_retornar, new_max)
 
467
                                    llista_de_solucions_a_retornar.reverse()
 
468
                            if len(llista_de_solucions_a_retornar) > max_solutions:
 
469
                                llista_de_solucions_a_retornar = llista_de_solucions_a_retornar[0:max_solutions]
 
470
                                        
 
471
                    return llista_de_solucions_a_retornar
 
472
 
 
473
                def assigna_cursos(cursos_list_orig, total_classes_hours, classes_data_by_course, total_teachers_hours, data_by_teacher, teachers_data_by_course, max_solutions):
 
474
                    llista_de_solucions_a_retornar = []
 
475
                    for courses_punter in range(len(cursos_list_orig)):
 
476
                        cursos_list = cursos_list_orig[:courses_punter] + cursos_list_orig[courses_punter + 1:]
 
477
                        course_id = cursos_list_orig[courses_punter]
 
478
 
 
479
                        classes = classes_data_by_course[course_id]['classes']
 
480
                        total_course_duration = classes_data_by_course[course_id]['duration']
 
481
                        teacher_course_data = teachers_data_by_course[course_id]
 
482
                        teacher_course_data_list = teacher_course_data['list']
 
483
 
 
484
                        sols = assigna_classes(classes, data_by_teacher, teacher_course_data_list, total_course_duration, total_classes_hours, teacher_course_data['pool_min'], total_teachers_hours['pool_min'], max_solutions)
 
485
 
 
486
                        if not sols: continue
 
487
 
 
488
                        if not cursos_list:
 
489
                            for (value, assignacio) in sols:
 
490
                                llista_de_solucions_a_retornar.append((value, {course_id: assignacio, }))
 
491
 
 
492
                            continue
 
493
 
 
494
                        for (value, assignacio) in sols:
 
495
                            # Contem les hores per reduir el necessari en l'estructura
 
496
                            # de dades d'hores maximes que passarem per assignar els seguents cursos
 
497
                            hours_by_teacher = {}
 
498
                            total_hours = 0
 
499
                            for i in range(len(assignacio)):
 
500
                                teacher_id = assignacio[i]
 
501
                                classe = classes[i]
 
502
                                if teacher_id not in hours_by_teacher:
 
503
                                    hours_by_teacher[teacher_id] = 0
 
504
                                hours_by_teacher[teacher_id] += classe['duration']
 
505
                                total_hours += classe['duration']
 
506
 
 
507
                            # Apliquem la reduccio i contabilitzem les hores minimes
 
508
                            total_teachers_hours['pool_max'] -= total_hours
 
509
                            min_to_reduce = 0
 
510
                            min_to_reduce_by_teacher = {}
 
511
                            for (teacher_id, hours) in hours_by_teacher.items():
 
512
                                data_by_teacher[teacher_id]['pool_max'] -= hours
 
513
                                min_to_reduce_by_teacher['teacher_id'] = min(hours, data_by_teacher[teacher_id]['pool_min'])
 
514
                                data_by_teacher[teacher_id]['pool_min'] -= min_to_reduce_by_teacher['teacher_id']
 
515
                                min_to_reduce += min_to_reduce_by_teacher['teacher_id']
 
516
                            total_teachers_hours['pool_min'] -= min_to_reduce
 
517
 
 
518
                            # Fem recursivitat per
 
519
                            value_assignacio_list = assigna_cursos(cursos_list, total_classes_hours - total_hours, classes_data_by_course, total_teachers_hours, data_by_teacher, teachers_data_by_course, max_solutions)
 
520
                            total_teachers_hours['pool_max'] += total_hours
 
521
                            total_teachers_hours['pool_min'] += min_to_reduce
 
522
                            for (teacher_id, hours) in hours_by_teacher.items():
 
523
                                data_by_teacher[teacher_id]['pool_max'] += hours
 
524
                                data_by_teacher[teacher_id]['pool_min'] += min_to_reduce_by_teacher['teacher_id']
 
525
 
 
526
                            for (value2, assignacio_by_course) in value_assignacio_list:
 
527
                                if len(llista_de_solucions_a_retornar) < max_solutions or llista_de_solucions_a_retornar[max_solutions-1][0] < value2 + value:
 
528
                                    assignacio_by_course[course_id] = assignacio
 
529
                                    new_max = (value2 + value, assignacio_by_course)
 
530
                                    llista_de_solucions_a_retornar.reverse()
 
531
                                    bisect.insort(llista_de_solucions_a_retornar, new_max)
 
532
                                    llista_de_solucions_a_retornar.reverse()
 
533
                            if len(llista_de_solucions_a_retornar) > max_solutions:
 
534
                                llista_de_solucions_a_retornar = llista_de_solucions_a_retornar[0:max_solutions]
 
535
                    return llista_de_solucions_a_retornar
 
536
                                    
 
537
                # TODO: fer una llista de cursos ordenada comencant per el mes dificil d'assignar
 
538
                #       professors. Es provaran totes pero s'ha de mirar de comencar a
 
539
                #       permutar per on hi ha mes probabilitat de solucio
 
540
                sols = assigna_cursos(list(agrupacio['courses']), total_classes_hours, classes_data_by_course, total_teachers_hours, data_by_teacher, teachers_data_by_course, number_max_solutions)
 
541
 
 
542
                # Passar solucions a base de dades
 
543
                for (value, assignacio_by_course) in sols:
 
544
                    sol_id = self.pool.get('school.teachers_solution').create(cr, uid, {'value': value, 'total_hours': total_classes_hours, })
 
545
                    sols_from_wizard.append(sol_id)
 
546
                    total_assigned = 0
 
547
                    for (course_id, assignacio) in assignacio_by_course.items():
 
548
                        for i in range(len(assignacio)):
 
549
                            classe = classes_data_by_course[course_id]['classes'][i]
 
550
                            total_assigned += classe['duration']
 
551
                            for iwl_id in classe['iwls']:
 
552
                                value = [x['percentage'] for x in teachers_data_by_course[course_id]['list'] if x['id'] == assignacio[i]][0]
 
553
                                self.pool.get('school.teacher_iwl_solution').create(cr, uid, {'solution_id': sol_id, 'iwl_id': iwl_id, 'teacher_id': assignacio[i], 'value': value})
 
554
                    self.pool.get('school.teachers_solution').write(cr, uid, [sol_id], {'total_assigned': total_assigned, })
 
555
 
 
556
        cr.execute('select id,name from ir_ui_view where model=%s and type=%s', ('school.teachers_solution', 'tree'))
 
557
        view_res = cr.fetchone()
 
558
 
 
559
        return {
 
560
            'domain': "[('id','in',%s),]" % (tuple(sols_from_wizard), ),
 
561
            'name': _("Solutions from wizard"),
 
562
            'view_type': 'form',
 
563
            'view_mode': 'tree,form',
 
564
            'res_model': 'school.teachers_solution',
 
565
            'view_id': view_res,
 
566
            'context': context,
 
567
            'type': 'ir.actions.act_window',
 
568
        }
 
569
 
 
570
                    
 
571
school_create_solutions_wizard()
 
572
 
 
573
class school_block_iwl_wizard(osv.osv_memory):
 
574
    _name = 'school.block_iwl_wizard'
 
575
 
 
576
    _columns = {
 
577
        'iwl_ids': fields.many2many('school.impartition_week_line', 'iwl_block_wizard_rel', 'wizard_id', 'iwl_id', string='IWLs', ),
 
578
        'block': fields.boolean('Block', help='Block, yes or no',),
 
579
        'teachers_needed': fields.integer('Teachers needed', ),
 
580
        'teachers_recommended': fields.integer('Teachers recommended', ),
 
581
 
 
582
    }
 
583
 
 
584
    _defaults = {
 
585
        'iwl_ids': lambda self, cr, uid, context = {}: [(6,0,context.get('active_ids', []))],
 
586
        'teachers_needed': lambda * a: 1,
 
587
        'teachers_recommended': lambda * a: 1,
 
588
    }
 
589
 
 
590
    def act_cancel(self, cr, uid, ids, context=None):
 
591
        return {'type': 'ir.actions.act_window_close'}
 
592
 
 
593
    def action_block(self, cr, uid, ids, context=None):
 
594
        for item in self.browse(cr, uid, ids):
 
595
            iwl_ids = [x.id for x in item.iwl_ids]
 
596
            self.pool.get('school.impartition_week_line').write(cr, uid, iwl_ids, {'teachers_needed': item.teachers_needed, 'teachers_recommended': item.teachers_recommended})
 
597
            td_ids = self.pool.get('school.teacher_data').search(cr, uid, [('iwl_id', 'in', iwl_ids)])
 
598
            self.pool.get('school.teacher_data').write(cr, uid, td_ids, {'blocked': item.block, })
 
599
 
 
600
school_block_iwl_wizard()
 
601
 
 
602
class school_block_td_wizard(osv.osv_memory):
 
603
    _name = 'school.block_td_wizard'
 
604
 
 
605
    _columns = {
 
606
        'td_ids': fields.many2many('school.impartition_week_line', 'iwl_block_wizard_rel', 'wizard_id', 'iwl_id', string='IWLs', ),
 
607
        'block': fields.boolean('Block', help='Block, yes or no',),
 
608
 
 
609
    }
 
610
 
 
611
    _defaults = {
 
612
        'td_ids': lambda self, cr, uid, context = {}: [(6,0,context.get('active_ids', []))],
 
613
    }
 
614
 
 
615
    def act_cancel(self, cr, uid, ids, context=None):
 
616
        return {'type': 'ir.actions.act_window_close'}
 
617
 
 
618
    def action_block(self, cr, uid, ids, context=None):
 
619
        for item in self.browse(cr, uid, ids):
 
620
            td_ids = [x.id for x in item.td_ids]
 
621
            self.pool.get('school.teacher_data').write(cr, uid, td_ids, {'blocked': item.block, })
 
622
 
 
623
school_block_td_wizard()
 
624
 
 
625
class school_teacher_change_hours(osv.osv_memory):
 
626
    _name = 'school.teacher_change_hours'
 
627
 
 
628
    _columns = {
 
629
        'teacher_ids': fields.many2many('school.teacher', 'teacher_change_hours_wizard_rel', 'wizard_id', 'teacher_id', string='Teachers', ),
 
630
        'max_hours': fields.float('Max Hours', help='Max hours',),
 
631
        'min_hours': fields.float('Min Hours', help='Min hours',),
 
632
    }
 
633
 
 
634
    _defaults = {
 
635
        'teacher_ids': lambda self, cr, uid, context = {}: context.get('active_ids', []),
 
636
    }
 
637
 
 
638
    def act_cancel(self, cr, uid, ids, context=None):
 
639
        return {'type': 'ir.actions.act_window_close'}
 
640
 
 
641
    def action_change(self, cr, uid, ids, context=None):
 
642
        for item in self.browse(cr, uid, ids):
 
643
            teacher_ids = [x.id for x in item.teacher_ids]
 
644
            self.pool.get('school.teacher').write(cr, uid, teacher_ids, {'max_week_hours': item.max_hours, 'min_week_hours': item.min_hours})
 
645
        return {'type': 'ir.actions.act_window_close'}
 
646
 
 
647
school_teacher_change_hours()
 
648
 
 
649
class school_teacher_suitability_change(osv.osv_memory):
 
650
    _name = 'school.teacher_suitability_change'
 
651
 
 
652
    _columns = {
 
653
        'teacher_ids': fields.many2many('school.teacher', 'teacher_suitability_change_teacher_rel', 'wizard_id', 'teacher_id', string='Teachers', ),
 
654
        'course_ids': fields.many2many('school.course', 'teacher_suitability_change_course_rel', 'wizard_id', 'course_id', string='Courses', ),
 
655
        'percentage': fields.float('Percentage', ),
 
656
        'max_hours': fields.float('Max Hours', help='Max hours',),
 
657
        'min_hours': fields.float('Min Hours', help='Min hours',),
 
658
    }
 
659
 
 
660
    _defaults = {
 
661
        'teacher_ids': lambda self, cr, uid, context = {}: context.get('active_ids', []),
 
662
    }
 
663
 
 
664
    def act_cancel(self, cr, uid, ids, context=None):
 
665
        return {'type': 'ir.actions.act_window_close'}
 
666
 
 
667
    def action_change(self, cr, uid, ids, context=None):
 
668
        obj = self.pool.get('school.teacher_course_suitability')
 
669
        for item in self.browse(cr, uid, ids):
 
670
            dicci = {'max_week_hours': item.max_hours, 'min_week_hours': item.min_hours, 'percentage': item.percentage,}
 
671
            ids_to_write = []
 
672
            for teacher in item.teacher_ids:
 
673
                for course in item.course_ids:
 
674
                    tcs_ids = obj.search(cr, uid, [('teacher_id', '=', teacher.id), ('course_id', '=', course.id)])
 
675
                    if tcs_ids:
 
676
                        ids_to_write += tcs_ids
 
677
                    else:
 
678
                        dicci2 = dict(dicci)
 
679
                        dicci2.update({'teacher_id': teacher.id, 'course_id': course.id,})
 
680
                        obj.create(cr, uid, dicci2, context = context)
 
681
            if ids_to_write:
 
682
                obj.write(cr, uid, ids_to_write, dicci, context = context)
 
683
        return {'type': 'ir.actions.act_window_close'}
 
684
 
 
685
school_teacher_suitability_change()
 
686
 
 
687
class school_apply_teacher_solution(osv.osv_memory):
 
688
    _name = 'school.apply_teacher_solution'
 
689
 
 
690
    _columns = {
 
691
        'solution_ids': fields.many2many('school.teachers_solution', 'teacher_solution_apply_rel', 'wizard_id', 'solution_id', string='Solutions', ),
 
692
    }
 
693
 
 
694
    _defaults = {
 
695
        'solution_ids': lambda self, cr, uid, context = {}: context.get('active_ids', []),
 
696
    }
 
697
 
 
698
    def act_cancel(self, cr, uid, ids, context=None):
 
699
        return {'type': 'ir.actions.act_window_close'}
 
700
 
 
701
    def action_change(self, cr, uid, ids, context=None):
 
702
        obj_td = self.pool.get('school.teacher_data')
 
703
        ids_td_created = []
 
704
        ids_to_unlink = []
 
705
        for item in self.browse(cr, uid, ids):
 
706
            iwls_cleaned = set()
 
707
            for solution in item.solution_ids:
 
708
                for teacher_iwl in solution.teacher_iwl_ids:
 
709
                    iwl_id = teacher_iwl.iwl_id.id
 
710
                    if iwl_id not in iwls_cleaned:
 
711
                        iwls_cleaned.add(iwl_id)
 
712
                        ids_to_unlink += obj_td.search(cr, uid, [('iwl_id','=',iwl_id),('blocked','=',False)])
 
713
                    ids_td_created.append( obj_td.create(cr, uid, {'iwl_id': iwl_id, 'teacher_id': teacher_iwl.teacher_id.id, 'title': 'needed'}) )
 
714
        obj_td.unlink(cr, uid, ids_to_unlink)
 
715
 
 
716
        cr.execute('select id,name from ir_ui_view where model=%s and type=%s', ('school.teacher_data', 'tree'))
 
717
        view_res = cr.fetchone()
 
718
 
 
719
        return  {
 
720
            'domain': "[('id','in',%s),]" % (tuple(ids_td_created), ),
 
721
            'name': _("Assignations created by the apply solutions wizard"),
 
722
            'view_type': 'form',
 
723
            'view_mode': 'tree,form',
 
724
            'res_model': 'school.teacher_data',
 
725
            'view_id': view_res,
 
726
            'context': context,
 
727
            'type': 'ir.actions.act_window',
 
728
        }
 
729
school_apply_teacher_solution()
 
730
 
 
731
 
 
732
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: