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