merge dev
[auf_rh_dae.git] / project / rh / models.py
1 # -=- encoding: utf-8 -=-
2
3 from django.core.files.storage import FileSystemStorage
4 from django.db import models
5 from django.conf import settings
6 from auf.django.metadata.models import AUFMetadata
7 from auf.django.metadata.managers import NoDeleteManager
8 import datamaster_modeles.models as ref
9 from validators import validate_date_passee
10
11 # Constantes
12 HELP_TEXT_DATE = "format: aaaa-mm-jj"
13 REGIME_TRAVAIL_DEFAULT = 100.00
14 REGIME_TRAVAIL_NB_HEURE_SEMAINE_DEFAULT = 35.00
15
16
17 # Upload de fichiers
18 storage_prive = FileSystemStorage(settings.PRIVE_MEDIA_ROOT,
19 base_url=settings.PRIVE_MEDIA_URL)
20
21 def poste_piece_dispatch(instance, filename):
22 path = "poste/%s/%s" % (instance.poste_id, filename)
23 return path
24
25 def dossier_piece_dispatch(instance, filename):
26 path = "dossier/%s/%s" % (instance.dossier_id, filename)
27 return path
28
29
30 class Commentaire(AUFMetadata):
31 texte = models.TextField()
32 owner = models.ForeignKey('auth.User', db_column='owner', related_name='+')
33
34 class Meta:
35 abstract = True
36 ordering = ['-date_creation']
37
38 def __unicode__(self):
39 return u'%s' % (self.texte)
40
41
42 ### POSTE
43
44 POSTE_APPEL_CHOICES = (
45 ('interne', 'Interne'),
46 ('externe', 'Externe'),
47 )
48
49 class PosteManager(NoDeleteManager):
50 def get_query_set(self):
51 return super(PosteManager, self).get_query_set().select_related('implantation')
52
53 class Poste_(AUFMetadata):
54 """Un Poste est un emploi (job) à combler dans une implantation.
55 Un Poste peut être comblé par un Employe, auquel cas un Dossier est créé.
56 Si on veut recruter 2 jardiniers, 2 Postes distincts existent.
57 """
58
59 objects = PosteManager()
60
61 # Identification
62 nom = models.CharField(max_length=255,
63 verbose_name = u"Titre du poste", )
64 nom_feminin = models.CharField(max_length=255,
65 verbose_name = u"Titre du poste (au féminin)",
66 null=True)
67 implantation = models.ForeignKey(ref.Implantation,
68 db_column='implantation', related_name='+')
69 type_poste = models.ForeignKey('TypePoste', db_column='type_poste',
70 related_name='+',
71 null=True)
72 service = models.ForeignKey('Service', db_column='service', null=True,
73 related_name='+',
74 verbose_name = u"Direction/Service/Pôle support",
75 default=1) # default = Rectorat
76 responsable = models.ForeignKey('Poste', db_column='responsable',
77 related_name='+', null=True,
78 verbose_name = u"Poste du responsable",
79 default=149) # default = Recteur
80
81 # Contrat
82 regime_travail = models.DecimalField(max_digits=12, decimal_places=2,
83 default=REGIME_TRAVAIL_DEFAULT, null=True,
84 verbose_name = u"Temps de travail",
85 help_text="% du temps complet")
86 regime_travail_nb_heure_semaine = models.DecimalField(max_digits=12,
87 decimal_places=2, null=True,
88 default=REGIME_TRAVAIL_NB_HEURE_SEMAINE_DEFAULT,
89 verbose_name = u"Nb. heures par semaine")
90
91 # Recrutement
92 local = models.NullBooleanField(verbose_name = u"Local", default=True,
93 null=True, blank=True)
94 expatrie = models.NullBooleanField(verbose_name = u"Expatrié", default=False,
95 null=True, blank=True)
96 mise_a_disposition = models.NullBooleanField(
97 verbose_name = u"Mise à disposition",
98 null=True, default=False)
99 appel = models.CharField(max_length=10, null=True,
100 verbose_name = u"Appel à candidature",
101 choices=POSTE_APPEL_CHOICES,
102 default='interne')
103
104 # Rémunération
105 classement_min = models.ForeignKey('Classement',
106 db_column='classement_min', related_name='+',
107 null=True, blank=True)
108 classement_max = models.ForeignKey('Classement',
109 db_column='classement_max', related_name='+',
110 null=True, blank=True)
111 valeur_point_min = models.ForeignKey('ValeurPoint',
112 db_column='valeur_point_min', related_name='+',
113 null=True, blank=True)
114 valeur_point_max = models.ForeignKey('ValeurPoint',
115 db_column='valeur_point_max', related_name='+',
116 null=True, blank=True)
117 devise_min = models.ForeignKey('Devise', db_column='devise_min', null=True,
118 related_name='+', default=5)
119 devise_max = models.ForeignKey('Devise', db_column='devise_max', null=True,
120 related_name='+', default=5)
121 salaire_min = models.DecimalField(max_digits=12, decimal_places=2,
122 null=True, default=0)
123 salaire_max = models.DecimalField(max_digits=12, decimal_places=2,
124 null=True, default=0)
125 indemn_min = models.DecimalField(max_digits=12, decimal_places=2,
126 null=True, default=0)
127 indemn_max = models.DecimalField(max_digits=12, decimal_places=2,
128 null=True, default=0)
129 autre_min = models.DecimalField(max_digits=12, decimal_places=2,
130 null=True, default=0)
131 autre_max = models.DecimalField(max_digits=12, decimal_places=2,
132 null=True, default=0)
133
134 # Comparatifs de rémunération
135 devise_comparaison = models.ForeignKey('Devise', null=True,
136 db_column='devise_comparaison',
137 related_name='+',
138 default=5)
139 comp_locale_min = models.DecimalField(max_digits=12, decimal_places=2,
140 null=True, blank=True)
141 comp_locale_max = models.DecimalField(max_digits=12, decimal_places=2,
142 null=True, blank=True)
143 comp_universite_min = models.DecimalField(max_digits=12, decimal_places=2,
144 null=True, blank=True)
145 comp_universite_max = models.DecimalField(max_digits=12, decimal_places=2,
146 null=True, blank=True)
147 comp_fonctionpub_min = models.DecimalField(max_digits=12, decimal_places=2,
148 null=True, blank=True)
149 comp_fonctionpub_max = models.DecimalField(max_digits=12, decimal_places=2,
150 null=True, blank=True)
151 comp_ong_min = models.DecimalField(max_digits=12, decimal_places=2,
152 null=True, blank=True)
153 comp_ong_max = models.DecimalField(max_digits=12, decimal_places=2,
154 null=True, blank=True)
155 comp_autre_min = models.DecimalField(max_digits=12, decimal_places=2,
156 null=True, blank=True)
157 comp_autre_max = models.DecimalField(max_digits=12, decimal_places=2,
158 null=True, blank=True)
159
160 # Justification
161 justification = models.TextField(null=True, blank=True)
162
163 # Autres Metadata
164 date_validation = models.DateTimeField(null=True, blank=True) # de dae
165 date_debut = models.DateField(verbose_name="Date de début",
166 help_text=HELP_TEXT_DATE,
167 null=True, blank=True)
168 date_fin = models.DateField(verbose_name="Date de fin",
169 help_text=HELP_TEXT_DATE,
170 null=True, blank=True)
171
172 class Meta:
173 abstract = True
174 ordering = ['implantation__nom', 'nom']
175 verbose_name = u"Poste"
176 verbose_name_plural = u"Postes"
177
178 def __unicode__(self):
179 representation = u'%s - %s [%s]' % (self.implantation, self.nom,
180 self.id)
181 if self.is_vacant():
182 representation = representation + u' (vacant)'
183 return representation
184
185 def is_vacant(self):
186 # TODO : si existe un dossier actif pour ce poste, return False
187 # self.dossier_set.all() fonctionne pas
188 return False
189
190
191 class Poste(Poste_):
192 __doc__ = Poste_.__doc__
193
194
195 class Poste(Poste_):
196 __doc__ = Poste_.__doc__
197
198
199 POSTE_FINANCEMENT_CHOICES = (
200 ('A', 'A - Frais de personnel'),
201 ('B', 'B - Projet(s)-Titre(s)'),
202 ('C', 'C - Autre')
203 )
204
205
206 class PosteFinancement_(models.Model):
207 """Pour un Poste, structure d'informations décrivant comment on prévoit
208 financer ce Poste.
209 """
210 poste = models.ForeignKey('Poste', db_column='poste',
211 related_name='%(app_label)s_financements')
212 type = models.CharField(max_length=1, choices=POSTE_FINANCEMENT_CHOICES)
213 pourcentage = models.DecimalField(max_digits=12, decimal_places=2,
214 help_text="ex.: 33.33 % (décimale avec point)")
215 commentaire = models.TextField(
216 help_text="Spécifiez la source de financement.")
217
218 class Meta:
219 abstract = True
220 ordering = ['type']
221
222 def __unicode__(self):
223 return u'%s : %s %' % (self.type, self.pourcentage)
224
225
226 class PosteFinancement(PosteFinancement_):
227 __doc__ = PosteFinancement_.__doc__
228
229
230 class PostePiece(models.Model):
231 """Documents relatifs au Poste.
232 Ex.: Description de poste
233 """
234 poste = models.ForeignKey('Poste', db_column='poste',
235 related_name='pieces')
236 nom = models.CharField(verbose_name = u"Nom", max_length=255)
237 fichier = models.FileField(verbose_name = u"Fichier",
238 upload_to=poste_piece_dispatch,
239 storage=storage_prive)
240
241 class Meta:
242 ordering = ['nom']
243
244 def __unicode__(self):
245 return u'%s' % (self.nom)
246
247 class PosteComparaison(models.Model):
248 """
249 De la même manière qu'un dossier, un poste peut-être comparé à un autre poste.
250 """
251 poste = models.ForeignKey('Poste', related_name='comparaisons_internes')
252 implantation = models.ForeignKey(ref.Implantation, null=True, blank=True, related_name="+")
253 nom = models.CharField(verbose_name = u"Poste", max_length=255, null=True, blank=True)
254 montant = models.IntegerField(null=True)
255 devise = models.ForeignKey("Devise", default=5, related_name='+', null=True, blank=True)
256
257 def taux_devise(self):
258 liste_taux = self.devise.tauxchange_set.order_by('-annee').filter(implantation=self.implantation)
259 if len(liste_taux) == 0:
260 raise Exception(u"La devise %s n'a pas de taux pour l'implantation %s" % (self.devise, self.implantation))
261 else:
262 return liste_taux[0].taux
263
264 def montant_euros(self):
265 return round(float(self.montant) * float(self.taux_devise()), 2)
266
267
268 class PosteCommentaire(Commentaire):
269 poste = models.ForeignKey('Poste', db_column='poste', related_name='+')
270
271
272 ### EMPLOYÉ/PERSONNE
273
274 GENRE_CHOICES = (
275 ('M', 'Homme'),
276 ('F', 'Femme'),
277 )
278 SITUATION_CHOICES = (
279 ('C', 'Célibataire'),
280 ('F', 'Fiancé'),
281 ('M', 'Marié'),
282 )
283
284 class Employe(AUFMetadata):
285 """Personne occupant ou ayant occupé un Poste. Un Employe aura autant de
286 Dossiers qu'il occupe ou a occupé de Postes.
287
288 Cette classe aurait pu avantageusement s'appeler Personne car la notion
289 d'employé n'a pas de sens si aucun Dossier n'existe pour une personne.
290 """
291 # Identification
292 nom = models.CharField(max_length=255)
293 prenom = models.CharField(max_length=255, verbose_name = u"Prénom")
294 nom_affichage = models.CharField(max_length=255,
295 verbose_name = u"Nom d'affichage",
296 null=True, blank=True)
297 nationalite = models.ForeignKey(ref.Pays, to_field='code',
298 db_column='nationalite',
299 related_name='employes_nationalite',
300 verbose_name = u"Nationalité")
301 date_naissance = models.DateField(help_text=HELP_TEXT_DATE,
302 verbose_name = u"Date de naissance",
303 validators=[validate_date_passee],
304 null=True, blank=True)
305 genre = models.CharField(max_length=1, choices=GENRE_CHOICES)
306
307 # Infos personnelles
308 situation_famille = models.CharField(max_length=1,
309 choices=SITUATION_CHOICES,
310 verbose_name = u"Situation familiale",
311 null=True, blank=True)
312 date_entree = models.DateField(verbose_name = u"Date d'entrée à l'AUF",
313 help_text=HELP_TEXT_DATE,
314 null=True, blank=True)
315
316 # Coordonnées
317 tel_domicile = models.CharField(max_length=255,
318 verbose_name = u"Tél. domicile",
319 null=True, blank=True)
320 tel_cellulaire = models.CharField(max_length=255,
321 verbose_name = u"Tél. cellulaire",
322 null=True, blank=True)
323 adresse = models.CharField(max_length=255, null=True, blank=True)
324 ville = models.CharField(max_length=255, null=True, blank=True)
325 province = models.CharField(max_length=255, null=True, blank=True)
326 code_postal = models.CharField(max_length=255, null=True, blank=True)
327 pays = models.ForeignKey(ref.Pays, to_field='code', db_column='pays',
328 related_name='employes',
329 null=True, blank=True)
330
331 class Meta:
332 ordering = ['nom_affichage','nom','prenom']
333 verbose_name = u"Employé"
334 verbose_name_plural = u"Employés"
335
336 def __unicode__(self):
337 return u'%s' % (self.get_nom())
338
339 def get_nom(self):
340 nom_affichage = self.nom_affichage
341 if not nom_affichage:
342 nom_affichage = u'%s %s' % (self.nom.upper(), self.prenom)
343 return nom_affichage
344
345 def civilite(self):
346 civilite = u''
347 if self.genre.upper() == u'M':
348 civilite = u'M.'
349 elif self.genre.upper() == u'F':
350 civilite = u'Mme'
351 return civilite
352
353 class EmployePiece(models.Model):
354 """Documents relatifs à un employé.
355 Ex.: CV...
356 """
357 employe = models.ForeignKey('Employe', db_column='employe',
358 related_name='+')
359 nom = models.CharField(verbose_name = u"Nom", max_length=255)
360 fichier = models.FileField(verbose_name = u"Fichier",
361 upload_to=dossier_piece_dispatch,
362 storage=storage_prive)
363
364 class Meta:
365 ordering = ['nom']
366
367 def __unicode__(self):
368 return u'%s' % (self.nom)
369
370 class EmployeCommentaire(Commentaire):
371 employe = models.ForeignKey('Employe', db_column='employe',
372 related_name='+')
373
374
375 LIEN_PARENTE_CHOICES = (
376 ('Conjoint', 'Conjoint'),
377 ('Conjointe', 'Conjointe'),
378 ('Fille', 'Fille'),
379 ('Fils', 'Fils'),
380 )
381
382 class AyantDroit(AUFMetadata):
383 """Personne en relation avec un Employe.
384 """
385 # Identification
386 nom = models.CharField(max_length=255)
387 prenom = models.CharField(max_length=255,
388 verbose_name = u"Prénom",)
389 nom_affichage = models.CharField(max_length=255,
390 verbose_name = u"Nom d'affichage",
391 null=True, blank=True)
392 nationalite = models.ForeignKey(ref.Pays, to_field='code',
393 db_column='nationalite',
394 related_name='ayantdroits_nationalite',
395 verbose_name = u"Nationalité")
396 date_naissance = models.DateField(help_text=HELP_TEXT_DATE,
397 verbose_name = u"Date de naissance",
398 validators=[validate_date_passee],
399 null=True, blank=True)
400 genre = models.CharField(max_length=1, choices=GENRE_CHOICES)
401
402 # Relation
403 employe = models.ForeignKey('Employe', db_column='employe',
404 related_name='ayantdroits',
405 verbose_name = u"Employé")
406 lien_parente = models.CharField(max_length=10,
407 choices=LIEN_PARENTE_CHOICES,
408 verbose_name = u"Lien de parenté",
409 null=True, blank=True)
410
411 class Meta:
412 ordering = ['nom_affichage']
413 verbose_name = u"Ayant droit"
414 verbose_name_plural = u"Ayants droit"
415
416 def __unicode__(self):
417 return u'%s' % (self.get_nom())
418
419 def get_nom(self):
420 nom_affichage = self.nom_affichage
421 if not nom_affichage:
422 nom_affichage = u'%s %s' % (self.nom.upper(), self.prenom)
423 return nom_affichage
424
425 class AyantDroitCommentaire(Commentaire):
426 ayant_droit = models.ForeignKey('AyantDroit', db_column='ayant_droit',
427 related_name='+')
428
429
430 ### DOSSIER
431
432 STATUT_RESIDENCE_CHOICES = (
433 ('local', 'Local'),
434 ('expat', 'Expatrié'),
435 )
436
437 COMPTE_COMPTA_CHOICES = (
438 ('coda', 'CODA'),
439 ('scs', 'SCS'),
440 ('aucun', 'Aucun'),
441 )
442
443 class Dossier_(AUFMetadata):
444 """Le Dossier regroupe les informations relatives à l'occupation
445 d'un Poste par un Employe. Un seul Dossier existe par Poste occupé
446 par un Employe.
447
448 Plusieurs Contrats peuvent être associés au Dossier.
449 Une structure de Remuneration est rattachée au Dossier. Un Poste pour
450 lequel aucun Dossier n'existe est un poste vacant.
451 """
452 # Identification
453 employe = models.ForeignKey('Employe', db_column='employe',
454 related_name='dossiers',
455 verbose_name=u"Employé")
456 poste = models.ForeignKey('Poste', db_column='poste', related_name='+')
457 statut = models.ForeignKey('Statut', related_name='+', default=3,
458 null=True)
459 organisme_bstg = models.ForeignKey('OrganismeBstg',
460 db_column='organisme_bstg',
461 related_name='+',
462 verbose_name = u"Organisme",
463 help_text="Si détaché (DET) ou \
464 mis à disposition (MAD), \
465 préciser l'organisme.",
466 null=True, blank=True)
467
468 # Recrutement
469 remplacement = models.BooleanField(default=False)
470 statut_residence = models.CharField(max_length=10, default='local',
471 verbose_name = u"Statut", null=True,
472 choices=STATUT_RESIDENCE_CHOICES)
473
474 # Rémunération
475 classement = models.ForeignKey('Classement', db_column='classement',
476 related_name='+',
477 null=True, blank=True)
478 regime_travail = models.DecimalField(max_digits=12, null=True,
479 decimal_places=2,
480 default=REGIME_TRAVAIL_DEFAULT,
481 verbose_name = u"Régime de travail",
482 help_text="% du temps complet")
483 regime_travail_nb_heure_semaine = models.DecimalField(max_digits=12,
484 decimal_places=2, null=True,
485 default=REGIME_TRAVAIL_NB_HEURE_SEMAINE_DEFAULT,
486 verbose_name = u"Nb. heures par semaine")
487
488 # Occupation du Poste par cet Employe (anciennement "mandat")
489 date_debut = models.DateField(verbose_name = u"Date de début d'occupation \
490 de poste",
491 help_text=HELP_TEXT_DATE)
492 date_fin = models.DateField(verbose_name = u"Date de fin d'occupation \
493 de poste",
494 help_text=HELP_TEXT_DATE,
495 null=True, blank=True)
496
497 # Comptes
498 # TODO?
499
500 class Meta:
501 abstract = True
502 ordering = ['employe__nom', ]
503 verbose_name = "Dossier"
504 verbose_name_plural = "Dossiers"
505
506 def __unicode__(self):
507 poste = self.poste.nom
508 if self.employe.genre == 'F':
509 poste = self.poste.nom_feminin
510 return u'%s - %s' % (self.employe, poste)
511
512
513 class Dossier(Dossier_):
514 __doc__ = Dossier_.__doc__
515
516
517 class DossierPiece(models.Model):
518 """Documents relatifs au Dossier (à l'occupation de ce poste par employé).
519 Ex.: Lettre de motivation.
520 """
521 dossier = models.ForeignKey('Dossier', db_column='dossier',
522 related_name='+')
523 nom = models.CharField(verbose_name = u"Nom", max_length=255)
524 fichier = models.FileField(verbose_name = u"Fichier",
525 upload_to=dossier_piece_dispatch,
526 storage=storage_prive)
527
528 class Meta:
529 ordering = ['nom']
530
531 def __unicode__(self):
532 return u'%s' % (self.nom)
533
534 class DossierCommentaire(Commentaire):
535 dossier = models.ForeignKey('Dossier', db_column='dossier',
536 related_name='+')
537
538 class DossierComparaison(models.Model):
539 """
540 Photo d'une comparaison salariale au moment de l'embauche.
541 """
542 dossier = models.ForeignKey('Dossier', related_name='comparaisons')
543 implantation = models.ForeignKey(ref.Implantation, related_name="+", null=True, blank=True)
544 poste = models.CharField(max_length=255, null=True, blank=True)
545 personne = models.CharField(max_length=255, null=True, blank=True)
546 montant = models.IntegerField(null=True)
547 devise = models.ForeignKey('Devise', default=5, related_name='+', null=True, blank=True)
548
549 def taux_devise(self):
550 liste_taux = self.devise.tauxchange_set.order_by('-annee').filter(implantation=self.dossier.poste.implantation)
551 if len(liste_taux) == 0:
552 raise Exception(u"La devise %s n'a pas de taux pour l'implantation %s" % (self.devise, self.dossier.poste.implantation))
553 else:
554 return liste_taux[0].taux
555
556 def montant_euros(self):
557 return round(float(self.montant) * float(self.taux_devise()), 2)
558
559
560 ### RÉMUNÉRATION
561
562 class RemunerationMixin(AUFMetadata):
563 # Identification
564 dossier = models.ForeignKey('Dossier', db_column='dossier',
565 related_name='%(app_label)s_%(class)s_remunerations')
566 type = models.ForeignKey('TypeRemuneration', db_column='type',
567 related_name='+',
568 verbose_name = u"Type de rémunération")
569 type_revalorisation = models.ForeignKey('TypeRevalorisation',
570 db_column='type_revalorisation',
571 related_name='+',
572 verbose_name = u"Type de revalorisation",
573 null=True, blank=True)
574 montant = models.FloatField(null=True, blank=True,
575 default=0)
576 # Annuel (12 mois, 52 semaines, 364 jours?)
577 devise = models.ForeignKey('Devise', to_field='id',
578 db_column='devise', related_name='+',
579 default=5)
580 # commentaire = precision
581 commentaire = models.CharField(max_length=255, null=True, blank=True)
582 # date_debut = anciennement date_effectif
583 date_debut = models.DateField(help_text=HELP_TEXT_DATE,
584 verbose_name = u"Date de début",
585 null=True, blank=True)
586 date_fin = models.DateField(help_text=HELP_TEXT_DATE,
587 verbose_name = u"Date de fin",
588 null=True, blank=True)
589
590 class Meta:
591 abstract = True
592 ordering = ['type__nom', '-date_fin']
593
594 def __unicode__(self):
595 return u'%s %s (%s)' % (self.montant, self.devise.code, self.type.nom)
596
597 class Remuneration_(RemunerationMixin):
598 """Structure de rémunération (données budgétaires) en situation normale
599 pour un Dossier. Si un Evenement existe, utiliser la structure de
600 rémunération EvenementRemuneration de cet événement.
601 """
602
603 def montant_mois(self):
604 return round(self.montant / 12, 2)
605
606 def taux_devise(self):
607 return self.devise.tauxchange_set.order_by('-annee').all()[0].taux
608
609 def montant_euro(self):
610 return round(float(self.montant) / float(self.taux_devise()), 2)
611
612 def montant_euro_mois(self):
613 return round(self.montant_euro() / 12, 2)
614
615 def __unicode__(self):
616 try:
617 devise = self.devise.code
618 except:
619 devise = "???"
620 return "%s %s" % (self.montant, devise)
621
622 class Meta:
623 abstract = True
624 verbose_name = u"Rémunération"
625 verbose_name_plural = u"Rémunérations"
626
627
628 class Remuneration(Remuneration_):
629 __doc__ = Remuneration_.__doc__
630
631
632 ### CONTRATS
633
634 class ContratManager(NoDeleteManager):
635 def get_query_set(self):
636 return super(ContratManager, self).get_query_set().select_related('dossier', 'dossier__poste')
637
638
639 class Contrat(AUFMetadata):
640 """Document juridique qui encadre la relation de travail d'un Employe
641 pour un Poste particulier. Pour un Dossier (qui documente cette
642 relation de travail) plusieurs contrats peuvent être associés.
643 """
644
645 objects = ContratManager()
646
647 dossier = models.ForeignKey('Dossier', db_column='dossier',
648 related_name='+')
649 type_contrat = models.ForeignKey('TypeContrat', db_column='type_contrat',
650 related_name='+',
651 verbose_name = u"Type de contrat")
652 date_debut = models.DateField(help_text=HELP_TEXT_DATE,
653 verbose_name = u"Date de début")
654 date_fin = models.DateField(help_text=HELP_TEXT_DATE,
655 verbose_name = u"Date de fin",
656 null=True, blank=True)
657
658 class Meta:
659 ordering = ['dossier__employe__nom_affichage']
660 verbose_name = u"Contrat"
661 verbose_name_plural = u"Contrats"
662
663 def __unicode__(self):
664 return u'%s - %s' % (self.dossier, self.id)
665
666 # TODO? class ContratPiece(models.Model):
667
668
669 ### ÉVÉNEMENTS
670
671 class Evenement_(AUFMetadata):
672 """Un Evenement sert à déclarer une situation temporaire (exceptionnelle)
673 d'un Dossier qui vient altérer des informations normales liées à un Dossier
674 (ex.: la Remuneration).
675
676 Ex.: congé de maternité, maladie...
677
678 Lors de ces situations exceptionnelles, l'Employe a un régime de travail
679 différent et une rémunération en conséquence. On souhaite toutefois
680 conserver le Dossier intact afin d'éviter une re-saisie des données lors
681 du retour à la normale.
682 """
683 dossier = models.ForeignKey('Dossier', db_column='dossier',
684 related_name='+')
685 nom = models.CharField(max_length=255)
686 date_debut = models.DateField(help_text=HELP_TEXT_DATE,
687 verbose_name = u"Date de début")
688 date_fin = models.DateField(help_text=HELP_TEXT_DATE,
689 verbose_name = u"Date de fin",
690 null=True, blank=True)
691
692 class Meta:
693 abstract = True
694 ordering = ['nom']
695 verbose_name = u"Évènement"
696 verbose_name_plural = u"Évènements"
697
698 def __unicode__(self):
699 return u'%s' % (self.nom)
700
701
702 class Evenement(Evenement_):
703 __doc__ = Evenement_.__doc__
704
705
706 class EvenementRemuneration_(RemunerationMixin):
707 """Structure de rémunération liée à un Evenement qui remplace
708 temporairement la Remuneration normale d'un Dossier, pour toute la durée
709 de l'Evenement.
710 """
711 evenement = models.ForeignKey("Evenement", db_column='evenement',
712 related_name='+',
713 verbose_name = u"Évènement")
714 # TODO : le champ dossier hérité de Remuneration doit être dérivé
715 # de l'Evenement associé
716
717 class Meta:
718 abstract = True
719 ordering = ['evenement', 'type__nom', '-date_fin']
720 verbose_name = u"Évènement - rémunération"
721 verbose_name_plural = u"Évènements - rémunérations"
722
723
724 class EvenementRemuneration(EvenementRemuneration_):
725 __doc__ = EvenementRemuneration_.__doc__
726
727 class Meta:
728 abstract = True
729
730
731 class EvenementRemuneration(EvenementRemuneration_):
732 __doc__ = EvenementRemuneration_.__doc__
733
734
735 ### RÉFÉRENCES RH
736
737 class FamilleEmploi(AUFMetadata):
738 """Catégorie utilisée dans la gestion des Postes.
739 Catégorie supérieure à TypePoste.
740 """
741 nom = models.CharField(max_length=255)
742
743 class Meta:
744 ordering = ['nom']
745 verbose_name = u"Famille d'emploi"
746 verbose_name_plural = u"Familles d'emploi"
747
748 def __unicode__(self):
749 return u'%s' % (self.nom)
750
751 class TypePoste(AUFMetadata):
752 """Catégorie de Poste.
753 """
754 nom = models.CharField(max_length=255)
755 nom_feminin = models.CharField(max_length=255,
756 verbose_name = u"Nom féminin")
757
758 is_responsable = models.BooleanField(default=False,
759 verbose_name = u"Poste de responsabilité")
760 famille_emploi = models.ForeignKey('FamilleEmploi',
761 db_column='famille_emploi',
762 related_name='+',
763 verbose_name = u"Famille d'emploi")
764
765 class Meta:
766 ordering = ['nom']
767 verbose_name = u"Type de poste"
768 verbose_name_plural = u"Types de poste"
769
770 def __unicode__(self):
771 return u'%s' % (self.nom)
772
773
774 TYPE_PAIEMENT_CHOICES = (
775 ('Régulier', 'Régulier'),
776 ('Ponctuel', 'Ponctuel'),
777 )
778
779 NATURE_REMUNERATION_CHOICES = (
780 ('Accessoire', 'Accessoire'),
781 ('Charges', 'Charges'),
782 ('Indemnité', 'Indemnité'),
783 ('RAS', 'Rémunération autre source'),
784 ('Traitement', 'Traitement'),
785 )
786
787 class TypeRemuneration(AUFMetadata):
788 """Catégorie de Remuneration.
789 """
790 nom = models.CharField(max_length=255)
791 type_paiement = models.CharField(max_length=30,
792 choices=TYPE_PAIEMENT_CHOICES,
793 verbose_name = u"Type de paiement")
794 nature_remuneration = models.CharField(max_length=30,
795 choices=NATURE_REMUNERATION_CHOICES,
796 verbose_name = u"Nature de la rémunération")
797
798 class Meta:
799 ordering = ['nom']
800 verbose_name = u"Type de rémunération"
801 verbose_name_plural = u"Types de rémunération"
802
803 def __unicode__(self):
804 return u'%s' % (self.nom)
805
806 class TypeRevalorisation(AUFMetadata):
807 """Justification du changement de la Remuneration.
808 (Actuellement utilisé dans aucun traitement informatique.)
809 """
810 nom = models.CharField(max_length=255)
811
812 class Meta:
813 ordering = ['nom']
814 verbose_name = u"Type de revalorisation"
815 verbose_name_plural = u"Types de revalorisation"
816
817 def __unicode__(self):
818 return u'%s' % (self.nom)
819
820 class Service(AUFMetadata):
821 """Unité administrative où les Postes sont rattachés.
822 """
823 nom = models.CharField(max_length=255)
824
825 class Meta:
826 ordering = ['nom']
827 verbose_name = u"Service"
828 verbose_name_plural = u"Services"
829
830 def __unicode__(self):
831 return u'%s' % (self.nom)
832
833
834 TYPE_ORGANISME_CHOICES = (
835 ('MAD', 'Mise à disposition'),
836 ('DET', 'Détachement'),
837 )
838
839 class OrganismeBstg(AUFMetadata):
840 """Organisation d'où provient un Employe mis à disposition (MAD) de
841 ou détaché (DET) à l'AUF à titre gratuit.
842
843 (BSTG = bien et service à titre gratuit.)
844 """
845 nom = models.CharField(max_length=255)
846 type = models.CharField(max_length=10, choices=TYPE_ORGANISME_CHOICES)
847 pays = models.ForeignKey(ref.Pays, to_field='code',
848 db_column='pays',
849 related_name='organismes_bstg',
850 null=True, blank=True)
851
852 class Meta:
853 ordering = ['type', 'nom']
854 verbose_name = u"Organisme BSTG"
855 verbose_name_plural = u"Organismes BSTG"
856
857 def __unicode__(self):
858 return u'%s (%s)' % (self.nom, self.get_type_display())
859
860 class Statut(AUFMetadata):
861 """Statut de l'Employe dans le cadre d'un Dossier particulier.
862 """
863 # Identification
864 code = models.CharField(max_length=25, unique=True)
865 nom = models.CharField(max_length=255)
866
867 class Meta:
868 ordering = ['code']
869 verbose_name = u"Statut d'employé"
870 verbose_name_plural = u"Statuts d'employé"
871
872 def __unicode__(self):
873 return u'%s : %s' % (self.code, self.nom)
874
875
876 TYPE_CLASSEMENT_CHOICES = (
877 ('S', 'S -Soutien'),
878 ('T', 'T - Technicien'),
879 ('P', 'P - Professionel'),
880 ('C', 'C - Cadre'),
881 ('D', 'D - Direction'),
882 ('SO', 'SO - Sans objet [expatriés]'),
883 ('HG', 'HG - Hors grille [direction]'),
884 )
885
886
887 class Classement_(AUFMetadata):
888 """Éléments de classement de la
889 "Grille générique de classement hiérarchique".
890
891 Utile pour connaître, pour un Dossier, le salaire de base théorique lié au
892 classement dans la grille. Le classement donne le coefficient utilisé dans:
893
894 salaire de base = coefficient * valeur du point de l'Implantation du Poste
895 """
896 # Identification
897 type = models.CharField(max_length=10, choices=TYPE_CLASSEMENT_CHOICES)
898 echelon = models.IntegerField(verbose_name = u"Échelon")
899 degre = models.IntegerField(verbose_name = u"Degré")
900 coefficient = models.FloatField(default=0, verbose_name = u"Coéfficient",
901 null=True)
902 # Méta
903 # annee # au lieu de date_debut et date_fin
904 commentaire = models.TextField(null=True, blank=True)
905
906 class Meta:
907 abstract = True
908 ordering = ['type','echelon','degre','coefficient']
909 verbose_name = u"Classement"
910 verbose_name_plural = u"Classements"
911
912 def __unicode__(self):
913 return u'%s.%s.%s (%s)' % (self.type, self.echelon, self.degre,
914 self.coefficient)
915
916 class Classement(Classement_):
917 __doc__ = Classement_.__doc__
918
919
920 class TauxChange_(AUFMetadata):
921 """Taux de change de la devise vers l'euro (EUR)
922 pour chaque année budgétaire.
923 """
924 # Identification
925 devise = models.ForeignKey('Devise', db_column='devise',
926 related_name='+')
927 annee = models.IntegerField(verbose_name = u"Année")
928 taux = models.FloatField(verbose_name = u"Taux vers l'euro")
929
930 class Meta:
931 abstract = True
932 ordering = ['-annee', 'devise__code']
933 verbose_name = u"Taux de change"
934 verbose_name_plural = u"Taux de change"
935
936 def __unicode__(self):
937 return u'%s : %s € (%s)' % (self.devise, self.taux, self.annee)
938
939
940 class TauxChange(TauxChange_):
941 __doc__ = TauxChange_.__doc__
942
943 class ValeurPointManager(NoDeleteManager):
944 def get_query_set(self):
945 return super(ValeurPointManager, self).get_query_set().select_related('devise', 'implantation')
946
947
948 class ValeurPoint_(AUFMetadata):
949 """Utile pour connaître, pour un Dossier, le salaire de base théorique lié
950 au classement dans la grille. La ValeurPoint s'obtient par l'implantation
951 du Poste de ce Dossier : dossier.poste.implantation (pseudo code).
952
953 salaire de base = coefficient * valeur du point de l'Implantation du Poste
954 """
955
956 objects = ValeurPointManager()
957
958 valeur = models.FloatField(null=True)
959 devise = models.ForeignKey('Devise', db_column='devise', null=True,
960 related_name='+', default=5)
961 implantation = models.ForeignKey(ref.Implantation,
962 db_column='implantation',
963 related_name='%(app_label)s_valeur_point')
964 # Méta
965 annee = models.IntegerField()
966
967 class Meta:
968 ordering = ['-annee', 'implantation__nom']
969 abstract = True
970 verbose_name = u"Valeur du point"
971 verbose_name_plural = u"Valeurs du point"
972
973 # TODO : cette fonction n'était pas présente dans la branche dev, utilité?
974 def get_tauxchange_courant(self):
975 """
976 Recherche le taux courant associé à la valeur d'un point.
977 Tous les taux de l'année courante sont chargés, pour optimiser un
978 affichage en liste. (On pourrait probablement améliorer le manager pour
979 lui greffer le taux courant sous forme de JOIN)
980 """
981 for tauxchange in self.tauxchange:
982 if tauxchange.implantation_id == self.implantation_id:
983 return tauxchange
984 return None
985
986 def __unicode__(self):
987 return u'%s %s (%s)' % (self.valeur, self.devise, self.annee)
988
989
990 class ValeurPoint(ValeurPoint_):
991 __doc__ = ValeurPoint_.__doc__
992
993
994 class Devise(AUFMetadata):
995 """Devise monétaire.
996 """
997 code = models.CharField(max_length=10, unique=True)
998 nom = models.CharField(max_length=255)
999
1000 class Meta:
1001 ordering = ['code']
1002 verbose_name = u"Devise"
1003 verbose_name_plural = u"Devises"
1004
1005 def __unicode__(self):
1006 return u'%s - %s' % (self.code, self.nom)
1007
1008 class TypeContrat(AUFMetadata):
1009 """Type de contrat.
1010 """
1011 nom = models.CharField(max_length=255)
1012 nom_long = models.CharField(max_length=255)
1013
1014 class Meta:
1015 ordering = ['nom']
1016 verbose_name = u"Type de contrat"
1017 verbose_name_plural = u"Types de contrat"
1018
1019 def __unicode__(self):
1020 return u'%s' % (self.nom)
1021
1022
1023 ### AUTRES
1024
1025 class ResponsableImplantation(AUFMetadata):
1026 """Le responsable d'une implantation.
1027 Anciennement géré sur le Dossier du responsable.
1028 """
1029 employe = models.ForeignKey('Employe', db_column='employe',
1030 related_name='+',
1031 null=True, blank=True)
1032 implantation = models.ForeignKey(ref.Implantation,
1033 db_column='implantation', related_name='+',
1034 unique=True)
1035
1036 def __unicode__(self):
1037 return u'%s : %s' % (self.implantation, self.employe)
1038
1039 class Meta:
1040 ordering = ['implantation__nom']
1041 verbose_name = u"Responsable d'implantation"
1042 verbose_name_plural = u"Responsables d'implantation"