#1940
[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
8c1ae2b3 13HELP_TEXT_DATE = "format: aaaa-mm-jj"
6e4600ef 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,
8c1ae2b3 79 verbose_name="Titre du poste", )
2d4d2fcf 80 nom_feminin = models.CharField(max_length=255,
8c1ae2b3 81 verbose_name="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='+',
8c1ae2b3 90 verbose_name="Direction/Service/Pôle support",
6e4600ef 91 default=1) # default = Rectorat
92 responsable = models.ForeignKey('Poste', db_column='responsable',
93 related_name='+',
8c1ae2b3 94 verbose_name="Poste du responsable",
6e4600ef 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,
8c1ae2b3 100 verbose_name="Temps de travail",
101 help_text="% 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,
8c1ae2b3 105 verbose_name="Nb. heures par semaine")
83b7692b 106
107 # Recrutement
8c1ae2b3 108 local = models.BooleanField(verbose_name="Local", default=True,
6e4600ef 109 blank=True)
8c1ae2b3 110 expatrie = models.BooleanField(verbose_name="Expatrié", default=False,
83b7692b 111 blank=True)
6e4600ef 112 mise_a_disposition = models.BooleanField(
8c1ae2b3 113 verbose_name="Mise à disposition",
6e4600ef 114 default=False)
115 appel = models.CharField(max_length=10,
8c1ae2b3 116 verbose_name="Appel à candidature",
6e4600ef 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
8c1ae2b3 181 date_debut = models.DateField(verbose_name="Date de début",
7332d8f9
OL
182 help_text=HELP_TEXT_DATE,
183 null=True, blank=True)
8c1ae2b3 184 date_fin = models.DateField(verbose_name="Date de fin",
6e4600ef 185 help_text=HELP_TEXT_DATE,
186 null=True, blank=True)
187
188 class Meta:
37868f0b 189 abstract = True
6e4600ef 190 ordering = ['implantation__nom', 'nom']
8c1ae2b3 191 verbose_name = "Poste"
192 verbose_name_plural = "Postes"
6e4600ef 193
83b7692b 194 def __unicode__(self):
8c1ae2b3 195 representation = u'%s - %s [%s]' % (self.implantation, self.nom,
196 self.id)
197 if self.is_vacant():
198 representation = representation + u' (vacant)'
199 return representation
200
201 def is_vacant(self):
202 # TODO : si existe un dossier actif pour ce poste, return False
203 # self.dossier_set.all() fonctionne pas
204 return False
83b7692b 205
206
37868f0b
NC
207class Poste(Poste_):
208 __doc__ = Poste_.__doc__
209
210
83b7692b 211POSTE_FINANCEMENT_CHOICES = (
212 ('A', 'A - Frais de personnel'),
213 ('B', 'B - Projet(s)-Titre(s)'),
214 ('C', 'C - Autre')
215)
216
6e7c919b
NC
217
218class PosteFinancement_(models.Model):
6e4600ef 219 """Pour un Poste, structure d'informations décrivant comment on prévoit
220 financer ce Poste.
221 """
222 poste = models.ForeignKey('Poste', db_column='poste',
6e7c919b 223 related_name='%(app_label)s_financements')
83b7692b 224 type = models.CharField(max_length=1, choices=POSTE_FINANCEMENT_CHOICES)
225 pourcentage = models.DecimalField(max_digits=12, decimal_places=2,
8c1ae2b3 226 help_text="ex.: 33.33 % (décimale avec point)")
83b7692b 227 commentaire = models.TextField(
8c1ae2b3 228 help_text="Spécifiez la source de financement.")
83b7692b 229
230 class Meta:
6e7c919b 231 abstract = True
83b7692b 232 ordering = ['type']
6e4600ef 233
234 def __unicode__(self):
235 return u'%s : %s %' % (self.type, self.pourcentage)
83b7692b 236
6e7c919b
NC
237
238class PosteFinancement(PosteFinancement_):
239 __doc__ = PosteFinancement_.__doc__
240
241
83b7692b 242class PostePiece(models.Model):
6e4600ef 243 """Documents relatifs au Poste.
7abc6d45 244 Ex.: Description de poste
245 """
8c1ae2b3 246 poste = models.ForeignKey('Poste', db_column='poste',
6e4600ef 247 related_name='pieces')
8c1ae2b3 248 nom = models.CharField(verbose_name="Nom", max_length=255)
249 fichier = models.FileField(verbose_name="Fichier",
83b7692b 250 upload_to=poste_piece_dispatch,
251 storage=storage_prive)
252
6e4600ef 253 class Meta:
254 ordering = ['nom']
255
256 def __unicode__(self):
257 return u'%s' % (self.nom)
258
068d1462
OL
259class PosteComparaison(models.Model):
260 """
261 De la même manière qu'un dossier, un poste peut-être comparé à un autre poste.
262 """
263 poste = models.ForeignKey('Poste', related_name='comparaisons_internes')
264 implantation = models.ForeignKey(ref.Implantation, null=True, blank=True)
265 nom = models.CharField(verbose_name="Poste", max_length=255, null=True, blank=True)
266 montant = models.IntegerField(null=True)
267 devise = models.ForeignKey("Devise", default=5, related_name='+', null=True, blank=True)
1d0f4eef
OL
268
269 def taux_devise(self):
270 liste_taux = self.devise.tauxchange_set.order_by('-annee').filter(implantation=self.implantation)
271 if len(liste_taux) == 0:
272 raise Exception(u"La devise %s n'a pas de taux pour l'implantation %s" % (self.devise, self.implantation))
273 else:
274 return liste_taux[0].taux
275
276 def montant_euros(self):
277 return round(float(self.montant) * float(self.taux_devise()), 2)
068d1462
OL
278
279
07b40eda 280class PosteCommentaire(Commentaire):
8c1ae2b3 281 poste = models.ForeignKey('Poste', db_column='poste', related_name='+')
83b7692b 282
2d4d2fcf 283
83b7692b 284### EMPLOYÉ/PERSONNE
e9bbd6ba 285
286GENRE_CHOICES = (
287 ('M', 'Homme'),
288 ('F', 'Femme'),
289)
290SITUATION_CHOICES = (
291 ('C', 'Célibataire'),
292 ('F', 'Fiancé'),
293 ('M', 'Marié'),
294)
295
2d4d2fcf 296class Employe(Metadata):
6e4600ef 297 """Personne occupant ou ayant occupé un Poste. Un Employe aura autant de
298 Dossiers qu'il occupe ou a occupé de Postes.
299
300 Cette classe aurait pu avantageusement s'appeler Personne car la notion
301 d'employé n'a pas de sens si aucun Dossier n'existe pour une personne.
302 """
9afaa55e 303 # Identification
e9bbd6ba 304 nom = models.CharField(max_length=255)
8c1ae2b3 305 prenom = models.CharField(max_length=255, verbose_name="Prénom")
6e4600ef 306 nom_affichage = models.CharField(max_length=255,
8c1ae2b3 307 verbose_name="Nom d'affichage",
6e4600ef 308 null=True, blank=True)
83b7692b 309 nationalite = models.ForeignKey(ref.Pays, to_field='code',
6e4600ef 310 db_column='nationalite',
8c1ae2b3 311 related_name='employes_nationalite',
312 verbose_name="Nationalité")
6e4600ef 313 date_naissance = models.DateField(help_text=HELP_TEXT_DATE,
8c1ae2b3 314 verbose_name="Date de naissance",
6e4600ef 315 null=True, blank=True)
2d4d2fcf 316 genre = models.CharField(max_length=1, choices=GENRE_CHOICES)
83b7692b 317
9afaa55e 318 # Infos personnelles
6e4600ef 319 situation_famille = models.CharField(max_length=1,
320 choices=SITUATION_CHOICES,
8c1ae2b3 321 verbose_name="Situation familiale",
6e4600ef 322 null=True, blank=True)
8c1ae2b3 323 date_entree = models.DateField(verbose_name="Date d'entrée à l'AUF",
6e4600ef 324 help_text=HELP_TEXT_DATE,
7abc6d45 325 null=True, blank=True)
83b7692b 326
9afaa55e 327 # Coordonnées
8c1ae2b3 328 tel_domicile = models.CharField(max_length=255,
329 verbose_name="Tél. domicile",
330 null=True, blank=True)
331 tel_cellulaire = models.CharField(max_length=255,
332 verbose_name="Tél. cellulaire",
333 null=True, blank=True)
e9bbd6ba 334 adresse = models.CharField(max_length=255, null=True, blank=True)
e9bbd6ba 335 ville = models.CharField(max_length=255, null=True, blank=True)
336 province = models.CharField(max_length=255, null=True, blank=True)
337 code_postal = models.CharField(max_length=255, null=True, blank=True)
6e4600ef 338 pays = models.ForeignKey(ref.Pays, to_field='code', db_column='pays',
339 related_name='employes',
340 null=True, blank=True)
9afaa55e 341
6e4600ef 342 class Meta:
67666927 343 ordering = ['nom_affichage','nom','prenom']
8c1ae2b3 344 verbose_name = "Employé"
345 verbose_name_plural = "Employés"
6e4600ef 346
9afaa55e 347 def __unicode__(self):
8c1ae2b3 348 return u'%s' % (self.get_nom())
349
350 def get_nom(self):
ebc8eb32 351 nom_affichage = self.nom_affichage
352 if not nom_affichage:
353 nom_affichage = u'%s %s' % (self.nom.upper(), self.prenom)
8c1ae2b3 354 return nom_affichage
9afaa55e 355
7abc6d45 356class EmployePiece(models.Model):
6e4600ef 357 """Documents relatifs à un employé.
7abc6d45 358 Ex.: CV...
359 """
8c1ae2b3 360 employe = models.ForeignKey('Employe', db_column='employe',
6e4600ef 361 related_name='+')
8c1ae2b3 362 nom = models.CharField(verbose_name="Nom", max_length=255)
363 fichier = models.FileField(verbose_name="Fichier",
7abc6d45 364 upload_to=dossier_piece_dispatch,
365 storage=storage_prive)
366
6e4600ef 367 class Meta:
368 ordering = ['nom']
369
370 def __unicode__(self):
371 return u'%s' % (self.nom)
372
07b40eda 373class EmployeCommentaire(Commentaire):
8c1ae2b3 374 employe = models.ForeignKey('Employe', db_column='employe',
6e4600ef 375 related_name='+')
9afaa55e 376
2d4d2fcf 377
e9bbd6ba 378LIEN_PARENTE_CHOICES = (
379 ('Conjoint', 'Conjoint'),
380 ('Conjointe', 'Conjointe'),
381 ('Fille', 'Fille'),
382 ('Fils', 'Fils'),
383)
384
2d4d2fcf 385class AyantDroit(Metadata):
6e4600ef 386 """Personne en relation avec un Employe.
387 """
9afaa55e 388 # Identification
e9bbd6ba 389 nom = models.CharField(max_length=255)
8c1ae2b3 390 prenom = models.CharField(max_length=255,
391 verbose_name="Prénom",)
6e4600ef 392 nom_affichage = models.CharField(max_length=255,
8c1ae2b3 393 verbose_name="Nom d'affichage",
6e4600ef 394 null=True, blank=True)
2d4d2fcf 395 nationalite = models.ForeignKey(ref.Pays, to_field='code',
6e4600ef 396 db_column='nationalite',
8c1ae2b3 397 related_name='ayantdroits_nationalite',
398 verbose_name="Nationalité")
6e4600ef 399 date_naissance = models.DateField(help_text=HELP_TEXT_DATE,
8c1ae2b3 400 verbose_name="Date de naissance",
6e4600ef 401 null=True, blank=True)
2d4d2fcf 402 genre = models.CharField(max_length=1, choices=GENRE_CHOICES)
83b7692b 403
9afaa55e 404 # Relation
405 employe = models.ForeignKey('Employe', db_column='employe',
8c1ae2b3 406 related_name='ayantdroits',
407 verbose_name="Employé")
6e4600ef 408 lien_parente = models.CharField(max_length=10,
409 choices=LIEN_PARENTE_CHOICES,
8c1ae2b3 410 verbose_name="Lien de parenté",
6e4600ef 411 null=True, blank=True)
412
413 class Meta:
414 ordering = ['nom_affichage']
8c1ae2b3 415 verbose_name = "Ayant droit"
416 verbose_name_plural = "Ayants droit"
417
6e4600ef 418 def __unicode__(self):
8c1ae2b3 419 return u'%s' % (self.get_nom())
420
421 def get_nom(self):
422 nom_affichage = self.nom_affichage
423 if not nom_affichage:
424 nom_affichage = u'%s %s' % (self.nom.upper(), self.prenom)
425 return nom_affichage
83b7692b 426
07b40eda 427class AyantDroitCommentaire(Commentaire):
8c1ae2b3 428 ayant_droit = models.ForeignKey('AyantDroit', db_column='ayant_droit',
6e4600ef 429 related_name='+')
83b7692b 430
2d4d2fcf 431
83b7692b 432### DOSSIER
433
434STATUT_RESIDENCE_CHOICES = (
435 ('local', 'Local'),
436 ('expat', 'Expatrié'),
437)
438
439COMPTE_COMPTA_CHOICES = (
440 ('coda', 'CODA'),
441 ('scs', 'SCS'),
442 ('aucun', 'Aucun'),
443)
444
6e7c919b 445
37868f0b 446class Dossier_(Metadata):
6e4600ef 447 """Le Dossier regroupe les informations relatives à l'occupation
448 d'un Poste par un Employe. Un seul Dossier existe par Poste occupé
449 par un Employe.
450
451 Plusieurs Contrats peuvent être associés au Dossier.
452 Une structure de Remuneration est rattachée au Dossier. Un Poste pour
453 lequel aucun Dossier n'existe est un poste vacant.
454 """
83b7692b 455 # Identification
6e4600ef 456 employe = models.ForeignKey('Employe', db_column='employe',
8c1ae2b3 457 related_name='+',
458 verbose_name="Employé")
6e4600ef 459 poste = models.ForeignKey('Poste', db_column='poste',
460 related_name='+', editable=False)
461 statut = models.ForeignKey('Statut', related_name='+', default=3)
83b7692b 462 organisme_bstg = models.ForeignKey('OrganismeBstg',
6e4600ef 463 db_column='organisme_bstg',
464 related_name='+',
8c1ae2b3 465 verbose_name="Organisme",
466 help_text="Si détaché (DET) ou \
6e4600ef 467 mis à disposition (MAD), \
468 préciser l'organisme.",
469 null=True, blank=True)
470
83b7692b 471 # Recrutement
2d4d2fcf 472 remplacement = models.BooleanField(default=False)
83b7692b 473 statut_residence = models.CharField(max_length=10, default='local',
8c1ae2b3 474 verbose_name="Statut",
2d4d2fcf 475 choices=STATUT_RESIDENCE_CHOICES)
83b7692b 476
477 # Rémunération
6e4600ef 478 classement = models.ForeignKey('Classement', db_column='classement',
479 related_name='+',
480 null=True, blank=True)
2d4d2fcf 481 regime_travail = models.DecimalField(max_digits=12,
482 decimal_places=2,
483 default=REGIME_TRAVAIL_DEFAULT,
8c1ae2b3 484 verbose_name="Régime de travail",
485 help_text="% du temps complet")
83b7692b 486 regime_travail_nb_heure_semaine = models.DecimalField(max_digits=12,
2d4d2fcf 487 decimal_places=2,
488 default=REGIME_TRAVAIL_NB_HEURE_SEMAINE_DEFAULT,
8c1ae2b3 489 verbose_name="Nb. heures par semaine")
7abc6d45 490
491 # Occupation du Poste par cet Employe (anciennement "mandat")
8c1ae2b3 492 date_debut = models.DateField(verbose_name="Date de début d'occupation \
6e4600ef 493 de poste",
494 help_text=HELP_TEXT_DATE)
8c1ae2b3 495 date_fin = models.DateField(verbose_name="Date de fin d'occupation \
6e4600ef 496 de poste",
2d4d2fcf 497 help_text=HELP_TEXT_DATE,
6e4600ef 498 null=True, blank=True)
e9bbd6ba 499
2d4d2fcf 500 # Comptes
501 # TODO?
83b7692b 502
6e4600ef 503 class Meta:
37868f0b 504 abstract = True
67666927 505 ordering = ['employe__nom_affichage', 'employe__nom', 'poste__nom']
8c1ae2b3 506 verbose_name = "Dossier"
507 verbose_name_plural = "Dossiers"
6e4600ef 508
83b7692b 509 def __unicode__(self):
8c1ae2b3 510 poste = self.poste.nom
511 if self.employe.genre == 'F':
512 poste = self.poste.nom_feminin
513 return u'%s - %s' % (self.employe, poste)
83b7692b 514
37868f0b
NC
515
516class Dossier(Dossier_):
517 __doc__ = Dossier_.__doc__
518
519
83b7692b 520class DossierPiece(models.Model):
7abc6d45 521 """Documents relatifs au Dossier (à l'occupation de ce poste par employé).
522 Ex.: Lettre de motivation.
523 """
8c1ae2b3 524 dossier = models.ForeignKey('Dossier', db_column='dossier',
6e4600ef 525 related_name='+')
8c1ae2b3 526 nom = models.CharField(verbose_name="Nom", max_length=255)
527 fichier = models.FileField(verbose_name="Fichier",
83b7692b 528 upload_to=dossier_piece_dispatch,
529 storage=storage_prive)
530
6e4600ef 531 class Meta:
532 ordering = ['nom']
533
534 def __unicode__(self):
535 return u'%s' % (self.nom)
536
07b40eda 537class DossierCommentaire(Commentaire):
8c1ae2b3 538 dossier = models.ForeignKey('Dossier', db_column='dossier',
6e4600ef 539 related_name='+')
83b7692b 540
1d0f4eef
OL
541class DossierComparaison(models.Model):
542 """
543 Photo d'une comparaison salariale au moment de l'embauche.
544 """
545 dossier = models.ForeignKey('Dossier', related_name='comparaisons')
546 implantation = models.ForeignKey(ref.Implantation, null=True, blank=True)
547 poste = models.CharField(max_length=255, null=True, blank=True)
548 personne = models.CharField(max_length=255, null=True, blank=True)
549 montant = models.IntegerField(null=True)
93494cf1 550 devise = models.ForeignKey('Devise', default=5, related_name='+', null=True, blank=True)
1d0f4eef
OL
551
552 def taux_devise(self):
553 liste_taux = self.devise.tauxchange_set.order_by('-annee').filter(implantation=self.dossier.poste.implantation)
554 if len(liste_taux) == 0:
555 raise Exception(u"La devise %s n'a pas de taux pour l'implantation %s" % (self.devise, self.dossier.poste.implantation))
556 else:
557 return liste_taux[0].taux
558
559 def montant_euros(self):
560 return round(float(self.montant) * float(self.taux_devise()), 2)
561
2d4d2fcf 562
07b40eda 563### RÉMUNÉRATION
e9bbd6ba 564
2d4d2fcf 565class RemunerationMixin(Metadata):
9afaa55e 566 # Identification
6e7c919b
NC
567 dossier = models.ForeignKey('Dossier', db_column='dossier',
568 related_name='%(app_label)s_%(class)s_remunerations')
83b7692b 569 type = models.ForeignKey('TypeRemuneration', db_column='type',
8c1ae2b3 570 related_name='+',
571 verbose_name="Type de rémunération")
7abc6d45 572 type_revalorisation = models.ForeignKey('TypeRevalorisation',
573 db_column='type_revalorisation',
6e4600ef 574 related_name='+',
8c1ae2b3 575 verbose_name="Type de revalorisation",
7abc6d45 576 null=True, blank=True)
50fa9bc1 577 montant = models.FloatField(null=True, blank=True,
6e4600ef 578 default=0)
2d4d2fcf 579 # Annuel (12 mois, 52 semaines, 364 jours?)
c589d980 580 devise = models.ForeignKey('Devise', to_field='id',
6e4600ef 581 db_column='devise', related_name='+',
582 default=5)
2d4d2fcf 583 # commentaire = precision
584 commentaire = models.CharField(max_length=255, null=True, blank=True)
585 # date_debut = anciennement date_effectif
8c1ae2b3 586 date_debut = models.DateField(help_text=HELP_TEXT_DATE,
587 verbose_name="Date de début",
6e4600ef 588 null=True, blank=True)
589 date_fin = models.DateField(help_text=HELP_TEXT_DATE,
8c1ae2b3 590 verbose_name="Date de fin",
6e4600ef 591 null=True, blank=True)
83b7692b 592
2d4d2fcf 593 class Meta:
594 abstract = True
6e4600ef 595 ordering = ['type__nom', '-date_fin']
596
597 def __unicode__(self):
598 return u'%s %s (%s)' % (self.montant, self.devise.code, self.type.nom)
2d4d2fcf 599
6e7c919b 600class Remuneration_(RemunerationMixin):
2d4d2fcf 601 """Structure de rémunération (données budgétaires) en situation normale
602 pour un Dossier. Si un Evenement existe, utiliser la structure de
603 rémunération EvenementRemuneration de cet événement.
604 """
83b7692b 605
606 def montant_mois(self):
607 return round(self.montant / 12, 2)
608
609 def taux_devise(self):
610 return self.devise.tauxchange_set.order_by('-annee').all()[0].taux
611
612 def montant_euro(self):
613 return round(float(self.montant) / float(self.taux_devise()), 2)
614
615 def montant_euro_mois(self):
616 return round(self.montant_euro() / 12, 2)
9afaa55e 617
618 def __unicode__(self):
619 try:
620 devise = self.devise.code
621 except:
622 devise = "???"
623 return "%s %s" % (self.montant, devise)
83b7692b 624
6e7c919b
NC
625 class Meta:
626 abstract = True
8c1ae2b3 627 verbose_name = "Rémunération"
628 verbose_name_plural = "Rémunérations"
6e7c919b
NC
629
630
631class Remuneration(Remuneration_):
632 __doc__ = Remuneration_.__doc__
633
2d4d2fcf 634
635### CONTRATS
636
637class Contrat(Metadata):
638 """Document juridique qui encadre la relation de travail d'un Employe
639 pour un Poste particulier. Pour un Dossier (qui documente cette
640 relation de travail) plusieurs contrats peuvent être associés.
641 """
6e4600ef 642 dossier = models.ForeignKey('Dossier', db_column='dossier',
643 related_name='+')
644 type_contrat = models.ForeignKey('TypeContrat', db_column='type_contrat',
8c1ae2b3 645 related_name='+',
646 verbose_name="Type de contrat")
647 date_debut = models.DateField(help_text=HELP_TEXT_DATE,
648 verbose_name="Date de début")
6e4600ef 649 date_fin = models.DateField(help_text=HELP_TEXT_DATE,
8c1ae2b3 650 verbose_name="Date de fin",
6e4600ef 651 null=True, blank=True)
652
653 class Meta:
654 ordering = ['dossier__employe__nom_affichage']
8c1ae2b3 655 verbose_name = "Contrat"
656 verbose_name_plural = "Contrats"
6e4600ef 657
658 def __unicode__(self):
8c1ae2b3 659 return u'%s - %s' % (self.dossier, self.id)
6e4600ef 660
661# TODO? class ContratPiece(models.Model):
2d4d2fcf 662
663
664### ÉVÉNEMENTS
665
6e7c919b 666class Evenement_(Metadata):
6e4600ef 667 """Un Evenement sert à déclarer une situation temporaire (exceptionnelle)
668 d'un Dossier qui vient altérer des informations normales liées à un Dossier
669 (ex.: la Remuneration).
670
671 Ex.: congé de maternité, maladie...
672
673 Lors de ces situations exceptionnelles, l'Employe a un régime de travail
674 différent et une rémunération en conséquence. On souhaite toutefois
675 conserver le Dossier intact afin d'éviter une re-saisie des données lors
676 du retour à la normale.
677 """
8c1ae2b3 678 dossier = models.ForeignKey('Dossier', db_column='dossier',
6e4600ef 679 related_name='+')
680 nom = models.CharField(max_length=255)
8c1ae2b3 681 date_debut = models.DateField(help_text=HELP_TEXT_DATE,
682 verbose_name="Date de début")
683 date_fin = models.DateField(help_text=HELP_TEXT_DATE,
684 verbose_name="Date de fin",
6e4600ef 685 null=True, blank=True)
6e7c919b 686
6e4600ef 687 class Meta:
6e7c919b 688 abstract = True
6e4600ef 689 ordering = ['nom']
8c1ae2b3 690 verbose_name = "Évènement"
691 verbose_name_plural = "Évènements"
6e4600ef 692
693 def __unicode__(self):
694 return u'%s' % (self.nom)
6e7c919b
NC
695
696
697class Evenement(Evenement_):
698 __doc__ = Evenement_.__doc__
699
2d4d2fcf 700
6e7c919b 701class EvenementRemuneration_(RemunerationMixin):
6e4600ef 702 """Structure de rémunération liée à un Evenement qui remplace
703 temporairement la Remuneration normale d'un Dossier, pour toute la durée
704 de l'Evenement.
705 """
706 evenement = models.ForeignKey("Evenement", db_column='evenement',
8c1ae2b3 707 related_name='+',
708 verbose_name="Évènement")
709 # TODO : le champ dossier hérité de Remuneration doit être dérivé
710 # de l'Evenement associé
83b7692b 711
6e7c919b
NC
712 class Meta:
713 abstract = True
8c1ae2b3 714 ordering = ['evenement', 'type__nom', '-date_fin']
715 verbose_name = "Évènement - rémunération"
716 verbose_name_plural = "Évènements - rémunérations"
6e7c919b
NC
717
718
719class EvenementRemuneration(EvenementRemuneration_):
720 __doc__ = EvenementRemuneration_.__doc__
721
83b7692b 722
723### RÉFÉRENCES RH
724
6e4600ef 725class FamilleEmploi(Metadata):
726 """Catégorie utilisée dans la gestion des Postes.
727 Catégorie supérieure à TypePoste.
728 """
e9bbd6ba 729 nom = models.CharField(max_length=255)
6e4600ef 730
8c1ae2b3 731 class Meta:
732 ordering = ['nom']
733 verbose_name = "Famille d'emploi"
734 verbose_name_plural = "Familles d'emploi"
735
6e4600ef 736 def __unicode__(self):
737 return u'%s' % (self.nom)
e9bbd6ba 738
6e4600ef 739class TypePoste(Metadata):
740 """Catégorie de Poste.
741 """
e9bbd6ba 742 nom = models.CharField(max_length=255)
8c1ae2b3 743 nom_feminin = models.CharField(max_length=255,
744 verbose_name="Nom féminin")
6e4600ef 745
8c1ae2b3 746 is_responsable = models.BooleanField(default=False,
747 verbose_name="Poste de responsabilité")
9afaa55e 748 famille_emploi = models.ForeignKey('FamilleEmploi',
6e4600ef 749 db_column='famille_emploi',
8c1ae2b3 750 related_name='+',
751 verbose_name="Famille d'emploi")
e9bbd6ba 752
6e4600ef 753 class Meta:
754 ordering = ['nom']
8c1ae2b3 755 verbose_name = "Type de poste"
756 verbose_name_plural = "Types de poste"
6e4600ef 757
e9bbd6ba 758 def __unicode__(self):
6e4600ef 759 return u'%s' % (self.nom)
e9bbd6ba 760
761
762TYPE_PAIEMENT_CHOICES = (
763 ('Régulier', 'Régulier'),
764 ('Ponctuel', 'Ponctuel'),
765)
766
767NATURE_REMUNERATION_CHOICES = (
768 ('Accessoire', 'Accessoire'),
769 ('Charges', 'Charges'),
770 ('Indemnité', 'Indemnité'),
7abc6d45 771 ('RAS', 'Rémunération autre source'),
e9bbd6ba 772 ('Traitement', 'Traitement'),
773)
774
6e4600ef 775class TypeRemuneration(Metadata):
776 """Catégorie de Remuneration.
777 """
e9bbd6ba 778 nom = models.CharField(max_length=255)
9afaa55e 779 type_paiement = models.CharField(max_length=30,
8c1ae2b3 780 choices=TYPE_PAIEMENT_CHOICES,
781 verbose_name="Type de paiement")
9afaa55e 782 nature_remuneration = models.CharField(max_length=30,
8c1ae2b3 783 choices=NATURE_REMUNERATION_CHOICES,
784 verbose_name="Nature de la rémunération")
785
786 class Meta:
787 ordering = ['nom']
788 verbose_name = "Type de rémunération"
789 verbose_name_plural = "Types de rémunération"
9afaa55e 790
791 def __unicode__(self):
6e4600ef 792 return u'%s' % (self.nom)
e9bbd6ba 793
6e4600ef 794class TypeRevalorisation(Metadata):
7abc6d45 795 """Justification du changement de la Remuneration.
6e4600ef 796 (Actuellement utilisé dans aucun traitement informatique.)
7abc6d45 797 """
e9bbd6ba 798 nom = models.CharField(max_length=255)
8c1ae2b3 799
800 class Meta:
801 ordering = ['nom']
802 verbose_name = "Type de revalorisation"
803 verbose_name_plural = "Types de revalorisation"
e9bbd6ba 804
805 def __unicode__(self):
6e4600ef 806 return u'%s' % (self.nom)
807
808class Service(Metadata):
809 """Unité administrative où les Postes sont rattachés.
810 """
811 nom = models.CharField(max_length=255)
9afaa55e 812
813 class Meta:
814 ordering = ['nom']
8c1ae2b3 815 verbose_name = "Service"
816 verbose_name_plural = "Services"
e9bbd6ba 817
6e4600ef 818 def __unicode__(self):
819 return u'%s' % (self.nom)
820
e9bbd6ba 821
822TYPE_ORGANISME_CHOICES = (
823 ('MAD', 'Mise à disposition'),
824 ('DET', 'Détachement'),
825)
826
6e4600ef 827class OrganismeBstg(Metadata):
828 """Organisation d'où provient un Employe mis à disposition (MAD) de
829 ou détaché (DET) à l'AUF à titre gratuit.
830
831 (BSTG = bien et service à titre gratuit.)
832 """
e9bbd6ba 833 nom = models.CharField(max_length=255)
834 type = models.CharField(max_length=10, choices=TYPE_ORGANISME_CHOICES)
6e4600ef 835 pays = models.ForeignKey(ref.Pays, to_field='code',
836 db_column='pays',
837 related_name='organismes_bstg',
838 null=True, blank=True)
9afaa55e 839
840 class Meta:
841 ordering = ['type', 'nom']
8c1ae2b3 842 verbose_name = "Organisme BSTG"
843 verbose_name_plural = "Organismes BSTG"
9afaa55e 844
6e4600ef 845 def __unicode__(self):
8c1ae2b3 846 return u'%s (%s)' % (self.nom, self.get_type_display())
83b7692b 847
6e4600ef 848class Statut(Metadata):
849 """Statut de l'Employe dans le cadre d'un Dossier particulier.
850 """
9afaa55e 851 # Identification
e9bbd6ba 852 code = models.CharField(max_length=25, unique=True)
853 nom = models.CharField(max_length=255)
e9bbd6ba 854
6e4600ef 855 class Meta:
856 ordering = ['code']
8c1ae2b3 857 verbose_name = "Statut d'employé"
858 verbose_name_plural = "Statuts d'employé"
6e4600ef 859
9afaa55e 860 def __unicode__(self):
861 return u'%s : %s' % (self.code, self.nom)
862
83b7692b 863
e9bbd6ba 864TYPE_CLASSEMENT_CHOICES = (
6e4600ef 865 ('S', 'S -Soutien'),
866 ('T', 'T - Technicien'),
867 ('P', 'P - Professionel'),
868 ('C', 'C - Cadre'),
869 ('D', 'D - Direction'),
870 ('SO', 'SO - Sans objet [expatriés]'),
871 ('HG', 'HG - Hors grille [direction]'),
e9bbd6ba 872)
83b7692b 873
6e7c919b
NC
874
875class Classement_(Metadata):
6e4600ef 876 """Éléments de classement de la
877 "Grille générique de classement hiérarchique".
878
879 Utile pour connaître, pour un Dossier, le salaire de base théorique lié au
880 classement dans la grille. Le classement donne le coefficient utilisé dans:
881
882 salaire de base = coefficient * valeur du point de l'Implantation du Poste
883 """
9afaa55e 884 # Identification
e9bbd6ba 885 type = models.CharField(max_length=10, choices=TYPE_CLASSEMENT_CHOICES)
8c1ae2b3 886 echelon = models.IntegerField(verbose_name="Échelon")
887 degre = models.IntegerField(verbose_name="Degré")
888 coefficient = models.FloatField(default=0, verbose_name="Coéfficient")
9afaa55e 889 # Méta
6e4600ef 890 # annee # au lieu de date_debut et date_fin
891 commentaire = models.TextField(null=True, blank=True)
892
893 class Meta:
6e7c919b 894 abstract = True
6e4600ef 895 ordering = ['type','echelon','degre','coefficient']
8c1ae2b3 896 verbose_name = "Classement"
897 verbose_name_plural = "Classements"
e9bbd6ba 898
899 def __unicode__(self):
900 return u'%s.%s.%s (%s)' % (self.type, self.echelon, self.degre,
901 self.coefficient)
902
6e7c919b
NC
903class Classement(Classement_):
904 __doc__ = Classement_.__doc__
905
906
907class TauxChange_(Metadata):
7abc6d45 908 """Taux de change de la devise vers l'euro (EUR)
6e4600ef 909 pour chaque année budgétaire.
7abc6d45 910 """
9afaa55e 911 # Identification
8c1ae2b3 912 devise = models.ForeignKey('Devise', db_column='devise',
6e4600ef 913 related_name='+')
8c1ae2b3 914 annee = models.IntegerField(verbose_name="Année")
915 taux = models.FloatField(verbose_name="Taux vers l'euro")
6e7c919b 916
6e4600ef 917 class Meta:
6e7c919b 918 abstract = True
8c1ae2b3 919 ordering = ['-annee', 'devise__code']
920 verbose_name = "Taux de change"
921 verbose_name_plural = "Taux de change"
6e4600ef 922
923 def __unicode__(self):
8c1ae2b3 924 return u'%s : %s € (%s)' % (self.devise, self.taux, self.annee)
e9bbd6ba 925
6e7c919b
NC
926
927class TauxChange(TauxChange_):
928 __doc__ = TauxChange_.__doc__
929
930
931class ValeurPoint_(Metadata):
6e4600ef 932 """Utile pour connaître, pour un Dossier, le salaire de base théorique lié
933 au classement dans la grille. La ValeurPoint s'obtient par l'implantation
8c1ae2b3 934 du Poste de ce Dossier : dossier.poste.implantation (pseudo code).
6e4600ef 935
936 salaire de base = coefficient * valeur du point de l'Implantation du Poste
937 """
9afaa55e 938 valeur = models.FloatField()
6e4600ef 939 devise = models.ForeignKey('Devise', db_column='devise',
940 related_name='+', default=5)
83b7692b 941 implantation = models.ForeignKey(ref.Implantation,
6e7c919b
NC
942 db_column='implantation',
943 related_name='%(app_label)s_valeur_point')
9afaa55e 944 # Méta
e9bbd6ba 945 annee = models.IntegerField()
9afaa55e 946
6e4600ef 947 class Meta:
6e7c919b 948 abstract = True
8c1ae2b3 949 ordering = ['annee']
950 verbose_name = "Valeur du point"
951 verbose_name_plural = "Valeurs du point"
6e0bbb73 952
9afaa55e 953 def __unicode__(self):
8c1ae2b3 954 return u'%s %s (%s)' % (self.valeur, self.devise, self.annee)
6e7c919b
NC
955
956
957class ValeurPoint(ValeurPoint_):
958 __doc__ = ValeurPoint_.__doc__
959
e9bbd6ba 960
6e4600ef 961class Devise(Metadata):
962 """Devise monétaire.
963 """
e9bbd6ba 964 code = models.CharField(max_length=10, unique=True)
965 nom = models.CharField(max_length=255)
966
6e4600ef 967 class Meta:
968 ordering = ['code']
8c1ae2b3 969 verbose_name = "Devise"
970 verbose_name_plural = "Devises"
6e4600ef 971
e9bbd6ba 972 def __unicode__(self):
973 return u'%s - %s' % (self.code, self.nom)
974
6e4600ef 975class TypeContrat(Metadata):
976 """Type de contrat.
977 """
e9bbd6ba 978 nom = models.CharField(max_length=255)
6e4600ef 979 nom_long = models.CharField(max_length=255)
49f9f116 980
8c1ae2b3 981 class Meta:
982 ordering = ['nom']
983 verbose_name = "Type de contrat"
984 verbose_name_plural = "Types de contrat"
985
9afaa55e 986 def __unicode__(self):
987 return u'%s' % (self.nom)
30be56d5 988
2d4d2fcf 989
990### AUTRES
991
6e4600ef 992class ResponsableImplantation(Metadata):
30be56d5 993 """Le responsable d'une implantation.
994 Anciennement géré sur le Dossier du responsable.
995 """
6e4600ef 996 employe = models.ForeignKey('Employe', db_column='employe',
997 related_name='+',
998 null=True, blank=True)
999 implantation = models.ForeignKey(ref.Implantation,
1000 db_column='implantation', related_name='+',
1001 unique=True)
30be56d5 1002
1003 def __unicode__(self):
1004 return u'%s : %s' % (self.implantation, self.employe)
1005
1006 class Meta:
1007 ordering = ['implantation__nom']
8c1ae2b3 1008 verbose_name = "Responsable d'implantation"
1009 verbose_name_plural = "Responsables d'implantation"