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