modifié les modèles pour accommoder les données RH pleines de NULL
[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', null=True,
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='+', null=True,
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, null=True,
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, null=True,
105 default=REGIME_TRAVAIL_NB_HEURE_SEMAINE_DEFAULT,
106 verbose_name="Nb. heures par semaine")
107
108 # Recrutement
109 local = models.NullBooleanField(verbose_name="Local", default=True,
110 null=True, blank=True)
111 expatrie = models.NullBooleanField(verbose_name="Expatrié", default=False,
112 null=True, blank=True)
113 mise_a_disposition = models.NullBooleanField(
114 verbose_name="Mise à disposition",
115 null=True, default=False)
116 appel = models.CharField(max_length=10, null=True,
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', null=True,
135 related_name='+', default=5)
136 devise_max = models.ForeignKey('Devise', db_column='devise_max', null=True,
137 related_name='+', default=5)
138 salaire_min = models.DecimalField(max_digits=12, decimal_places=2,
139 null=True, default=0)
140 salaire_max = models.DecimalField(max_digits=12, decimal_places=2,
141 null=True, default=0)
142 indemn_min = models.DecimalField(max_digits=12, decimal_places=2,
143 null=True, default=0)
144 indemn_max = models.DecimalField(max_digits=12, decimal_places=2,
145 null=True, default=0)
146 autre_min = models.DecimalField(max_digits=12, decimal_places=2,
147 null=True, default=0)
148 autre_max = models.DecimalField(max_digits=12, decimal_places=2,
149 null=True, default=0)
150
151 # Comparatifs de rémunération
152 devise_comparaison = models.ForeignKey('Devise', null=True,
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", null=True,
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 null=True)
457 organisme_bstg = models.ForeignKey('OrganismeBstg',
458 db_column='organisme_bstg',
459 related_name='+',
460 verbose_name="Organisme",
461 help_text="Si détaché (DET) ou \
462 mis à disposition (MAD), \
463 préciser l'organisme.",
464 null=True, blank=True)
465
466 # Recrutement
467 remplacement = models.BooleanField(default=False)
468 statut_residence = models.CharField(max_length=10, default='local',
469 verbose_name="Statut", null=True,
470 choices=STATUT_RESIDENCE_CHOICES)
471
472 # Rémunération
473 classement = models.ForeignKey('Classement', db_column='classement',
474 related_name='+',
475 null=True, blank=True)
476 regime_travail = models.DecimalField(max_digits=12, null=True,
477 decimal_places=2,
478 default=REGIME_TRAVAIL_DEFAULT,
479 verbose_name="Régime de travail",
480 help_text="% du temps complet")
481 regime_travail_nb_heure_semaine = models.DecimalField(max_digits=12,
482 decimal_places=2, null=True,
483 default=REGIME_TRAVAIL_NB_HEURE_SEMAINE_DEFAULT,
484 verbose_name="Nb. heures par semaine")
485
486 # Occupation du Poste par cet Employe (anciennement "mandat")
487 date_debut = models.DateField(verbose_name="Date de début d'occupation \
488 de poste",
489 help_text=HELP_TEXT_DATE)
490 date_fin = models.DateField(verbose_name="Date de fin d'occupation \
491 de poste",
492 help_text=HELP_TEXT_DATE,
493 null=True, blank=True)
494
495 # Comptes
496 # TODO?
497
498 class Meta:
499 abstract = True
500 ordering = ['employe__nom_affichage', 'employe__nom', 'poste__nom']
501 verbose_name = "Dossier"
502 verbose_name_plural = "Dossiers"
503
504 def __unicode__(self):
505 poste = self.poste.nom
506 if self.employe.genre == 'F':
507 poste = self.poste.nom_feminin
508 return u'%s - %s' % (self.employe, poste)
509
510
511 class Dossier(Dossier_):
512 __doc__ = Dossier_.__doc__
513
514
515
516 class Dossier(Dossier_):
517 __doc__ = Dossier_.__doc__
518
519
520 class DossierPiece(models.Model):
521 """Documents relatifs au Dossier (à l'occupation de ce poste par employé).
522 Ex.: Lettre de motivation.
523 """
524 dossier = models.ForeignKey('Dossier', db_column='dossier',
525 related_name='+')
526 nom = models.CharField(verbose_name="Nom", max_length=255)
527 fichier = models.FileField(verbose_name="Fichier",
528 upload_to=dossier_piece_dispatch,
529 storage=storage_prive)
530
531 class Meta:
532 ordering = ['nom']
533
534 def __unicode__(self):
535 return u'%s' % (self.nom)
536
537 class DossierCommentaire(Commentaire):
538 dossier = models.ForeignKey('Dossier', db_column='dossier',
539 related_name='+')
540
541
542 ### RÉMUNÉRATION
543
544 class RemunerationMixin(Metadata):
545 # Identification
546 dossier = models.ForeignKey('Dossier', db_column='dossier',
547 related_name='%(app_label)s_%(class)s_remunerations')
548 type = models.ForeignKey('TypeRemuneration', db_column='type',
549 related_name='+',
550 verbose_name="Type de rémunération")
551 type_revalorisation = models.ForeignKey('TypeRevalorisation',
552 db_column='type_revalorisation',
553 related_name='+',
554 verbose_name="Type de revalorisation",
555 null=True, blank=True)
556 montant = models.FloatField(null=True, blank=True,
557 default=0)
558 # Annuel (12 mois, 52 semaines, 364 jours?)
559 devise = models.ForeignKey('Devise', to_field='id',
560 db_column='devise', related_name='+',
561 default=5)
562 # commentaire = precision
563 commentaire = models.CharField(max_length=255, null=True, blank=True)
564 # date_debut = anciennement date_effectif
565 date_debut = models.DateField(help_text=HELP_TEXT_DATE,
566 verbose_name="Date de début",
567 null=True, blank=True)
568 date_fin = models.DateField(help_text=HELP_TEXT_DATE,
569 verbose_name="Date de fin",
570 null=True, blank=True)
571
572 class Meta:
573 abstract = True
574 ordering = ['type__nom', '-date_fin']
575
576 def __unicode__(self):
577 return u'%s %s (%s)' % (self.montant, self.devise.code, self.type.nom)
578
579 class Remuneration_(RemunerationMixin):
580 """Structure de rémunération (données budgétaires) en situation normale
581 pour un Dossier. Si un Evenement existe, utiliser la structure de
582 rémunération EvenementRemuneration de cet événement.
583 """
584
585 def montant_mois(self):
586 return round(self.montant / 12, 2)
587
588 def taux_devise(self):
589 return self.devise.tauxchange_set.order_by('-annee').all()[0].taux
590
591 def montant_euro(self):
592 return round(float(self.montant) / float(self.taux_devise()), 2)
593
594 def montant_euro_mois(self):
595 return round(self.montant_euro() / 12, 2)
596
597 def __unicode__(self):
598 try:
599 devise = self.devise.code
600 except:
601 devise = "???"
602 return "%s %s" % (self.montant, devise)
603
604 class Meta:
605 abstract = True
606 verbose_name = "Rémunération"
607 verbose_name_plural = "Rémunérations"
608
609
610 class Remuneration(Remuneration_):
611 __doc__ = Remuneration_.__doc__
612
613
614 ### CONTRATS
615
616 class Contrat(Metadata):
617 """Document juridique qui encadre la relation de travail d'un Employe
618 pour un Poste particulier. Pour un Dossier (qui documente cette
619 relation de travail) plusieurs contrats peuvent être associés.
620 """
621 dossier = models.ForeignKey('Dossier', db_column='dossier',
622 related_name='+')
623 type_contrat = models.ForeignKey('TypeContrat', db_column='type_contrat',
624 related_name='+',
625 verbose_name="Type de contrat")
626 date_debut = models.DateField(help_text=HELP_TEXT_DATE,
627 verbose_name="Date de début")
628 date_fin = models.DateField(help_text=HELP_TEXT_DATE,
629 verbose_name="Date de fin",
630 null=True, blank=True)
631
632 class Meta:
633 ordering = ['dossier__employe__nom_affichage']
634 verbose_name = "Contrat"
635 verbose_name_plural = "Contrats"
636
637 def __unicode__(self):
638 return u'%s - %s' % (self.dossier, self.id)
639
640 # TODO? class ContratPiece(models.Model):
641
642
643 ### ÉVÉNEMENTS
644
645 class Evenement_(Metadata):
646 """Un Evenement sert à déclarer une situation temporaire (exceptionnelle)
647 d'un Dossier qui vient altérer des informations normales liées à un Dossier
648 (ex.: la Remuneration).
649
650 Ex.: congé de maternité, maladie...
651
652 Lors de ces situations exceptionnelles, l'Employe a un régime de travail
653 différent et une rémunération en conséquence. On souhaite toutefois
654 conserver le Dossier intact afin d'éviter une re-saisie des données lors
655 du retour à la normale.
656 """
657 dossier = models.ForeignKey('Dossier', db_column='dossier',
658 related_name='+')
659 nom = models.CharField(max_length=255)
660 date_debut = models.DateField(help_text=HELP_TEXT_DATE,
661 verbose_name="Date de début")
662 date_fin = models.DateField(help_text=HELP_TEXT_DATE,
663 verbose_name="Date de fin",
664 null=True, blank=True)
665
666 class Meta:
667 abstract = True
668 ordering = ['nom']
669 verbose_name = "Évènement"
670 verbose_name_plural = "Évènements"
671
672 def __unicode__(self):
673 return u'%s' % (self.nom)
674
675
676 class Evenement(Evenement_):
677 __doc__ = Evenement_.__doc__
678
679
680 class EvenementRemuneration_(RemunerationMixin):
681 """Structure de rémunération liée à un Evenement qui remplace
682 temporairement la Remuneration normale d'un Dossier, pour toute la durée
683 de l'Evenement.
684 """
685 evenement = models.ForeignKey("Evenement", db_column='evenement',
686 related_name='+',
687 verbose_name="Évènement")
688 # TODO : le champ dossier hérité de Remuneration doit être dérivé
689 # de l'Evenement associé
690
691 class Meta:
692 abstract = True
693 ordering = ['evenement', 'type__nom', '-date_fin']
694 verbose_name = "Évènement - rémunération"
695 verbose_name_plural = "Évènements - rémunérations"
696
697
698 class EvenementRemuneration(EvenementRemuneration_):
699 __doc__ = EvenementRemuneration_.__doc__
700
701 class Meta:
702 abstract = True
703
704
705 class EvenementRemuneration(EvenementRemuneration_):
706 __doc__ = EvenementRemuneration_.__doc__
707
708
709 ### RÉFÉRENCES RH
710
711 class FamilleEmploi(Metadata):
712 """Catégorie utilisée dans la gestion des Postes.
713 Catégorie supérieure à TypePoste.
714 """
715 nom = models.CharField(max_length=255)
716
717 class Meta:
718 ordering = ['nom']
719 verbose_name = "Famille d'emploi"
720 verbose_name_plural = "Familles d'emploi"
721
722 def __unicode__(self):
723 return u'%s' % (self.nom)
724
725 class TypePoste(Metadata):
726 """Catégorie de Poste.
727 """
728 nom = models.CharField(max_length=255)
729 nom_feminin = models.CharField(max_length=255,
730 verbose_name="Nom féminin")
731
732 is_responsable = models.BooleanField(default=False,
733 verbose_name="Poste de responsabilité")
734 famille_emploi = models.ForeignKey('FamilleEmploi',
735 db_column='famille_emploi',
736 related_name='+',
737 verbose_name="Famille d'emploi")
738
739 class Meta:
740 ordering = ['nom']
741 verbose_name = "Type de poste"
742 verbose_name_plural = "Types de poste"
743
744 def __unicode__(self):
745 return u'%s' % (self.nom)
746
747
748 TYPE_PAIEMENT_CHOICES = (
749 ('Régulier', 'Régulier'),
750 ('Ponctuel', 'Ponctuel'),
751 )
752
753 NATURE_REMUNERATION_CHOICES = (
754 ('Accessoire', 'Accessoire'),
755 ('Charges', 'Charges'),
756 ('Indemnité', 'Indemnité'),
757 ('RAS', 'Rémunération autre source'),
758 ('Traitement', 'Traitement'),
759 )
760
761 class TypeRemuneration(Metadata):
762 """Catégorie de Remuneration.
763 """
764 nom = models.CharField(max_length=255)
765 type_paiement = models.CharField(max_length=30,
766 choices=TYPE_PAIEMENT_CHOICES,
767 verbose_name="Type de paiement")
768 nature_remuneration = models.CharField(max_length=30,
769 choices=NATURE_REMUNERATION_CHOICES,
770 verbose_name="Nature de la rémunération")
771
772 class Meta:
773 ordering = ['nom']
774 verbose_name = "Type de rémunération"
775 verbose_name_plural = "Types de rémunération"
776
777 def __unicode__(self):
778 return u'%s' % (self.nom)
779
780 class TypeRevalorisation(Metadata):
781 """Justification du changement de la Remuneration.
782 (Actuellement utilisé dans aucun traitement informatique.)
783 """
784 nom = models.CharField(max_length=255)
785
786 class Meta:
787 ordering = ['nom']
788 verbose_name = "Type de revalorisation"
789 verbose_name_plural = "Types de revalorisation"
790
791 def __unicode__(self):
792 return u'%s' % (self.nom)
793
794 class Service(Metadata):
795 """Unité administrative où les Postes sont rattachés.
796 """
797 nom = models.CharField(max_length=255)
798
799 class Meta:
800 ordering = ['nom']
801 verbose_name = "Service"
802 verbose_name_plural = "Services"
803
804 def __unicode__(self):
805 return u'%s' % (self.nom)
806
807
808 TYPE_ORGANISME_CHOICES = (
809 ('MAD', 'Mise à disposition'),
810 ('DET', 'Détachement'),
811 )
812
813 class OrganismeBstg(Metadata):
814 """Organisation d'où provient un Employe mis à disposition (MAD) de
815 ou détaché (DET) à l'AUF à titre gratuit.
816
817 (BSTG = bien et service à titre gratuit.)
818 """
819 nom = models.CharField(max_length=255)
820 type = models.CharField(max_length=10, choices=TYPE_ORGANISME_CHOICES)
821 pays = models.ForeignKey(ref.Pays, to_field='code',
822 db_column='pays',
823 related_name='organismes_bstg',
824 null=True, blank=True)
825
826 class Meta:
827 ordering = ['type', 'nom']
828 verbose_name = "Organisme BSTG"
829 verbose_name_plural = "Organismes BSTG"
830
831 def __unicode__(self):
832 return u'%s (%s)' % (self.nom, self.get_type_display())
833
834 class Statut(Metadata):
835 """Statut de l'Employe dans le cadre d'un Dossier particulier.
836 """
837 # Identification
838 code = models.CharField(max_length=25, unique=True)
839 nom = models.CharField(max_length=255)
840
841 class Meta:
842 ordering = ['code']
843 verbose_name = "Statut d'employé"
844 verbose_name_plural = "Statuts d'employé"
845
846 def __unicode__(self):
847 return u'%s : %s' % (self.code, self.nom)
848
849
850 TYPE_CLASSEMENT_CHOICES = (
851 ('S', 'S -Soutien'),
852 ('T', 'T - Technicien'),
853 ('P', 'P - Professionel'),
854 ('C', 'C - Cadre'),
855 ('D', 'D - Direction'),
856 ('SO', 'SO - Sans objet [expatriés]'),
857 ('HG', 'HG - Hors grille [direction]'),
858 )
859
860
861 class Classement_(Metadata):
862 """Éléments de classement de la
863 "Grille générique de classement hiérarchique".
864
865 Utile pour connaître, pour un Dossier, le salaire de base théorique lié au
866 classement dans la grille. Le classement donne le coefficient utilisé dans:
867
868 salaire de base = coefficient * valeur du point de l'Implantation du Poste
869 """
870 # Identification
871 type = models.CharField(max_length=10, choices=TYPE_CLASSEMENT_CHOICES)
872 echelon = models.IntegerField(verbose_name="Échelon")
873 degre = models.IntegerField(verbose_name="Degré")
874 coefficient = models.FloatField(default=0, verbose_name="Coéfficient",
875 null=True)
876 # Méta
877 # annee # au lieu de date_debut et date_fin
878 commentaire = models.TextField(null=True, blank=True)
879
880 class Meta:
881 abstract = True
882 ordering = ['type','echelon','degre','coefficient']
883 verbose_name = "Classement"
884 verbose_name_plural = "Classements"
885
886 def __unicode__(self):
887 return u'%s.%s.%s (%s)' % (self.type, self.echelon, self.degre,
888 self.coefficient)
889
890 class Classement(Classement_):
891 __doc__ = Classement_.__doc__
892
893
894 class TauxChange_(Metadata):
895 """Taux de change de la devise vers l'euro (EUR)
896 pour chaque année budgétaire.
897 """
898 # Identification
899 devise = models.ForeignKey('Devise', db_column='devise',
900 related_name='+')
901 annee = models.IntegerField(verbose_name="Année")
902 taux = models.FloatField(verbose_name="Taux vers l'euro")
903
904 class Meta:
905 abstract = True
906 ordering = ['-annee', 'devise__code']
907 verbose_name = "Taux de change"
908 verbose_name_plural = "Taux de change"
909
910 def __unicode__(self):
911 return u'%s : %s € (%s)' % (self.devise, self.taux, self.annee)
912
913
914 class TauxChange(TauxChange_):
915 __doc__ = TauxChange_.__doc__
916
917
918 class ValeurPoint_(Metadata):
919 """Utile pour connaître, pour un Dossier, le salaire de base théorique lié
920 au classement dans la grille. La ValeurPoint s'obtient par l'implantation
921 du Poste de ce Dossier : dossier.poste.implantation (pseudo code).
922
923 salaire de base = coefficient * valeur du point de l'Implantation du Poste
924 """
925 valeur = models.FloatField(null=True)
926 devise = models.ForeignKey('Devise', db_column='devise', null=True,
927 related_name='+', default=5)
928 implantation = models.ForeignKey(ref.Implantation,
929 db_column='implantation',
930 related_name='%(app_label)s_valeur_point')
931 # Méta
932 annee = models.IntegerField()
933
934 class Meta:
935 ordering = ['annee', 'implantation__nom']
936 abstract = True
937 ordering = ['annee']
938 verbose_name = "Valeur du point"
939 verbose_name_plural = "Valeurs du point"
940
941 # TODO : cette fonction n'était pas présente dans la branche dev, utilité?
942 def get_tauxchange_courant(self):
943 """
944 Recherche le taux courant associé à la valeur d'un point.
945 Tous les taux de l'année courante sont chargés, pour optimiser un
946 affichage en liste. (On pourrait probablement améliorer le manager pour
947 lui greffer le taux courant sous forme de JOIN)
948 """
949 for tauxchange in self.tauxchange:
950 if tauxchange.implantation_id == self.implantation_id:
951 return tauxchange
952 return None
953
954 def __unicode__(self):
955 return u'%s %s (%s)' % (self.valeur, self.devise, self.annee)
956
957
958 class ValeurPoint(ValeurPoint_):
959 __doc__ = ValeurPoint_.__doc__
960
961
962 class Devise(Metadata):
963 """Devise monétaire.
964 """
965 code = models.CharField(max_length=10, unique=True)
966 nom = models.CharField(max_length=255)
967
968 class Meta:
969 ordering = ['code']
970 verbose_name = "Devise"
971 verbose_name_plural = "Devises"
972
973 def __unicode__(self):
974 return u'%s - %s' % (self.code, self.nom)
975
976 class TypeContrat(Metadata):
977 """Type de contrat.
978 """
979 nom = models.CharField(max_length=255)
980 nom_long = models.CharField(max_length=255)
981
982 class Meta:
983 ordering = ['nom']
984 verbose_name = "Type de contrat"
985 verbose_name_plural = "Types de contrat"
986
987 def __unicode__(self):
988 return u'%s' % (self.nom)
989
990
991 ### AUTRES
992
993 class ResponsableImplantation(Metadata):
994 """Le responsable d'une implantation.
995 Anciennement géré sur le Dossier du responsable.
996 """
997 employe = models.ForeignKey('Employe', db_column='employe',
998 related_name='+',
999 null=True, blank=True)
1000 implantation = models.ForeignKey(ref.Implantation,
1001 db_column='implantation', related_name='+',
1002 unique=True)
1003
1004 def __unicode__(self):
1005 return u'%s : %s' % (self.implantation, self.employe)
1006
1007 class Meta:
1008 ordering = ['implantation__nom']
1009 verbose_name = "Responsable d'implantation"
1010 verbose_name_plural = "Responsables d'implantation"