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