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