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