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