Ajout d'un champ charge_reel dans palier
[OpenERP/cmmi.git] / axes.py
1 #-*- coding: utf8 -*-
2 '''
3 '''
4
5 from openerp.osv import osv, fields
6 from datetime import date, timedelta, datetime
7
8 # ================================ MESURABLE ================================ #
9 class Mesurable(osv.Model):
10     _name = "cmmi.axes.mesurable"
11
12     _description = "Table de reference des mesusrables."
13
14     _states = [("cree", "Crée"), ("encours", "En cours"),
15                 ("termine", "Terminé"), ("abandonne", "Abandonné"),
16                 ("suspendu", "Suspendu"), ("generique", "Générique")]
17
18
19     def _nb_jours_init(self, cr, uid, ids, field, arg, context=None):
20         result = {}
21         for m in self.browse(cr, uid, ids, context=context):
22             if not m.date_init_deb or not m.date_init_fin:
23                 result[m.id] = 0
24                 continue
25             result[m.id] = Mesurable._nb_jours_ouvre_entre_2_dates(
26                         datetime.strptime(m.date_init_deb, "%Y-%m-%d").date(),
27                         datetime.strptime(m.date_init_fin, "%Y-%m-%d").date())
28         return result
29
30
31     def _nb_jours_plan(self, cr, uid, ids, field, arg, context=None):
32         result = {}
33         for m in self.browse(cr, uid, ids, context=context):
34             if not m.date_plan_deb or not m.date_plan_fin:
35                 result[m.id] = 0
36                 continue
37             result[m.id] = Mesurable._nb_jours_ouvre_entre_2_dates(
38                         datetime.strptime(m.date_plan_deb, "%Y-%m-%d").date(),
39                         datetime.strptime(m.date_plan_fin, "%Y-%m-%d").date())
40         return result
41
42
43     def _nb_jours_reel(self, cr, uid, ids, field, arg, context=None):
44         result = {}
45         for m in self.browse(cr, uid, ids, context=context):
46             if not m.date_reel_deb or not m.date_reel_fin:
47                 result[m.id] = 0
48                 continue
49             result[m.id] = Mesurable._nb_jours_ouvre_entre_2_dates(
50                         datetime.strptime(m.date_reel_deb, "%Y-%m-%d").date(),
51                         datetime.strptime(m.date_reel_fin, "%Y-%m-%d").date())
52         return result
53
54
55     _columns = {
56         "name": fields.char(string="Title", size=64, required=True),
57         "description": fields.text(string="Description"),
58         "commentaire": fields.text(string="Commentaire"),
59         "state": fields.selection(_states, string="State"),
60         "version": fields.char(string="Version", size=16),
61         "date_jalon": fields.date(string="Jalon"),
62         "date_init_deb": fields.date(string="Init début"),
63         "date_init_fin": fields.date(string="Init fin"),
64         "date_plan_deb": fields.date(string="Plan début"),
65         "date_plan_fin": fields.date(string="Plan fin"),
66         "date_reel_deb": fields.date(string="Réel début"),
67         "date_reel_fin": fields.date(string="Réel fin"),
68         "nb_jours_init": fields.function(_nb_jours_init,
69                                          type="integer",
70                                          string="Nombre de jours initials"),
71         "nb_jours_plan": fields.function(_nb_jours_plan,
72                                          type="integer",
73                                          string="Nombre de jours planifiés"),
74         "nb_jours_reel": fields.function(_nb_jours_reel,
75                                          type="integer",
76                                          string="Nombre de jours réels"),
77     }
78
79     _defaults = {
80         "state": "cree",
81     }
82
83     _sql_constraints = [
84         (
85             "date_init_deb_before_date_init_fin",
86             "CHECK(date_init_deb <= date_init_fin)",
87             "The date_init_deb should be previous date_init_fin",
88         ),
89         (
90             "date_plan_deb_before_date_plan_fin",
91             "CHECK(date_plan_deb <= date_plan_fin)",
92             "The date_plan_deb should be previous date_plan_fin",
93         ),
94         (
95             "date_reel_deb_before_date_reel_fin",
96             "CHECK(date_reel_deb <= date_reel_fin)",
97             "The date_reel_deb should be previous date_reel_fin",
98         ),
99     ]
100
101
102     def action_commencer(self, cr, uid, ids, context=None):
103         if type(ids) == list:
104             if len(ids) != 1:
105                 return
106             ids = ids[0]
107
108         palier = self.read(cr, uid, ids, ['date_plan_deb', 'date_plan_fin', 'state'], context)
109
110         if palier['state'] != 'cree':
111             return
112
113         self.write(
114             cr,
115             uid,
116             ids, {
117                 'date_init_deb' : palier['date_plan_deb'],
118                 'date_init_fin' : palier['date_plan_fin'],
119                 'state': 'encours'
120             },
121             context)
122         return self
123
124
125     def action_suspendre(self, cr, uid, ids, context=None):
126         if type(ids) == list:
127             if len(ids) != 1:
128                 return # TODO: message d'avertissement
129             ids = ids[0]
130
131         mesurable = self.read(cr, uid, ids, ['state'], context)
132         if mesurable['state'] != 'encours':
133             return
134         self.write(
135             cr,
136             uid,
137             ids,
138             {'state': 'suspendu'},
139             context,
140         )
141         return self
142
143     def action_terminer(self, cr, uid, ids, context=None):
144         if type(ids) == list:
145             if len(ids) != 1:
146                 return # TODO: message d'avertissement
147             ids = ids[0]
148
149         mesurable = self.read(cr, uid, ids, ['state'], context)
150         if mesurable['state'] != 'encours':
151             return
152         self.write(
153             cr,
154             uid,
155             ids,
156             {'state': 'termine'},
157             context,
158         )
159         return self
160
161     def action_abandonner(self, cr, uid, ids, context=None):
162         if type(ids) == list:
163             if len(ids) != 1:
164                 return # TODO: message d'avertissement
165             ids = ids[0]
166
167         mesurable = self.read(cr, uid, ids, ['state'], context)
168         if not ('encours', 'cree').__contains__(mesurable['state']):
169             return
170         self.write(
171             cr,
172             uid,
173             ids,
174             {'state': 'abandonne'},
175             context,
176         )
177         return self
178
179     def action_reprendre(self, cr, uid, ids, context=None):
180         if type(ids) == list:
181             if len(ids) != 1:
182                 return # TODO: message d'avertissement
183             ids = ids[0]
184
185         mesurable = self.read(cr, uid, ids, ['state'], context)
186         if mesurable['state'] != 'suspendu':
187             return
188         self.write(
189             cr,
190             uid,
191             ids,
192             {'state': 'encours'},
193             context,
194         )
195         return self
196
197 #------------ TRAVAIL CALCUL JOURS OUVRES ------------
198     @staticmethod
199     def _get_date_paques(annee):
200         """
201         Retourne la date du dimanque de pâques pour une année donnée
202             sous la forme d'un objet date.
203         """
204         a = annee % 19
205         b = annee // 100
206         c = annee % 100
207         d = (19 * a + b - b // 4 - ((b - (b + 8) // 25 + 1) // 3) + 15) % 30
208         e = (32 + 2 * (b % 4) + 2 * (c // 4) - d - (c % 4)) % 7
209         f = d + e - 7 * ((a + 11 * d + 22 * e) // 451) + 114
210         mois = f // 31
211         jours = f % 31 + 1
212         return date(annee, mois, jours)
213
214
215     @staticmethod
216     def _get_jours_feries(annee):
217         """
218         Retourne une liste contenant les jours fériés d'une année donnée.
219         """
220         date_paques = Mesurable._get_date_paques(annee)
221         return [
222             date_paques+timedelta(days=1), # Lundi de Pâques
223             date_paques+timedelta(days=39), # Jeudi de l'Ascension
224             date_paques+timedelta(days=50), # Lundi de Pentecôte
225             date(annee, 1, 1), # Jour de l'An
226             date(annee, 5, 1), # Fête du travail
227             date(annee, 5, 8), # Armistice 1945
228             date(annee, 7, 14), # Fête Nationale
229             date(annee, 8, 15), # Assomption
230             date(annee, 11, 1), # Toussaint
231             date(annee, 11, 11), # Armistice 1918
232             date(annee, 12, 25), # Noël
233         ]
234
235
236     @staticmethod
237     def _jour_ouvre(d):
238         """ Retourne vrai si la date est un jour ouvre, faux sinon."""
239         if (5,6).__contains__(d.weekday()) or Mesurable._get_jours_feries(d.year).__contains__(d):
240             return False
241         else:
242             return True
243
244
245     @staticmethod
246     def _nb_jours_ouvre_entre_2_dates(d1, d2):
247         """
248         Retourne le nombre de jours ouvres entre deux dates données.
249
250         >>> nb_jours_ouvre_entre_2_dates(date(2013, 5, 1), date(2013, 7, 31))
251         62
252         >>> nb_jours_ouvre_entre_2_dates(date(2013, 7, 31), date(2013, 5, 1))
253         62
254         """
255         if not d1 or not d2:
256             return 0
257         if d1>d2:
258             d1, d2 = d2, d1 #Switch les 2 dates pour que d1 soit la plus petite
259         tmp = d1
260         jour_ouvres = 0
261         while tmp <= d2:
262             if Mesurable._jour_ouvre(tmp): # Si tmp est un jour ouvre
263                 jour_ouvres += 1
264             tmp += timedelta(days=1)
265         return jour_ouvres
266
267
268
269 # ================================= PALIER ================================== #
270 class Palier(osv.Model):
271     _name = "cmmi.axes.palier"
272
273     _description = "Palier d'un projet."
274
275     _inherit = "cmmi.axes.mesurable"
276
277     _types_palier = [("normal", "Normal"), ("exceptionnel", "Exceptionnel"),
278                      ("correctif", "Correctif"), ("autre", "Autre")]
279
280
281     def _get_charge_init(self, cr, uid, ids, field, arg, context=None):
282         result = {}
283         for palier in self.browse(cr, uid, ids, context=context):
284             result[palier.id] = sum([e.charge_init for e in palier.evolutions])
285         return result
286
287
288     def _get_charge_plan(self, cr, uid, ids, field, arg, context=None):
289         result = {}
290         for palier in self.browse(cr, uid, ids, context=context):
291             result[palier.id] = sum([e.charge_plan for e in palier.evolutions])
292         return result
293
294
295     def _get_charge_reel(self, cr, uid, ids, field, arg, context=None):
296         result = {}
297         for p in self.browse(cr, uid, ids, context=context):
298             result[p.id] = sum([e.charge_reel for e in p.evolutions])
299         return result
300
301
302     _columns = {
303         "type_palier": fields.selection(_types_palier, string="Type"),
304         "projet_id": fields.many2one("cmmi.projet",
305                                      string="Projet",
306                                      required=True),
307         "evolutions": fields.one2many("cmmi.evolution",
308                                       "palier_id",
309                                       string="Evolutions"),
310         "phases": fields.one2many("cmmi.axes.palier.phase",
311                                   "palier_id",
312                                   string="Phases"),
313         "charge_init": fields.function(_get_charge_init,
314                                        type="integer",
315                                        string="Charge initiale"),
316         "charge_plan": fields.function(_get_charge_plan,
317                                        type="integer",
318                                        string="Charge plannifiée"),
319         "charge_reel": fields.function(_get_charge_reel,
320                                        type="integer",
321                                        string="Charge réelle"),
322     }
323
324     _defaults = {
325         "type_palier": "normal",
326     }
327
328
329     def create(self, cr, uid, vals, context=None):
330         palier_id = osv.Model.create(self, cr, uid, vals, context=context)
331
332         # Récupération des ids de toutes les phases
333         phase_model = self.pool.get("cmmi.projet.phase")
334         phases_ids = phase_model.search(cr, uid, [('selectionne', '=', True)])
335
336         palier_model = self.pool.get("cmmi.axes.palier")
337         palier = palier_model.read(cr, uid, palier_id, ['date_plan_deb', 'date_plan_fin'])
338
339         # Création des PalierPhase
340         palier_phase_model = self.pool.get("cmmi.axes.palier.phase")
341         for phase_id in phases_ids:
342             palier_phase_model.create(
343                 cr,
344                 uid,
345                 {
346                     'phase_id': phase_id,
347                     'palier_id': palier_id,
348                     'date_plan_deb': palier['date_plan_deb'],
349                     'date_plan_fin': palier['date_plan_fin'],
350                 }
351             )
352         return palier_id
353
354
355
356 # =============================== PALIER-PHASE ============================== #
357 class PalierPhase(osv.Model):
358     _name = "cmmi.axes.palier.phase"
359
360     _description = "Phase d'un palier"
361
362     _inherit = "cmmi.axes.mesurable"
363
364     def _get_name(self, cr, uid, ids, field_name=None, arg=None, context=None):
365         if isinstance(ids, (int, long)):
366             ids = [ids]
367         return dict([(i, r.phase_id.name) for i, r in
368                 zip(ids, self.browse(cr, uid, ids, context=context))])
369
370
371     def _get_charge_init(self, cr, uid, ids, field, arg, context=None):
372         result = {}
373         for pp in self.browse(cr, uid, ids, context=context):
374             result[pp.id] = sum([p.charge_init for p in pp.phases])
375         return result
376
377
378     def _get_charge_plan(self, cr, uid, ids, field, arg, context=None):
379         result = {}
380         for pp in self.browse(cr, uid, ids, context=context):
381             result[pp.id] = sum([p.charge_plan for p in pp.phases])
382         return result
383
384     def _get_charge_reel(self, cr, uid, ids, field, arg, context=None):
385         result = {}
386         for pp in self.browse(cr, uid, ids, context=context):
387             result[pp.id] = sum([c.quantite for c in pp.charges])
388         return result
389
390
391     _columns = {
392         "name": fields.function(_get_name,
393                                 type='char',
394                                 store=True,
395                                 string="Nom de la phase"),
396         "phase_id": fields.many2one("cmmi.projet.phase",
397                                     string="Phase du projet"),
398         "palier_id": fields.many2one("cmmi.axes.palier",
399                                      string="Palier"),
400         "charge_init": fields.function(_get_charge_init,
401                                        type="integer",
402                                        string="Charge initiale"),
403         "charge_plan": fields.function(_get_charge_plan,
404                                        type="integer",
405                                        string="Charge plannifiée"),
406         "charge_reel": fields.function(_get_charge_reel,
407                                        type="integer",
408                                        string="Charge plannifiée"),
409         # backrefs
410         "charges": fields.one2many("cmmi.evolution.charge",
411                                    "phase_id",
412                                    string="Charges"),
413         "phases": fields.one2many("cmmi.evolution.phase",
414                                   "phase_id",
415                                   string="Phases"),
416 #        "evolutions": fields.one2many("cmmi.evolution", #Supprimé !
417 #                                      "phase_id",
418 #                                      string="Evolutions"),
419     }
420
421     def create(self, cr, uid, vals, context=None):
422         # TODO: gérer la création d'une phase de palier.
423         # Vérifier les valeurs contenues dans vals et les modifier / rajouter si nécessaire selon les cas suivants
424
425         # Si description est vide, alors par défaut, recopie de la description du palier et de la phase (concaténés avec un retour à la ligne entre les deux).
426         # Si commentaire est vide, alors par défaut, recopie du commentaire du palier.
427         # Si version est vide, alors par dégaut, recopie de la version du palier.
428
429         return osv.Model.create(self, cr, uid, vals, context=context)
430
431
432 # ================================ CHANTIER ================================= #
433 class Chantier(osv.Model):
434     _name = "cmmi.axes.chantier"
435
436     _description = "Chantiers d'un projet."
437
438     _inherit = "cmmi.axes.mesurable"
439
440
441     def _get_charge_init(self, cr, uid, ids, field, arg, context=None):
442         result = {}
443         for chantier in self.browse(cr, uid, ids, context=context):
444             result[chantier.id] = sum([e.charge_init for e in chantier.evolutions])
445         return result
446
447
448     def _get_charge_plan(self, cr, uid, ids, field, arg, context=None):
449         result = {}
450         for chantier in self.browse(cr, uid, ids, context=context):
451             result[chantier.id] = sum([e.charge_plan for e in chantier.evolutions])
452         return result
453
454
455     def _get_charge_reel(self, cr, uid, ids, field, arg, context=None):
456         result = {}
457         for c in self.browse(cr, uid, ids, context=context):
458             result[c.id] = sum([e.charge_reel for e in c.evolutions])
459         return result
460
461
462     _columns = {
463         "projet_id": fields.many2one("cmmi.projet",
464                                      string="Projet",
465                                      required=True),
466         "module_ids": fields.many2many("cmmi.description.module",
467                                        "cmmi_module_chantier_rel",
468                                        "chantier_id",
469                                        "module_id",
470                                        "Modules"),
471         "evolutions": fields.one2many("cmmi.evolution",
472                                       "chantier_id",
473                                       string="Evolutions"),
474         "charge_init": fields.function(_get_charge_init,
475                                        type="integer",
476                                        string="Charge initiale"),
477         "charge_plan": fields.function(_get_charge_plan,
478                                        type="integer",
479                                        string="Charge plannifiée"),
480         "charge_reel": fields.function(_get_charge_reel,
481                                        type="integer",
482                                        string="Charge réelle"),
483     }