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