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