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