regénéré les migrations budget
[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)
8277a35b 89 service = models.ForeignKey('Service', db_column='service', null=True,
6e4600ef 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',
8277a35b 94 related_name='+', null=True,
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,
8277a35b 100 default=REGIME_TRAVAIL_DEFAULT, null=True,
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,
8277a35b 104 decimal_places=2, null=True,
2d4d2fcf 105 default=REGIME_TRAVAIL_NB_HEURE_SEMAINE_DEFAULT,
8c1ae2b3 106 verbose_name="Nb. heures par semaine")
83b7692b 107
108 # Recrutement
8277a35b
NC
109 local = models.NullBooleanField(verbose_name="Local", default=True,
110 null=True, blank=True)
111 expatrie = models.NullBooleanField(verbose_name="Expatrié", default=False,
112 null=True, blank=True)
113 mise_a_disposition = models.NullBooleanField(
8c1ae2b3 114 verbose_name="Mise à disposition",
8277a35b
NC
115 null=True, default=False)
116 appel = models.CharField(max_length=10, null=True,
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)
8277a35b 134 devise_min = models.ForeignKey('Devise', db_column='devise_min', null=True,
6e4600ef 135 related_name='+', default=5)
8277a35b 136 devise_max = models.ForeignKey('Devise', db_column='devise_max', null=True,
6e4600ef 137 related_name='+', default=5)
83b7692b 138 salaire_min = models.DecimalField(max_digits=12, decimal_places=2,
8277a35b 139 null=True, default=0)
83b7692b 140 salaire_max = models.DecimalField(max_digits=12, decimal_places=2,
8277a35b 141 null=True, default=0)
83b7692b 142 indemn_min = models.DecimalField(max_digits=12, decimal_places=2,
8277a35b 143 null=True, default=0)
83b7692b 144 indemn_max = models.DecimalField(max_digits=12, decimal_places=2,
8277a35b 145 null=True, default=0)
83b7692b 146 autre_min = models.DecimalField(max_digits=12, decimal_places=2,
8277a35b 147 null=True, default=0)
83b7692b 148 autre_max = models.DecimalField(max_digits=12, decimal_places=2,
8277a35b 149 null=True, default=0)
83b7692b 150
151 # Comparatifs de rémunération
8277a35b 152 devise_comparaison = models.ForeignKey('Devise', null=True,
6e4600ef 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
8277a35b 182 date_debut = models.DateField(verbose_name="Date de début", null=True,
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)
8277a35b
NC
455 statut = models.ForeignKey('Statut', related_name='+', default=3,
456 null=True)
83b7692b 457 organisme_bstg = models.ForeignKey('OrganismeBstg',
6e4600ef 458 db_column='organisme_bstg',
459 related_name='+',
8c1ae2b3 460 verbose_name="Organisme",
461 help_text="Si détaché (DET) ou \
6e4600ef 462 mis à disposition (MAD), \
463 préciser l'organisme.",
464 null=True, blank=True)
465
83b7692b 466 # Recrutement
2d4d2fcf 467 remplacement = models.BooleanField(default=False)
83b7692b 468 statut_residence = models.CharField(max_length=10, default='local',
8277a35b 469 verbose_name="Statut", null=True,
2d4d2fcf 470 choices=STATUT_RESIDENCE_CHOICES)
83b7692b 471
472 # Rémunération
6e4600ef 473 classement = models.ForeignKey('Classement', db_column='classement',
474 related_name='+',
475 null=True, blank=True)
8277a35b 476 regime_travail = models.DecimalField(max_digits=12, null=True,
2d4d2fcf 477 decimal_places=2,
478 default=REGIME_TRAVAIL_DEFAULT,
8c1ae2b3 479 verbose_name="Régime de travail",
480 help_text="% du temps complet")
83b7692b 481 regime_travail_nb_heure_semaine = models.DecimalField(max_digits=12,
8277a35b 482 decimal_places=2, null=True,
2d4d2fcf 483 default=REGIME_TRAVAIL_NB_HEURE_SEMAINE_DEFAULT,
8c1ae2b3 484 verbose_name="Nb. heures par semaine")
7abc6d45 485
486 # Occupation du Poste par cet Employe (anciennement "mandat")
8c1ae2b3 487 date_debut = models.DateField(verbose_name="Date de début d'occupation \
6e4600ef 488 de poste",
489 help_text=HELP_TEXT_DATE)
8c1ae2b3 490 date_fin = models.DateField(verbose_name="Date de fin d'occupation \
6e4600ef 491 de poste",
2d4d2fcf 492 help_text=HELP_TEXT_DATE,
6e4600ef 493 null=True, blank=True)
e9bbd6ba 494
2d4d2fcf 495 # Comptes
496 # TODO?
83b7692b 497
6e4600ef 498 class Meta:
769a0755 499 abstract = True
67666927 500 ordering = ['employe__nom_affichage', 'employe__nom', 'poste__nom']
8c1ae2b3 501 verbose_name = "Dossier"
502 verbose_name_plural = "Dossiers"
6e4600ef 503
83b7692b 504 def __unicode__(self):
8c1ae2b3 505 poste = self.poste.nom
506 if self.employe.genre == 'F':
507 poste = self.poste.nom_feminin
508 return u'%s - %s' % (self.employe, poste)
83b7692b 509
37868f0b
NC
510
511class Dossier(Dossier_):
512 __doc__ = Dossier_.__doc__
513
83b7692b 514
769a0755
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
2d4d2fcf 541
07b40eda 542### RÉMUNÉRATION
e9bbd6ba 543
2d4d2fcf 544class RemunerationMixin(Metadata):
9afaa55e 545 # Identification
6e7c919b
NC
546 dossier = models.ForeignKey('Dossier', db_column='dossier',
547 related_name='%(app_label)s_%(class)s_remunerations')
83b7692b 548 type = models.ForeignKey('TypeRemuneration', db_column='type',
8c1ae2b3 549 related_name='+',
550 verbose_name="Type de rémunération")
7abc6d45 551 type_revalorisation = models.ForeignKey('TypeRevalorisation',
552 db_column='type_revalorisation',
6e4600ef 553 related_name='+',
8c1ae2b3 554 verbose_name="Type de revalorisation",
7abc6d45 555 null=True, blank=True)
50fa9bc1 556 montant = models.FloatField(null=True, blank=True,
6e4600ef 557 default=0)
2d4d2fcf 558 # Annuel (12 mois, 52 semaines, 364 jours?)
c589d980 559 devise = models.ForeignKey('Devise', to_field='id',
6e4600ef 560 db_column='devise', related_name='+',
561 default=5)
2d4d2fcf 562 # commentaire = precision
563 commentaire = models.CharField(max_length=255, null=True, blank=True)
564 # date_debut = anciennement date_effectif
8c1ae2b3 565 date_debut = models.DateField(help_text=HELP_TEXT_DATE,
566 verbose_name="Date de début",
6e4600ef 567 null=True, blank=True)
568 date_fin = models.DateField(help_text=HELP_TEXT_DATE,
8c1ae2b3 569 verbose_name="Date de fin",
6e4600ef 570 null=True, blank=True)
83b7692b 571
2d4d2fcf 572 class Meta:
573 abstract = True
6e4600ef 574 ordering = ['type__nom', '-date_fin']
575
576 def __unicode__(self):
577 return u'%s %s (%s)' % (self.montant, self.devise.code, self.type.nom)
2d4d2fcf 578
f31ddfa0 579class Remuneration_(RemunerationMixin):
2d4d2fcf 580 """Structure de rémunération (données budgétaires) en situation normale
581 pour un Dossier. Si un Evenement existe, utiliser la structure de
582 rémunération EvenementRemuneration de cet événement.
583 """
83b7692b 584
585 def montant_mois(self):
586 return round(self.montant / 12, 2)
587
588 def taux_devise(self):
589 return self.devise.tauxchange_set.order_by('-annee').all()[0].taux
590
591 def montant_euro(self):
592 return round(float(self.montant) / float(self.taux_devise()), 2)
593
594 def montant_euro_mois(self):
595 return round(self.montant_euro() / 12, 2)
9afaa55e 596
597 def __unicode__(self):
598 try:
599 devise = self.devise.code
600 except:
601 devise = "???"
602 return "%s %s" % (self.montant, devise)
83b7692b 603
f31ddfa0
NC
604 class Meta:
605 abstract = True
8c1ae2b3 606 verbose_name = "Rémunération"
607 verbose_name_plural = "Rémunérations"
f31ddfa0
NC
608
609
610class Remuneration(Remuneration_):
611 __doc__ = Remuneration_.__doc__
612
2d4d2fcf 613
614### CONTRATS
615
616class Contrat(Metadata):
617 """Document juridique qui encadre la relation de travail d'un Employe
618 pour un Poste particulier. Pour un Dossier (qui documente cette
619 relation de travail) plusieurs contrats peuvent être associés.
620 """
6e4600ef 621 dossier = models.ForeignKey('Dossier', db_column='dossier',
622 related_name='+')
623 type_contrat = models.ForeignKey('TypeContrat', db_column='type_contrat',
8c1ae2b3 624 related_name='+',
625 verbose_name="Type de contrat")
626 date_debut = models.DateField(help_text=HELP_TEXT_DATE,
627 verbose_name="Date de début")
6e4600ef 628 date_fin = models.DateField(help_text=HELP_TEXT_DATE,
8c1ae2b3 629 verbose_name="Date de fin",
6e4600ef 630 null=True, blank=True)
631
632 class Meta:
633 ordering = ['dossier__employe__nom_affichage']
8c1ae2b3 634 verbose_name = "Contrat"
635 verbose_name_plural = "Contrats"
6e4600ef 636
637 def __unicode__(self):
8c1ae2b3 638 return u'%s - %s' % (self.dossier, self.id)
6e4600ef 639
640# TODO? class ContratPiece(models.Model):
2d4d2fcf 641
642
643### ÉVÉNEMENTS
644
f31ddfa0 645class Evenement_(Metadata):
6e4600ef 646 """Un Evenement sert à déclarer une situation temporaire (exceptionnelle)
647 d'un Dossier qui vient altérer des informations normales liées à un Dossier
648 (ex.: la Remuneration).
649
650 Ex.: congé de maternité, maladie...
651
652 Lors de ces situations exceptionnelles, l'Employe a un régime de travail
653 différent et une rémunération en conséquence. On souhaite toutefois
654 conserver le Dossier intact afin d'éviter une re-saisie des données lors
655 du retour à la normale.
656 """
8c1ae2b3 657 dossier = models.ForeignKey('Dossier', db_column='dossier',
6e4600ef 658 related_name='+')
659 nom = models.CharField(max_length=255)
8c1ae2b3 660 date_debut = models.DateField(help_text=HELP_TEXT_DATE,
661 verbose_name="Date de début")
662 date_fin = models.DateField(help_text=HELP_TEXT_DATE,
663 verbose_name="Date de fin",
6e4600ef 664 null=True, blank=True)
f31ddfa0 665
6e4600ef 666 class Meta:
f31ddfa0 667 abstract = True
6e4600ef 668 ordering = ['nom']
8c1ae2b3 669 verbose_name = "Évènement"
670 verbose_name_plural = "Évènements"
6e4600ef 671
672 def __unicode__(self):
673 return u'%s' % (self.nom)
f31ddfa0
NC
674
675
676class Evenement(Evenement_):
677 __doc__ = Evenement_.__doc__
678
2d4d2fcf 679
f31ddfa0 680class EvenementRemuneration_(RemunerationMixin):
6e4600ef 681 """Structure de rémunération liée à un Evenement qui remplace
682 temporairement la Remuneration normale d'un Dossier, pour toute la durée
683 de l'Evenement.
684 """
685 evenement = models.ForeignKey("Evenement", db_column='evenement',
8c1ae2b3 686 related_name='+',
687 verbose_name="Évènement")
688 # TODO : le champ dossier hérité de Remuneration doit être dérivé
689 # de l'Evenement associé
83b7692b 690
6e7c919b
NC
691 class Meta:
692 abstract = True
8c1ae2b3 693 ordering = ['evenement', 'type__nom', '-date_fin']
694 verbose_name = "Évènement - rémunération"
695 verbose_name_plural = "Évènements - rémunérations"
6e7c919b
NC
696
697
698class EvenementRemuneration(EvenementRemuneration_):
699 __doc__ = EvenementRemuneration_.__doc__
83b7692b 700
f31ddfa0
NC
701 class Meta:
702 abstract = True
703
704
705class EvenementRemuneration(EvenementRemuneration_):
706 __doc__ = EvenementRemuneration_.__doc__
707
83b7692b 708
709### RÉFÉRENCES RH
710
6e4600ef 711class FamilleEmploi(Metadata):
712 """Catégorie utilisée dans la gestion des Postes.
713 Catégorie supérieure à TypePoste.
714 """
e9bbd6ba 715 nom = models.CharField(max_length=255)
6e4600ef 716
8c1ae2b3 717 class Meta:
718 ordering = ['nom']
719 verbose_name = "Famille d'emploi"
720 verbose_name_plural = "Familles d'emploi"
721
6e4600ef 722 def __unicode__(self):
723 return u'%s' % (self.nom)
e9bbd6ba 724
6e4600ef 725class TypePoste(Metadata):
726 """Catégorie de Poste.
727 """
e9bbd6ba 728 nom = models.CharField(max_length=255)
8c1ae2b3 729 nom_feminin = models.CharField(max_length=255,
730 verbose_name="Nom féminin")
6e4600ef 731
8c1ae2b3 732 is_responsable = models.BooleanField(default=False,
733 verbose_name="Poste de responsabilité")
9afaa55e 734 famille_emploi = models.ForeignKey('FamilleEmploi',
6e4600ef 735 db_column='famille_emploi',
8c1ae2b3 736 related_name='+',
737 verbose_name="Famille d'emploi")
e9bbd6ba 738
6e4600ef 739 class Meta:
740 ordering = ['nom']
8c1ae2b3 741 verbose_name = "Type de poste"
742 verbose_name_plural = "Types de poste"
6e4600ef 743
e9bbd6ba 744 def __unicode__(self):
6e4600ef 745 return u'%s' % (self.nom)
e9bbd6ba 746
747
748TYPE_PAIEMENT_CHOICES = (
749 ('Régulier', 'Régulier'),
750 ('Ponctuel', 'Ponctuel'),
751)
752
753NATURE_REMUNERATION_CHOICES = (
754 ('Accessoire', 'Accessoire'),
755 ('Charges', 'Charges'),
756 ('Indemnité', 'Indemnité'),
7abc6d45 757 ('RAS', 'Rémunération autre source'),
e9bbd6ba 758 ('Traitement', 'Traitement'),
759)
760
6e4600ef 761class TypeRemuneration(Metadata):
762 """Catégorie de Remuneration.
763 """
e9bbd6ba 764 nom = models.CharField(max_length=255)
9afaa55e 765 type_paiement = models.CharField(max_length=30,
8c1ae2b3 766 choices=TYPE_PAIEMENT_CHOICES,
767 verbose_name="Type de paiement")
9afaa55e 768 nature_remuneration = models.CharField(max_length=30,
8c1ae2b3 769 choices=NATURE_REMUNERATION_CHOICES,
770 verbose_name="Nature de la rémunération")
771
772 class Meta:
773 ordering = ['nom']
774 verbose_name = "Type de rémunération"
775 verbose_name_plural = "Types de rémunération"
9afaa55e 776
777 def __unicode__(self):
6e4600ef 778 return u'%s' % (self.nom)
e9bbd6ba 779
6e4600ef 780class TypeRevalorisation(Metadata):
7abc6d45 781 """Justification du changement de la Remuneration.
6e4600ef 782 (Actuellement utilisé dans aucun traitement informatique.)
7abc6d45 783 """
e9bbd6ba 784 nom = models.CharField(max_length=255)
8c1ae2b3 785
786 class Meta:
787 ordering = ['nom']
788 verbose_name = "Type de revalorisation"
789 verbose_name_plural = "Types de revalorisation"
e9bbd6ba 790
791 def __unicode__(self):
6e4600ef 792 return u'%s' % (self.nom)
793
794class Service(Metadata):
795 """Unité administrative où les Postes sont rattachés.
796 """
797 nom = models.CharField(max_length=255)
9afaa55e 798
799 class Meta:
800 ordering = ['nom']
8c1ae2b3 801 verbose_name = "Service"
802 verbose_name_plural = "Services"
e9bbd6ba 803
6e4600ef 804 def __unicode__(self):
805 return u'%s' % (self.nom)
806
e9bbd6ba 807
808TYPE_ORGANISME_CHOICES = (
809 ('MAD', 'Mise à disposition'),
810 ('DET', 'Détachement'),
811)
812
6e4600ef 813class OrganismeBstg(Metadata):
814 """Organisation d'où provient un Employe mis à disposition (MAD) de
815 ou détaché (DET) à l'AUF à titre gratuit.
816
817 (BSTG = bien et service à titre gratuit.)
818 """
e9bbd6ba 819 nom = models.CharField(max_length=255)
820 type = models.CharField(max_length=10, choices=TYPE_ORGANISME_CHOICES)
6e4600ef 821 pays = models.ForeignKey(ref.Pays, to_field='code',
822 db_column='pays',
823 related_name='organismes_bstg',
824 null=True, blank=True)
9afaa55e 825
826 class Meta:
827 ordering = ['type', 'nom']
8c1ae2b3 828 verbose_name = "Organisme BSTG"
829 verbose_name_plural = "Organismes BSTG"
9afaa55e 830
6e4600ef 831 def __unicode__(self):
8c1ae2b3 832 return u'%s (%s)' % (self.nom, self.get_type_display())
83b7692b 833
6e4600ef 834class Statut(Metadata):
835 """Statut de l'Employe dans le cadre d'un Dossier particulier.
836 """
9afaa55e 837 # Identification
e9bbd6ba 838 code = models.CharField(max_length=25, unique=True)
839 nom = models.CharField(max_length=255)
e9bbd6ba 840
6e4600ef 841 class Meta:
842 ordering = ['code']
8c1ae2b3 843 verbose_name = "Statut d'employé"
844 verbose_name_plural = "Statuts d'employé"
6e4600ef 845
9afaa55e 846 def __unicode__(self):
847 return u'%s : %s' % (self.code, self.nom)
848
83b7692b 849
e9bbd6ba 850TYPE_CLASSEMENT_CHOICES = (
6e4600ef 851 ('S', 'S -Soutien'),
852 ('T', 'T - Technicien'),
853 ('P', 'P - Professionel'),
854 ('C', 'C - Cadre'),
855 ('D', 'D - Direction'),
856 ('SO', 'SO - Sans objet [expatriés]'),
857 ('HG', 'HG - Hors grille [direction]'),
e9bbd6ba 858)
83b7692b 859
6e7c919b
NC
860
861class Classement_(Metadata):
6e4600ef 862 """Éléments de classement de la
863 "Grille générique de classement hiérarchique".
864
865 Utile pour connaître, pour un Dossier, le salaire de base théorique lié au
866 classement dans la grille. Le classement donne le coefficient utilisé dans:
867
868 salaire de base = coefficient * valeur du point de l'Implantation du Poste
869 """
9afaa55e 870 # Identification
e9bbd6ba 871 type = models.CharField(max_length=10, choices=TYPE_CLASSEMENT_CHOICES)
8c1ae2b3 872 echelon = models.IntegerField(verbose_name="Échelon")
873 degre = models.IntegerField(verbose_name="Degré")
8277a35b
NC
874 coefficient = models.FloatField(default=0, verbose_name="Coéfficient",
875 null=True)
9afaa55e 876 # Méta
6e4600ef 877 # annee # au lieu de date_debut et date_fin
878 commentaire = models.TextField(null=True, blank=True)
879
880 class Meta:
6e7c919b 881 abstract = True
6e4600ef 882 ordering = ['type','echelon','degre','coefficient']
8c1ae2b3 883 verbose_name = "Classement"
884 verbose_name_plural = "Classements"
e9bbd6ba 885
886 def __unicode__(self):
887 return u'%s.%s.%s (%s)' % (self.type, self.echelon, self.degre,
888 self.coefficient)
889
6e7c919b
NC
890class Classement(Classement_):
891 __doc__ = Classement_.__doc__
892
893
894class TauxChange_(Metadata):
7abc6d45 895 """Taux de change de la devise vers l'euro (EUR)
6e4600ef 896 pour chaque année budgétaire.
7abc6d45 897 """
9afaa55e 898 # Identification
8c1ae2b3 899 devise = models.ForeignKey('Devise', db_column='devise',
6e4600ef 900 related_name='+')
8c1ae2b3 901 annee = models.IntegerField(verbose_name="Année")
902 taux = models.FloatField(verbose_name="Taux vers l'euro")
6e7c919b 903
6e4600ef 904 class Meta:
6e7c919b 905 abstract = True
8c1ae2b3 906 ordering = ['-annee', 'devise__code']
907 verbose_name = "Taux de change"
908 verbose_name_plural = "Taux de change"
6e4600ef 909
910 def __unicode__(self):
8c1ae2b3 911 return u'%s : %s € (%s)' % (self.devise, self.taux, self.annee)
e9bbd6ba 912
e9bbd6ba 913
6e7c919b
NC
914class TauxChange(TauxChange_):
915 __doc__ = TauxChange_.__doc__
916
917
918class ValeurPoint_(Metadata):
6e4600ef 919 """Utile pour connaître, pour un Dossier, le salaire de base théorique lié
920 au classement dans la grille. La ValeurPoint s'obtient par l'implantation
8c1ae2b3 921 du Poste de ce Dossier : dossier.poste.implantation (pseudo code).
6e4600ef 922
923 salaire de base = coefficient * valeur du point de l'Implantation du Poste
924 """
8277a35b
NC
925 valeur = models.FloatField(null=True)
926 devise = models.ForeignKey('Devise', db_column='devise', null=True,
6e4600ef 927 related_name='+', default=5)
83b7692b 928 implantation = models.ForeignKey(ref.Implantation,
6e7c919b
NC
929 db_column='implantation',
930 related_name='%(app_label)s_valeur_point')
9afaa55e 931 # Méta
e9bbd6ba 932 annee = models.IntegerField()
9afaa55e 933
6e4600ef 934 class Meta:
935 ordering = ['annee', 'implantation__nom']
6e7c919b 936 abstract = True
8c1ae2b3 937 ordering = ['annee']
938 verbose_name = "Valeur du point"
939 verbose_name_plural = "Valeurs du point"
ee23ecbc 940
e9d7483c 941 # TODO : cette fonction n'était pas présente dans la branche dev, utilité?
ee23ecbc
NC
942 def get_tauxchange_courant(self):
943 """
944 Recherche le taux courant associé à la valeur d'un point.
945 Tous les taux de l'année courante sont chargés, pour optimiser un
946 affichage en liste. (On pourrait probablement améliorer le manager pour
947 lui greffer le taux courant sous forme de JOIN)
948 """
949 for tauxchange in self.tauxchange:
950 if tauxchange.implantation_id == self.implantation_id:
951 return tauxchange
952 return None
953
9afaa55e 954 def __unicode__(self):
8c1ae2b3 955 return u'%s %s (%s)' % (self.valeur, self.devise, self.annee)
6e7c919b
NC
956
957
958class ValeurPoint(ValeurPoint_):
959 __doc__ = ValeurPoint_.__doc__
960
e9bbd6ba 961
6e4600ef 962class Devise(Metadata):
963 """Devise monétaire.
964 """
e9bbd6ba 965 code = models.CharField(max_length=10, unique=True)
966 nom = models.CharField(max_length=255)
967
6e4600ef 968 class Meta:
969 ordering = ['code']
8c1ae2b3 970 verbose_name = "Devise"
971 verbose_name_plural = "Devises"
6e4600ef 972
e9bbd6ba 973 def __unicode__(self):
974 return u'%s - %s' % (self.code, self.nom)
975
6e4600ef 976class TypeContrat(Metadata):
977 """Type de contrat.
978 """
e9bbd6ba 979 nom = models.CharField(max_length=255)
6e4600ef 980 nom_long = models.CharField(max_length=255)
49f9f116 981
8c1ae2b3 982 class Meta:
983 ordering = ['nom']
984 verbose_name = "Type de contrat"
985 verbose_name_plural = "Types de contrat"
986
9afaa55e 987 def __unicode__(self):
988 return u'%s' % (self.nom)
30be56d5 989
2d4d2fcf 990
991### AUTRES
992
6e4600ef 993class ResponsableImplantation(Metadata):
30be56d5 994 """Le responsable d'une implantation.
995 Anciennement géré sur le Dossier du responsable.
996 """
6e4600ef 997 employe = models.ForeignKey('Employe', db_column='employe',
998 related_name='+',
999 null=True, blank=True)
1000 implantation = models.ForeignKey(ref.Implantation,
1001 db_column='implantation', related_name='+',
1002 unique=True)
30be56d5 1003
1004 def __unicode__(self):
1005 return u'%s : %s' % (self.implantation, self.employe)
1006
1007 class Meta:
1008 ordering = ['implantation__nom']
8c1ae2b3 1009 verbose_name = "Responsable d'implantation"
1010 verbose_name_plural = "Responsables d'implantation"