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