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