arst
[auf_rh_dae.git] / project / rh / models.py
CommitLineData
e9bbd6ba 1# -=- encoding: utf-8 -=-
2
9afaa55e 3import datetime
83b7692b 4
5from django.core.files.storage import FileSystemStorage
49f9f116 6from django.db import models
83b7692b 7import settings
8
9import datamaster_modeles.models as ref
10
11
2d4d2fcf 12# Constantes
6e4600ef 13HELP_TEXT_DATE = u"format: aaaa-mm-jj"
14REGIME_TRAVAIL_DEFAULT = 100.00
15REGIME_TRAVAIL_NB_HEURE_SEMAINE_DEFAULT = 35.00
2d4d2fcf 16
17
83b7692b 18# Upload de fichiers
19storage_prive = FileSystemStorage(settings.PRIVE_MEDIA_ROOT,
20 base_url=settings.PRIVE_MEDIA_URL)
21
22def poste_piece_dispatch(instance, filename):
23 path = "poste/%s/%s" % (instance.poste_id, filename)
24 return path
25
26def dossier_piece_dispatch(instance, filename):
27 path = "dossier/%s/%s" % (instance.dossier_id, filename)
28 return path
29
2d4d2fcf 30# Abstracts
31class Metadata(models.Model):
32 """Méta-données AUF.
6e4600ef 33 Metadata.actif = flag remplaçant la suppression.
34 actif == False : objet réputé supprimé.
2d4d2fcf 35 """
36 actif = models.BooleanField(default=True)
37 date_creation = models.DateField(auto_now_add=True)
6e4600ef 38 user_creation = models.ForeignKey('auth.User',
39 db_column='user_creation', related_name='+',
50fa9bc1 40 null=True, blank=True)
07b40eda 41 date_modification = models.DateField(auto_now=True)
6e4600ef 42 user_modification = models.ForeignKey('auth.User',
43 db_column='user_modification', related_name='+',
50fa9bc1
OL
44 null=True, blank=True)
45 date_desactivation = models.DateField(null=True, blank=True)
6e4600ef 46 user_desactivation = models.ForeignKey('auth.User',
47 db_column='user_desactivation', related_name='+',
50fa9bc1 48 null=True, blank=True)
07b40eda 49
50 class Meta:
51 abstract = True
52
2d4d2fcf 53class Commentaire(Metadata):
54 texte = models.TextField()
6e4600ef 55 owner = models.ForeignKey('auth.User', db_column='owner', related_name='+')
2d4d2fcf 56
57 class Meta:
58 abstract = True
6e4600ef 59 ordering = ['-date_creation']
60
61 def __unicode__(self):
62 return u'%s' % (self.texte)
07b40eda 63
83b7692b 64
65### POSTE
66
67POSTE_APPEL_CHOICES = (
68 ('interne', 'Interne'),
69 ('externe', 'Externe'),
70)
71
769a0755 72class Poste_(Metadata):
6e4600ef 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 """
83b7692b 77 # Identification
2d4d2fcf 78 nom = models.CharField(max_length=255,
6e4600ef 79 verbose_name=u"Titre du poste", )
2d4d2fcf 80 nom_feminin = models.CharField(max_length=255,
50fa9bc1 81 verbose_name=u"Titre du poste (au féminin)",
6e4600ef 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
83b7692b 96
97 # Contrat
98 regime_travail = models.DecimalField(max_digits=12, decimal_places=2,
2d4d2fcf 99 default=REGIME_TRAVAIL_DEFAULT,
6e4600ef 100 verbose_name=u"Temps de travail",
101 help_text=u"% du temps complet")
83b7692b 102 regime_travail_nb_heure_semaine = models.DecimalField(max_digits=12,
2d4d2fcf 103 decimal_places=2,
104 default=REGIME_TRAVAIL_NB_HEURE_SEMAINE_DEFAULT,
6e4600ef 105 verbose_name=u"Nb. heures par semaine")
83b7692b 106
107 # Recrutement
6e4600ef 108 local = models.BooleanField(verbose_name=u"Local", default=True,
109 blank=True)
110 expatrie = models.BooleanField(verbose_name=u"Expatrié", default=False,
83b7692b 111 blank=True)
6e4600ef 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')
83b7692b 119
120 # Rémunération
6e4600ef 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)
83b7692b 137 salaire_min = models.DecimalField(max_digits=12, decimal_places=2,
6e4600ef 138 default=0)
83b7692b 139 salaire_max = models.DecimalField(max_digits=12, decimal_places=2,
6e4600ef 140 default=0)
83b7692b 141 indemn_min = models.DecimalField(max_digits=12, decimal_places=2,
6e4600ef 142 default=0)
83b7692b 143 indemn_max = models.DecimalField(max_digits=12, decimal_places=2,
6e4600ef 144 default=0)
83b7692b 145 autre_min = models.DecimalField(max_digits=12, decimal_places=2,
6e4600ef 146 default=0)
83b7692b 147 autre_max = models.DecimalField(max_digits=12, decimal_places=2,
6e4600ef 148 default=0)
83b7692b 149
150 # Comparatifs de rémunération
6e4600ef 151 devise_comparaison = models.ForeignKey('Devise',
152 db_column='devise_comparaison',
153 related_name='+',
154 default=5)
83b7692b 155 comp_locale_min = models.DecimalField(max_digits=12, decimal_places=2,
6e4600ef 156 null=True, blank=True)
83b7692b 157 comp_locale_max = models.DecimalField(max_digits=12, decimal_places=2,
6e4600ef 158 null=True, blank=True)
83b7692b 159 comp_universite_min = models.DecimalField(max_digits=12, decimal_places=2,
6e4600ef 160 null=True, blank=True)
83b7692b 161 comp_universite_max = models.DecimalField(max_digits=12, decimal_places=2,
6e4600ef 162 null=True, blank=True)
83b7692b 163 comp_fonctionpub_min = models.DecimalField(max_digits=12, decimal_places=2,
6e4600ef 164 null=True, blank=True)
83b7692b 165 comp_fonctionpub_max = models.DecimalField(max_digits=12, decimal_places=2,
6e4600ef 166 null=True, blank=True)
83b7692b 167 comp_ong_min = models.DecimalField(max_digits=12, decimal_places=2,
6e4600ef 168 null=True, blank=True)
83b7692b 169 comp_ong_max = models.DecimalField(max_digits=12, decimal_places=2,
6e4600ef 170 null=True, blank=True)
83b7692b 171 comp_autre_min = models.DecimalField(max_digits=12, decimal_places=2,
6e4600ef 172 null=True, blank=True)
83b7692b 173 comp_autre_max = models.DecimalField(max_digits=12, decimal_places=2,
6e4600ef 174 null=True, blank=True)
83b7692b 175
176 # Justification
6e4600ef 177 justification = models.TextField(null=True, blank=True)
83b7692b 178
2d4d2fcf 179 # Autres Metadata
07b40eda 180 date_validation = models.DateTimeField(null=True, blank=True) # de dae
6e4600ef 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:
769a0755 188 abstract = True
6e4600ef 189 ordering = ['implantation__nom', 'nom']
190
83b7692b 191 def __unicode__(self):
2d4d2fcf 192 # TODO : gérer si poste est vacant ou non dans affichage
6e4600ef 193 # TODO : gérer le nom_feminin (autre méthode appelée par __unicode__ ?)
83b7692b 194 return u'%s - %s [%s]' % (self.implantation, self.nom, self.id)
195
196
769a0755
NC
197class Poste(Poste_):
198 __doc__ = Poste_.__doc__
199
200
83b7692b 201POSTE_FINANCEMENT_CHOICES = (
202 ('A', 'A - Frais de personnel'),
203 ('B', 'B - Projet(s)-Titre(s)'),
204 ('C', 'C - Autre')
205)
206
f31ddfa0
NC
207
208class PosteFinancement_(models.Model):
6e4600ef 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',
f31ddfa0 213 related_name='%(app_label)s_financements')
83b7692b 214 type = models.CharField(max_length=1, choices=POSTE_FINANCEMENT_CHOICES)
215 pourcentage = models.DecimalField(max_digits=12, decimal_places=2,
6e4600ef 216 help_text=u"ex.: 33.33 % (décimale avec point)")
83b7692b 217 commentaire = models.TextField(
6e4600ef 218 help_text=u"Spécifiez la source de financement.")
83b7692b 219
220 class Meta:
f31ddfa0 221 abstract = True
83b7692b 222 ordering = ['type']
6e4600ef 223
224 def __unicode__(self):
225 return u'%s : %s %' % (self.type, self.pourcentage)
83b7692b 226
f31ddfa0
NC
227
228class PosteFinancement(PosteFinancement_):
229 __doc__ = PosteFinancement_.__doc__
230
231
83b7692b 232class PostePiece(models.Model):
6e4600ef 233 """Documents relatifs au Poste.
7abc6d45 234 Ex.: Description de poste
235 """
6e4600ef 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",
83b7692b 240 upload_to=poste_piece_dispatch,
241 storage=storage_prive)
242
6e4600ef 243 class Meta:
244 ordering = ['nom']
245
246 def __unicode__(self):
247 return u'%s' % (self.nom)
248
07b40eda 249class PosteCommentaire(Commentaire):
6e4600ef 250 poste = models.ForeignKey("Poste", db_column='poste', related_name='+')
83b7692b 251
2d4d2fcf 252
83b7692b 253### EMPLOYÉ/PERSONNE
e9bbd6ba 254
255GENRE_CHOICES = (
256 ('M', 'Homme'),
257 ('F', 'Femme'),
258)
259SITUATION_CHOICES = (
260 ('C', 'Célibataire'),
261 ('F', 'Fiancé'),
262 ('M', 'Marié'),
263)
264
2d4d2fcf 265class Employe(Metadata):
6e4600ef 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 """
9afaa55e 272 # Identification
e9bbd6ba 273 nom = models.CharField(max_length=255)
6e4600ef 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)
83b7692b 279 nationalite = models.ForeignKey(ref.Pays, to_field='code',
6e4600ef 280 db_column='nationalite',
281 related_name='employes_nationalite')
282 date_naissance = models.DateField(help_text=HELP_TEXT_DATE,
283 null=True, blank=True)
2d4d2fcf 284 genre = models.CharField(max_length=1, choices=GENRE_CHOICES)
83b7692b 285
9afaa55e 286 # Infos personnelles
6e4600ef 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,
7abc6d45 292 null=True, blank=True)
83b7692b 293
9afaa55e 294 # Coordonnées
e9bbd6ba 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)
e9bbd6ba 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)
6e4600ef 301 pays = models.ForeignKey(ref.Pays, to_field='code', db_column='pays',
302 related_name='employes',
303 null=True, blank=True)
9afaa55e 304
6e4600ef 305 class Meta:
306 ordering = ['nom_affichage']
307
9afaa55e 308 def __unicode__(self):
6e4600ef 309 # TODO : gérer nom d'affichage
310 return u'%s' % (self.nom_affichage)
9afaa55e 311
7abc6d45 312class EmployePiece(models.Model):
6e4600ef 313 """Documents relatifs à un employé.
7abc6d45 314 Ex.: CV...
315 """
6e4600ef 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",
7abc6d45 320 upload_to=dossier_piece_dispatch,
321 storage=storage_prive)
322
6e4600ef 323 class Meta:
324 ordering = ['nom']
325
326 def __unicode__(self):
327 return u'%s' % (self.nom)
328
07b40eda 329class EmployeCommentaire(Commentaire):
6e4600ef 330 employe = models.ForeignKey("Employe", db_column='employe',
331 related_name='+')
9afaa55e 332
2d4d2fcf 333
e9bbd6ba 334LIEN_PARENTE_CHOICES = (
335 ('Conjoint', 'Conjoint'),
336 ('Conjointe', 'Conjointe'),
337 ('Fille', 'Fille'),
338 ('Fils', 'Fils'),
339)
340
2d4d2fcf 341class AyantDroit(Metadata):
6e4600ef 342 """Personne en relation avec un Employe.
343 """
9afaa55e 344 # Identification
e9bbd6ba 345 nom = models.CharField(max_length=255)
346 prenom = models.CharField(max_length=255)
6e4600ef 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)
2d4d2fcf 351 nationalite = models.ForeignKey(ref.Pays, to_field='code',
6e4600ef 352 db_column='nationalite',
353 related_name='ayantdroits_nationalite')
354 date_naissance = models.DateField(help_text=HELP_TEXT_DATE,
355 null=True, blank=True)
2d4d2fcf 356 genre = models.CharField(max_length=1, choices=GENRE_CHOICES)
83b7692b 357
9afaa55e 358 # Relation
359 employe = models.ForeignKey('Employe', db_column='employe',
6e4600ef 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())
83b7692b 370
07b40eda 371class AyantDroitCommentaire(Commentaire):
6e4600ef 372 ayant_droit = models.ForeignKey("AyantDroit", db_column='ayant_droit',
373 related_name='+')
83b7692b 374
2d4d2fcf 375
83b7692b 376### DOSSIER
377
378STATUT_RESIDENCE_CHOICES = (
379 ('local', 'Local'),
380 ('expat', 'Expatrié'),
381)
382
383COMPTE_COMPTA_CHOICES = (
384 ('coda', 'CODA'),
385 ('scs', 'SCS'),
386 ('aucun', 'Aucun'),
387)
388
769a0755 389class Dossier_(Metadata):
6e4600ef 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 """
83b7692b 398 # Identification
6e4600ef 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)
83b7692b 404 organisme_bstg = models.ForeignKey('OrganismeBstg',
6e4600ef 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
83b7692b 413 # Recrutement
2d4d2fcf 414 remplacement = models.BooleanField(default=False)
83b7692b 415 statut_residence = models.CharField(max_length=10, default='local',
6e4600ef 416 verbose_name=u"Statut",
2d4d2fcf 417 choices=STATUT_RESIDENCE_CHOICES)
83b7692b 418
419 # Rémunération
6e4600ef 420 classement = models.ForeignKey('Classement', db_column='classement',
421 related_name='+',
422 null=True, blank=True)
2d4d2fcf 423 regime_travail = models.DecimalField(max_digits=12,
424 decimal_places=2,
425 default=REGIME_TRAVAIL_DEFAULT,
6e4600ef 426 verbose_name=u"Régime de travail",
427 help_text=u"% du temps complet")
83b7692b 428 regime_travail_nb_heure_semaine = models.DecimalField(max_digits=12,
2d4d2fcf 429 decimal_places=2,
430 default=REGIME_TRAVAIL_NB_HEURE_SEMAINE_DEFAULT,
6e4600ef 431 verbose_name=u"Nb. heures par semaine")
7abc6d45 432
433 # Occupation du Poste par cet Employe (anciennement "mandat")
6e4600ef 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",
2d4d2fcf 439 help_text=HELP_TEXT_DATE,
6e4600ef 440 null=True, blank=True)
e9bbd6ba 441
2d4d2fcf 442 # Comptes
443 # TODO?
83b7692b 444
6e4600ef 445 class Meta:
769a0755 446 abstract = True
6e4600ef 447 ordering = ['poste__nom', 'employe__nom_affichage']
448
83b7692b 449 def __unicode__(self):
450 return u'%s - %s' % (self.poste.nom, self.employe)
451
769a0755
NC
452
453class Dossier(Dossier_):
454 __doc__ = Dossier_.__doc__
455
456
83b7692b 457class DossierPiece(models.Model):
7abc6d45 458 """Documents relatifs au Dossier (à l'occupation de ce poste par employé).
459 Ex.: Lettre de motivation.
460 """
6e4600ef 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",
83b7692b 465 upload_to=dossier_piece_dispatch,
466 storage=storage_prive)
467
6e4600ef 468 class Meta:
469 ordering = ['nom']
470
471 def __unicode__(self):
472 return u'%s' % (self.nom)
473
07b40eda 474class DossierCommentaire(Commentaire):
6e4600ef 475 dossier = models.ForeignKey("Dossier", db_column='dossier',
476 related_name='+')
83b7692b 477
2d4d2fcf 478
07b40eda 479### RÉMUNÉRATION
e9bbd6ba 480
2d4d2fcf 481class RemunerationMixin(Metadata):
9afaa55e 482 # Identification
e9bbd6ba 483 dossier = models.ForeignKey('Dossier', db_column='dossier')
83b7692b 484 type = models.ForeignKey('TypeRemuneration', db_column='type',
485 related_name='+')
7abc6d45 486 type_revalorisation = models.ForeignKey('TypeRevalorisation',
487 db_column='type_revalorisation',
6e4600ef 488 related_name='+',
7abc6d45 489 null=True, blank=True)
50fa9bc1 490 montant = models.FloatField(null=True, blank=True,
6e4600ef 491 default=0)
2d4d2fcf 492 # Annuel (12 mois, 52 semaines, 364 jours?)
c589d980 493 devise = models.ForeignKey('Devise', to_field='id',
6e4600ef 494 db_column='devise', related_name='+',
495 default=5)
2d4d2fcf 496 # commentaire = precision
497 commentaire = models.CharField(max_length=255, null=True, blank=True)
498 # date_debut = anciennement date_effectif
6e4600ef 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)
83b7692b 503
2d4d2fcf 504 class Meta:
505 abstract = True
6e4600ef 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)
2d4d2fcf 510
f31ddfa0 511class Remuneration_(RemunerationMixin):
2d4d2fcf 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 """
83b7692b 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)
9afaa55e 528
529 def __unicode__(self):
530 try:
531 devise = self.devise.code
532 except:
533 devise = "???"
534 return "%s %s" % (self.montant, devise)
83b7692b 535
f31ddfa0
NC
536 class Meta:
537 abstract = True
538
539
540class Remuneration(Remuneration_):
541 __doc__ = Remuneration_.__doc__
542
2d4d2fcf 543
544### CONTRATS
545
546class 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 """
6e4600ef 551 dossier = models.ForeignKey('Dossier', db_column='dossier',
552 related_name='+')
553 type_contrat = models.ForeignKey('TypeContrat', db_column='type_contrat',
554 related_name='+')
2d4d2fcf 555 date_debut = models.DateField(help_text=HELP_TEXT_DATE)
6e4600ef 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):
2d4d2fcf 566
567
568### ÉVÉNEMENTS
569
f31ddfa0 570class Evenement_(Metadata):
6e4600ef 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)
f31ddfa0 588
6e4600ef 589 class Meta:
f31ddfa0 590 abstract = True
6e4600ef 591 ordering = ['nom']
592
593 def __unicode__(self):
594 return u'%s' % (self.nom)
f31ddfa0
NC
595
596
597class Evenement(Evenement_):
598 __doc__ = Evenement_.__doc__
599
2d4d2fcf 600
f31ddfa0 601class EvenementRemuneration_(RemunerationMixin):
6e4600ef 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='+')
83b7692b 608
f31ddfa0
NC
609 class Meta:
610 abstract = True
611
612
613class EvenementRemuneration(EvenementRemuneration_):
614 __doc__ = EvenementRemuneration_.__doc__
615
83b7692b 616
617### RÉFÉRENCES RH
618
6e4600ef 619class FamilleEmploi(Metadata):
620 """Catégorie utilisée dans la gestion des Postes.
621 Catégorie supérieure à TypePoste.
622 """
e9bbd6ba 623 nom = models.CharField(max_length=255)
6e4600ef 624
625 def __unicode__(self):
626 return u'%s' % (self.nom)
e9bbd6ba 627
6e4600ef 628class TypePoste(Metadata):
629 """Catégorie de Poste.
630 """
e9bbd6ba 631 nom = models.CharField(max_length=255)
632 nom_feminin = models.CharField(max_length=255)
6e4600ef 633
634 is_responsable = models.BooleanField(default=False)
9afaa55e 635 famille_emploi = models.ForeignKey('FamilleEmploi',
6e4600ef 636 db_column='famille_emploi',
637 related_name='+')
e9bbd6ba 638
6e4600ef 639 class Meta:
640 ordering = ['nom']
641
e9bbd6ba 642 def __unicode__(self):
6e4600ef 643 # TODO : gérer nom féminin
644 return u'%s' % (self.nom)
e9bbd6ba 645
646
647TYPE_PAIEMENT_CHOICES = (
648 ('Régulier', 'Régulier'),
649 ('Ponctuel', 'Ponctuel'),
650)
651
652NATURE_REMUNERATION_CHOICES = (
653 ('Accessoire', 'Accessoire'),
654 ('Charges', 'Charges'),
655 ('Indemnité', 'Indemnité'),
7abc6d45 656 ('RAS', 'Rémunération autre source'),
e9bbd6ba 657 ('Traitement', 'Traitement'),
658)
659
6e4600ef 660class TypeRemuneration(Metadata):
661 """Catégorie de Remuneration.
662 """
e9bbd6ba 663 nom = models.CharField(max_length=255)
9afaa55e 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)
9afaa55e 668
669 def __unicode__(self):
6e4600ef 670 return u'%s' % (self.nom)
e9bbd6ba 671
6e4600ef 672class TypeRevalorisation(Metadata):
7abc6d45 673 """Justification du changement de la Remuneration.
6e4600ef 674 (Actuellement utilisé dans aucun traitement informatique.)
7abc6d45 675 """
e9bbd6ba 676 nom = models.CharField(max_length=255)
e9bbd6ba 677
678 def __unicode__(self):
6e4600ef 679 return u'%s' % (self.nom)
680
681class Service(Metadata):
682 """Unité administrative où les Postes sont rattachés.
683 """
684 nom = models.CharField(max_length=255)
9afaa55e 685
686 class Meta:
687 ordering = ['nom']
e9bbd6ba 688
6e4600ef 689 def __unicode__(self):
690 return u'%s' % (self.nom)
691
e9bbd6ba 692
693TYPE_ORGANISME_CHOICES = (
694 ('MAD', 'Mise à disposition'),
695 ('DET', 'Détachement'),
696)
697
6e4600ef 698class 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 """
e9bbd6ba 704 nom = models.CharField(max_length=255)
705 type = models.CharField(max_length=10, choices=TYPE_ORGANISME_CHOICES)
6e4600ef 706 pays = models.ForeignKey(ref.Pays, to_field='code',
707 db_column='pays',
708 related_name='organismes_bstg',
709 null=True, blank=True)
9afaa55e 710
711 class Meta:
712 ordering = ['type', 'nom']
713
6e4600ef 714 def __unicode__(self):
715 return u'%s (%s)' % (self.nom, self.type)
83b7692b 716
6e4600ef 717class Statut(Metadata):
718 """Statut de l'Employe dans le cadre d'un Dossier particulier.
719 """
9afaa55e 720 # Identification
e9bbd6ba 721 code = models.CharField(max_length=25, unique=True)
722 nom = models.CharField(max_length=255)
e9bbd6ba 723
6e4600ef 724 class Meta:
725 ordering = ['code']
726
9afaa55e 727 def __unicode__(self):
728 return u'%s : %s' % (self.code, self.nom)
729
83b7692b 730
e9bbd6ba 731TYPE_CLASSEMENT_CHOICES = (
6e4600ef 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]'),
e9bbd6ba 739)
83b7692b 740
6e4600ef 741class 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 """
9afaa55e 750 # Identification
e9bbd6ba 751 type = models.CharField(max_length=10, choices=TYPE_CLASSEMENT_CHOICES)
752 echelon = models.IntegerField()
753 degre = models.IntegerField()
6e4600ef 754 coefficient = models.FloatField(default=0)
9afaa55e 755 # Méta
6e4600ef 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']
e9bbd6ba 761
762 def __unicode__(self):
763 return u'%s.%s.%s (%s)' % (self.type, self.echelon, self.degre,
764 self.coefficient)
765
6e4600ef 766class TauxChange(Metadata):
7abc6d45 767 """Taux de change de la devise vers l'euro (EUR)
6e4600ef 768 pour chaque année budgétaire.
7abc6d45 769 """
9afaa55e 770 # Identification
6e4600ef 771 devise = models.ForeignKey('Devise', to_field='code', db_column='devise',
772 related_name='+')
e9bbd6ba 773 annee = models.IntegerField()
9afaa55e 774 taux = models.FloatField()
6e4600ef 775
776 class Meta:
777 ordering = ['annee', 'devise__code']
778
779 def __unicode__(self):
780 return u'%s : %s €' % (self.devise.code, self.taux)
e9bbd6ba 781
6e4600ef 782class 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 """
9afaa55e 789 valeur = models.FloatField()
6e4600ef 790 devise = models.ForeignKey('Devise', db_column='devise',
791 related_name='+', default=5)
83b7692b 792 implantation = models.ForeignKey(ref.Implantation,
9afaa55e 793 db_column='implantation',
6e4600ef 794 related_name='valeur_point')
9afaa55e 795 # Méta
e9bbd6ba 796 annee = models.IntegerField()
9afaa55e 797
2d4d2fcf 798 # Stockage de tous les taux de change
799 # pour optimiser la recherche de la devise associée
9afaa55e 800 annee_courante = datetime.datetime.now().year
6e4600ef 801 tauxchange = TauxChange.objects.select_related('devise') \
802 .filter(annee=annee_courante)
9afaa55e 803
6e4600ef 804 class Meta:
805 ordering = ['annee', 'implantation__nom']
ee23ecbc
NC
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
9afaa55e 819 def __unicode__(self):
820 tx = self.get_tauxchange_courant()
821 if tx:
822 devise_code = tx.devise.code
823 else:
824 devise_code = "??"
2d4d2fcf 825 return u'%s %s (%s-%s)' % (self.valeur, devise_code,
826 self.implantation_id, self.annee)
9afaa55e 827
828 class Meta:
829 ordering = ['valeur']
e9bbd6ba 830
6e4600ef 831class Devise(Metadata):
832 """Devise monétaire.
833 """
e9bbd6ba 834 code = models.CharField(max_length=10, unique=True)
835 nom = models.CharField(max_length=255)
836
6e4600ef 837 class Meta:
838 ordering = ['code']
839
e9bbd6ba 840 def __unicode__(self):
841 return u'%s - %s' % (self.code, self.nom)
842
6e4600ef 843class TypeContrat(Metadata):
844 """Type de contrat.
845 """
e9bbd6ba 846 nom = models.CharField(max_length=255)
6e4600ef 847 nom_long = models.CharField(max_length=255)
49f9f116 848
9afaa55e 849 def __unicode__(self):
850 return u'%s' % (self.nom)
30be56d5 851
2d4d2fcf 852
853### AUTRES
854
6e4600ef 855class ResponsableImplantation(Metadata):
30be56d5 856 """Le responsable d'une implantation.
857 Anciennement géré sur le Dossier du responsable.
858 """
6e4600ef 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)
30be56d5 865
866 def __unicode__(self):
867 return u'%s : %s' % (self.implantation, self.employe)
868
869 class Meta:
870 ordering = ['implantation__nom']