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