liens
[auf_rh_dae.git] / project / rh / models.py
CommitLineData
e9bbd6ba 1# -=- encoding: utf-8 -=-
2
a4125771 3import datetime
c267f20c 4from datetime import date
ca1a7b76 5from decimal import Decimal
c267f20c 6
83b7692b 7from django.core.files.storage import FileSystemStorage
49f9f116 8from django.db import models
838bc59d 9from django.db.models import Q
d6985a3a 10from django.conf import settings
c267f20c 11
94cb1300 12from auf.django.emploi.models import GENRE_CHOICES, SITUATION_CHOICES # devrait plutot être dans references
d6985a3a
OL
13from auf.django.metadata.models import AUFMetadata
14from auf.django.metadata.managers import NoDeleteManager
bf6f2712 15import auf.django.references.models as ref
b4aeadf3 16from validators import validate_date_passee
7ba822a6 17from managers import PosteManager, DossierManager, DossierComparaisonManager, \
6bee05ff
OL
18 PosteComparaisonManager, DeviseManager, ServiceManager, TypeRemunerationManager
19from change_list import RechercheTemporelle, KEY_STATUT, STATUT_ACTIF, \
20 STATUT_INACTIF, STATUT_FUTUR
83b7692b 21
a4125771 22
a4125771
OL
23# Gruick hack pour déterminer d'ou provient l'instanciation d'une classe pour l'héritage.
24# Cela permet de faire du dynamic loading par app sans avoir à redéfinir dans DAE la FK
25def app_context():
26 import inspect;
27 models_stack = [s[1].split('/')[-2] for s in inspect.stack() if s[1].endswith('models.py')]
28 return models_stack[-1]
29
30
2d4d2fcf 31# Constantes
4047b783 32HELP_TEXT_DATE = "format: jj-mm-aaaa"
ca1a7b76
EMS
33REGIME_TRAVAIL_DEFAULT = Decimal('100.00')
34REGIME_TRAVAIL_NB_HEURE_SEMAINE_DEFAULT = Decimal('35.00')
e2827532 35REGIME_TRAVAIL_NB_HEURE_SEMAINE_HELP_TEXT = "Saisir le nombre d'heure de travail à temps complet (100%), sans tenir compte du régime de travail"
2d4d2fcf 36
83b7692b 37# Upload de fichiers
ca1a7b76 38storage_prive = FileSystemStorage(settings.PRIVE_MEDIA_ROOT,
83b7692b 39 base_url=settings.PRIVE_MEDIA_URL)
40
41def poste_piece_dispatch(instance, filename):
5be60939 42 path = "%s/poste/%s/%s" % (instance._meta.app_label, instance.poste_id, filename)
83b7692b 43 return path
44
45def dossier_piece_dispatch(instance, filename):
5be60939 46 path = "%s/dossier/%s/%s" % (instance._meta.app_label, instance.dossier_id, filename)
83b7692b 47 return path
48
5ea6b5bb 49def employe_piece_dispatch(instance, filename):
5be60939 50 path = "%s/employe/%s/%s" % (instance._meta.app_label, instance.employe_id, filename)
5ea6b5bb 51 return path
52
4ba73558 53def contrat_dispatch(instance, filename):
5be60939 54 path = "%s/contrat/%s/%s" % (instance._meta.app_label, instance.dossier_id, filename)
4ba73558
OL
55 return path
56
5f5a4f06 57
e84c8ef1
OL
58class DevisableMixin(object):
59
60 def get_annee_pour_taux_devise(self):
5a165e95 61 return datetime.datetime.now().year
e84c8ef1 62
03ff41e3
OL
63
64 def taux_devise(self, devise=None):
65 if devise is None:
66 devise = self.devise
67
68 if devise is None:
e84c8ef1 69 return None
03ff41e3 70 if devise.code == "EUR":
e84c8ef1
OL
71 return 1
72
73 annee = self.get_annee_pour_taux_devise()
03ff41e3 74 taux = [tc.taux for tc in TauxChange.objects.filter(devise=devise, annee=annee)]
e84c8ef1
OL
75 taux = set(taux)
76
77 if len(taux) == 0:
03ff41e3 78 raise Exception(u"Pas de taux pour %s en %s" % (devise.code, annee))
e84c8ef1
OL
79
80 if len(taux) > 1:
81 raise Exception(u"Il existe plusieurs taux de %s en %s" %
03ff41e3 82 (devise.code, annee))
e84c8ef1
OL
83 else:
84 return list(taux)[0]
85
86 def montant_euros(self):
87 try:
88 taux = self.taux_devise()
89 except Exception, e:
90 return e
91 if not taux:
92 return None
93 return int(round(float(self.montant) * float(taux), 2))
94
95
d6985a3a 96class Commentaire(AUFMetadata):
2d4d2fcf 97 texte = models.TextField()
098043b6 98 owner = models.ForeignKey('auth.User', db_column='owner', related_name='+', verbose_name=u"Commentaire de")
ca1a7b76 99
2d4d2fcf 100 class Meta:
101 abstract = True
6e4600ef 102 ordering = ['-date_creation']
ca1a7b76 103
6e4600ef 104 def __unicode__(self):
105 return u'%s' % (self.texte)
07b40eda 106
83b7692b 107
108### POSTE
109
110POSTE_APPEL_CHOICES = (
111 ('interne', 'Interne'),
112 ('externe', 'Externe'),
113)
114
d6985a3a 115class Poste_(AUFMetadata):
ca1a7b76 116 """Un Poste est un emploi (job) à combler dans une implantation.
6e4600ef 117 Un Poste peut être comblé par un Employe, auquel cas un Dossier est créé.
118 Si on veut recruter 2 jardiniers, 2 Postes distincts existent.
119 """
1f2979b8
OL
120
121 objects = PosteManager()
122
83b7692b 123 # Identification
ca1a7b76 124 nom = models.CharField(max_length=255,
c1195471 125 verbose_name = u"Titre du poste", )
2d4d2fcf 126 nom_feminin = models.CharField(max_length=255,
c1195471 127 verbose_name = u"Titre du poste (au féminin)",
6e4600ef 128 null=True)
105dd778 129 implantation = models.ForeignKey(ref.Implantation, help_text=u"Taper le nom de l'implantation ou sa région",
6e4600ef 130 db_column='implantation', related_name='+')
105dd778 131 type_poste = models.ForeignKey('TypePoste', db_column='type_poste', help_text=u"Taper le nom du type de poste",
6e4600ef 132 related_name='+',
ec1b4c33
JPC
133 null=True,
134 verbose_name=u"type de poste")
ca1a7b76 135 service = models.ForeignKey('Service', db_column='service',
6e4600ef 136 related_name='+',
d48f0922
OL
137 verbose_name = u"direction/service/pôle support",
138 null=True,)
6e4600ef 139 responsable = models.ForeignKey('Poste', db_column='responsable',
d48f0922
OL
140 related_name='+',
141 null=True,
142 help_text=u"Taper le nom du poste ou du type de poste",
3f5cbabe 143 verbose_name = u"Poste du responsable", )
83b7692b 144
145 # Contrat
146 regime_travail = models.DecimalField(max_digits=12, decimal_places=2,
8277a35b 147 default=REGIME_TRAVAIL_DEFAULT, null=True,
ca1a7b76 148 verbose_name = u"Temps de travail",
8c1ae2b3 149 help_text="% du temps complet")
83b7692b 150 regime_travail_nb_heure_semaine = models.DecimalField(max_digits=12,
8277a35b 151 decimal_places=2, null=True,
2d4d2fcf 152 default=REGIME_TRAVAIL_NB_HEURE_SEMAINE_DEFAULT,
e2827532
OL
153 verbose_name= u"Nb. heures par semaine",
154 help_text=REGIME_TRAVAIL_NB_HEURE_SEMAINE_HELP_TEXT)
83b7692b 155
156 # Recrutement
ca1a7b76 157 local = models.NullBooleanField(verbose_name = u"Local", default=True,
8277a35b 158 null=True, blank=True)
ca1a7b76 159 expatrie = models.NullBooleanField(verbose_name = u"Expatrié", default=False,
8277a35b
NC
160 null=True, blank=True)
161 mise_a_disposition = models.NullBooleanField(
c1195471 162 verbose_name = u"Mise à disposition",
8277a35b
NC
163 null=True, default=False)
164 appel = models.CharField(max_length=10, null=True,
c1195471 165 verbose_name = u"Appel à candidature",
6e4600ef 166 choices=POSTE_APPEL_CHOICES,
167 default='interne')
83b7692b 168
169 # Rémunération
ca1a7b76 170 classement_min = models.ForeignKey('Classement',
6e4600ef 171 db_column='classement_min', related_name='+',
172 null=True, blank=True)
ca1a7b76 173 classement_max = models.ForeignKey('Classement',
6e4600ef 174 db_column='classement_max', related_name='+',
175 null=True, blank=True)
105dd778 176 valeur_point_min = models.ForeignKey('ValeurPoint', help_text=u"Taper le code ou le nom de l'implantation",
ca1a7b76 177 db_column='valeur_point_min', related_name='+',
6e4600ef 178 null=True, blank=True)
105dd778 179 valeur_point_max = models.ForeignKey('ValeurPoint', help_text=u"Taper le code ou le nom de l'implantation",
ca1a7b76 180 db_column='valeur_point_max', related_name='+',
6e4600ef 181 null=True, blank=True)
8277a35b 182 devise_min = models.ForeignKey('Devise', db_column='devise_min', null=True,
eb6bf568 183 related_name='+',)
8277a35b 184 devise_max = models.ForeignKey('Devise', db_column='devise_max', null=True,
eb6bf568 185 related_name='+',)
83b7692b 186 salaire_min = models.DecimalField(max_digits=12, decimal_places=2,
8277a35b 187 null=True, default=0)
83b7692b 188 salaire_max = models.DecimalField(max_digits=12, decimal_places=2,
8277a35b 189 null=True, default=0)
83b7692b 190 indemn_min = models.DecimalField(max_digits=12, decimal_places=2,
8277a35b 191 null=True, default=0)
83b7692b 192 indemn_max = models.DecimalField(max_digits=12, decimal_places=2,
8277a35b 193 null=True, default=0)
83b7692b 194 autre_min = models.DecimalField(max_digits=12, decimal_places=2,
8277a35b 195 null=True, default=0)
83b7692b 196 autre_max = models.DecimalField(max_digits=12, decimal_places=2,
8277a35b 197 null=True, default=0)
83b7692b 198
199 # Comparatifs de rémunération
2f7a8932 200 devise_comparaison = models.ForeignKey('Devise', null=True, blank=True,
ca1a7b76 201 db_column='devise_comparaison',
eb6bf568 202 related_name='+', )
83b7692b 203 comp_locale_min = models.DecimalField(max_digits=12, decimal_places=2,
6e4600ef 204 null=True, blank=True)
83b7692b 205 comp_locale_max = models.DecimalField(max_digits=12, decimal_places=2,
6e4600ef 206 null=True, blank=True)
83b7692b 207 comp_universite_min = models.DecimalField(max_digits=12, decimal_places=2,
6e4600ef 208 null=True, blank=True)
83b7692b 209 comp_universite_max = models.DecimalField(max_digits=12, decimal_places=2,
6e4600ef 210 null=True, blank=True)
83b7692b 211 comp_fonctionpub_min = models.DecimalField(max_digits=12, decimal_places=2,
6e4600ef 212 null=True, blank=True)
83b7692b 213 comp_fonctionpub_max = models.DecimalField(max_digits=12, decimal_places=2,
6e4600ef 214 null=True, blank=True)
83b7692b 215 comp_ong_min = models.DecimalField(max_digits=12, decimal_places=2,
6e4600ef 216 null=True, blank=True)
83b7692b 217 comp_ong_max = models.DecimalField(max_digits=12, decimal_places=2,
6e4600ef 218 null=True, blank=True)
83b7692b 219 comp_autre_min = models.DecimalField(max_digits=12, decimal_places=2,
6e4600ef 220 null=True, blank=True)
83b7692b 221 comp_autre_max = models.DecimalField(max_digits=12, decimal_places=2,
6e4600ef 222 null=True, blank=True)
83b7692b 223
224 # Justification
6e4600ef 225 justification = models.TextField(null=True, blank=True)
83b7692b 226
2d4d2fcf 227 # Autres Metadata
a2459daf 228 date_debut = models.DateField(verbose_name=u"Date de début", help_text=HELP_TEXT_DATE,
7332d8f9 229 null=True, blank=True)
a2459daf 230 date_fin = models.DateField(verbose_name=u"Date de fin", help_text=HELP_TEXT_DATE,
6e4600ef 231 null=True, blank=True)
232
233 class Meta:
37868f0b 234 abstract = True
6e4600ef 235 ordering = ['implantation__nom', 'nom']
c1195471
OL
236 verbose_name = u"Poste"
237 verbose_name_plural = u"Postes"
30e3cf7c 238 ordering = ["nom"]
6e4600ef 239
83b7692b 240 def __unicode__(self):
ca1a7b76 241 representation = u'%s - %s [%s]' % (self.implantation, self.nom,
8c1ae2b3 242 self.id)
8c1ae2b3 243 return representation
ca1a7b76 244
22343fe7 245
4c53dda4
OL
246 prefix_implantation = "implantation__region"
247 def get_regions(self):
248 return [self.implantation.region]
249
552d0db7 250 def get_devise(self):
523c8c0f
EMS
251 vp = ValeurPoint.objects.filter(implantation=self.implantation, devise__archive=False).order_by('annee')
252 if len(vp) > 0:
253 return vp[0].devise
254 else:
255 return Devise.objects.get(code='EUR')
4c53dda4
OL
256
257class Poste(Poste_):
258 __doc__ = Poste_.__doc__
259
260 # meta dématérialisation : pour permettre le filtrage
ec1b4c33 261 vacant = models.NullBooleanField(verbose_name = u"vacant", null=True, blank=True)
4c53dda4 262
8c1ae2b3 263 def is_vacant(self):
23102192
DB
264 vacant = True
265 if self.occupe_par():
266 vacant = False
267 return vacant
268
269 def occupe_par(self):
270 """Retourne la liste d'employé occupant ce poste.
271 Généralement, retourne une liste d'un élément.
272 Si poste inoccupé, retourne liste vide.
4c53dda4 273 UTILISE pour mettre a jour le flag vacant
23102192 274 """
f614ca5c 275 return [d.employe for d in self.rh_dossiers.filter(supprime=False).exclude(date_fin__lt=date.today())]
83b7692b 276
37868f0b 277
83b7692b 278POSTE_FINANCEMENT_CHOICES = (
279 ('A', 'A - Frais de personnel'),
280 ('B', 'B - Projet(s)-Titre(s)'),
281 ('C', 'C - Autre')
282)
283
6e7c919b
NC
284
285class PosteFinancement_(models.Model):
6e4600ef 286 """Pour un Poste, structure d'informations décrivant comment on prévoit
287 financer ce Poste.
288 """
a4125771 289 poste = models.ForeignKey('%s.Poste' % app_context(), db_column='poste', related_name='%(app_label)s_financements')
83b7692b 290 type = models.CharField(max_length=1, choices=POSTE_FINANCEMENT_CHOICES)
291 pourcentage = models.DecimalField(max_digits=12, decimal_places=2,
8c1ae2b3 292 help_text="ex.: 33.33 % (décimale avec point)")
83b7692b 293 commentaire = models.TextField(
8c1ae2b3 294 help_text="Spécifiez la source de financement.")
83b7692b 295
296 class Meta:
6e7c919b 297 abstract = True
83b7692b 298 ordering = ['type']
ca1a7b76 299
6e4600ef 300 def __unicode__(self):
a184c555 301 return u'%s : %s %%' % (self.type, self.pourcentage)
83b7692b 302
abf91905
JPC
303 def choix(self):
304 return u"%s" % dict(POSTE_FINANCEMENT_CHOICES)[self.type]
305
6e7c919b
NC
306
307class PosteFinancement(PosteFinancement_):
a4125771 308 pass
6e7c919b
NC
309
310
317ce433 311class PostePiece_(models.Model):
6e4600ef 312 """Documents relatifs au Poste.
7abc6d45 313 Ex.: Description de poste
314 """
a4125771 315 poste = models.ForeignKey('%s.Poste' % app_context(), db_column='poste', related_name='%(app_label)s_pieces')
c1195471 316 nom = models.CharField(verbose_name = u"Nom", max_length=255)
ca1a7b76
EMS
317 fichier = models.FileField(verbose_name = u"Fichier",
318 upload_to=poste_piece_dispatch,
83b7692b 319 storage=storage_prive)
320
6e4600ef 321 class Meta:
317ce433 322 abstract = True
6e4600ef 323 ordering = ['nom']
ca1a7b76 324
6e4600ef 325 def __unicode__(self):
326 return u'%s' % (self.nom)
327
317ce433 328class PostePiece(PostePiece_):
a4125771 329 pass
317ce433 330
e84c8ef1 331class PosteComparaison_(AUFMetadata, DevisableMixin):
068d1462
OL
332 """
333 De la même manière qu'un dossier, un poste peut-être comparé à un autre poste.
334 """
a4125771 335 poste = models.ForeignKey('%s.Poste' % app_context(), related_name='%(app_label)s_comparaisons_internes')
16b1454e
OL
336 objects = PosteComparaisonManager()
337
0f0dacbb 338 implantation = models.ForeignKey(ref.Implantation, null=True, blank=True, related_name="+")
c1195471 339 nom = models.CharField(verbose_name = u"Poste", max_length=255, null=True, blank=True)
068d1462 340 montant = models.IntegerField(null=True)
eb6bf568 341 devise = models.ForeignKey("Devise", related_name='+', null=True, blank=True)
1d0f4eef 342
317ce433
OL
343 class Meta:
344 abstract = True
345
1d0f4eef 346
783e077a
JPC
347 def __unicode__(self):
348 return self.nom
349
317ce433 350class PosteComparaison(PosteComparaison_):
c86fc8ef 351 objects = NoDeleteManager()
068d1462 352
317ce433 353class PosteCommentaire_(Commentaire):
a4125771 354 poste = models.ForeignKey('%s.Poste' % app_context(), db_column='poste', related_name='+')
83b7692b 355
317ce433
OL
356 class Meta:
357 abstract = True
358
359class PosteCommentaire(PosteCommentaire_):
360 pass
2d4d2fcf 361
83b7692b 362### EMPLOYÉ/PERSONNE
e9bbd6ba 363
d6985a3a 364class Employe(AUFMetadata):
ca1a7b76 365 """Personne occupant ou ayant occupé un Poste. Un Employe aura autant de
6e4600ef 366 Dossiers qu'il occupe ou a occupé de Postes.
ca1a7b76
EMS
367
368 Cette classe aurait pu avantageusement s'appeler Personne car la notion
6e4600ef 369 d'employé n'a pas de sens si aucun Dossier n'existe pour une personne.
370 """
9afaa55e 371 # Identification
e9bbd6ba 372 nom = models.CharField(max_length=255)
c1195471 373 prenom = models.CharField(max_length=255, verbose_name = u"Prénom")
ca1a7b76 374 nom_affichage = models.CharField(max_length=255,
c1195471 375 verbose_name = u"Nom d'affichage",
6e4600ef 376 null=True, blank=True)
ca1a7b76 377 nationalite = models.ForeignKey(ref.Pays, to_field='code',
6e4600ef 378 db_column='nationalite',
8c1ae2b3 379 related_name='employes_nationalite',
ca1a7b76
EMS
380 verbose_name = u"Nationalité",
381 blank=True, null=True)
a25e1d5c 382 date_naissance = models.DateField(verbose_name = u"Date de naissance",
4047b783 383 help_text=HELP_TEXT_DATE,
b4aeadf3 384 validators=[validate_date_passee],
6e4600ef 385 null=True, blank=True)
2d4d2fcf 386 genre = models.CharField(max_length=1, choices=GENRE_CHOICES)
ca1a7b76 387
9afaa55e 388 # Infos personnelles
ca1a7b76 389 situation_famille = models.CharField(max_length=1,
6e4600ef 390 choices=SITUATION_CHOICES,
c1195471 391 verbose_name = u"Situation familiale",
6e4600ef 392 null=True, blank=True)
c1195471 393 date_entree = models.DateField(verbose_name = u"Date d'entrée à l'AUF",
3feb97b0 394 help_text=HELP_TEXT_DATE,
7abc6d45 395 null=True, blank=True)
ca1a7b76 396
9afaa55e 397 # Coordonnées
ca1a7b76 398 tel_domicile = models.CharField(max_length=255,
c1195471 399 verbose_name = u"Tél. domicile",
8c1ae2b3 400 null=True, blank=True)
ca1a7b76 401 tel_cellulaire = models.CharField(max_length=255,
c1195471 402 verbose_name = u"Tél. cellulaire",
8c1ae2b3 403 null=True, blank=True)
e9bbd6ba 404 adresse = models.CharField(max_length=255, null=True, blank=True)
e9bbd6ba 405 ville = models.CharField(max_length=255, null=True, blank=True)
406 province = models.CharField(max_length=255, null=True, blank=True)
407 code_postal = models.CharField(max_length=255, null=True, blank=True)
6e4600ef 408 pays = models.ForeignKey(ref.Pays, to_field='code', db_column='pays',
ca1a7b76 409 related_name='employes',
6e4600ef 410 null=True, blank=True)
9afaa55e 411
a45e414b 412 # meta dématérialisation : pour permettre le filtrage
ec1b4c33 413 nb_postes = models.IntegerField(verbose_name = u"nombre de postes", null=True, blank=True)
a45e414b 414
6e4600ef 415 class Meta:
9b0d1822 416 ordering = ['nom','prenom']
c1195471
OL
417 verbose_name = u"Employé"
418 verbose_name_plural = u"Employés"
ca1a7b76 419
9afaa55e 420 def __unicode__(self):
a2c3ad52 421 return u'%s %s [%s]' % (self.nom.upper(), self.prenom, self.id)
ca1a7b76 422
d04d084c 423 def civilite(self):
424 civilite = u''
425 if self.genre.upper() == u'M':
426 civilite = u'M.'
427 elif self.genre.upper() == u'F':
428 civilite = u'Mme'
429 return civilite
ca1a7b76 430
5ea6b5bb 431 def url_photo(self):
432 """Retourne l'URL du service retournant la photo de l'Employe.
433 Équivalent reverse url 'rh_photo' avec id en param.
434 """
435 from django.core.urlresolvers import reverse
436 return reverse('rh_photo', kwargs={'id':self.id})
ca1a7b76 437
c267f20c 438 def dossiers_passes(self):
6bee05ff
OL
439 params = {KEY_STATUT: STATUT_INACTIF, }
440 search = RechercheTemporelle(params, self.__class__)
441 search.purge_params(params)
dcd1b959
OL
442 q = search.get_q_temporel(self.rh_dossiers)
443 return self.rh_dossiers.filter(q)
ca1a7b76 444
c267f20c 445 def dossiers_futurs(self):
6bee05ff
OL
446 params = {KEY_STATUT: STATUT_FUTUR, }
447 search = RechercheTemporelle(params, self.__class__)
448 search.purge_params(params)
dcd1b959
OL
449 q = search.get_q_temporel(self.rh_dossiers)
450 return self.rh_dossiers.filter(q)
ca1a7b76 451
c267f20c 452 def dossiers_encours(self):
6bee05ff
OL
453 params = {KEY_STATUT: STATUT_ACTIF, }
454 search = RechercheTemporelle(params, self.__class__)
455 search.purge_params(params)
dcd1b959
OL
456 q = search.get_q_temporel(self.rh_dossiers)
457 return self.rh_dossiers.filter(q)
ca1a7b76 458
35c0c2fe 459 def postes_encours(self):
460 postes_encours = set()
461 for d in self.dossiers_encours():
462 postes_encours.add(d.poste)
463 return postes_encours
ca1a7b76 464
35c0c2fe 465 def poste_principal(self):
65f9fac8 466 """
467 Retourne le Poste du premier Dossier créé parmi les Dossiers en cours.
ca1a7b76 468 Idée derrière :
65f9fac8 469 si on ajout d'autre Dossiers, c'est pour des Postes secondaires.
470 """
471 poste = Poste.objects.none()
472 try:
473 poste = self.dossiers_encours().order_by('date_debut')[0].poste
474 except:
475 pass
476 return poste
9afaa55e 477
b5cc0357 478 prefix_implantation = "rh_dossiers__poste__implantation__region"
aff1a4c6
PP
479 def get_regions(self):
480 regions = []
481 for d in self.dossiers.all():
482 regions.append(d.poste.implantation.region)
483 return regions
484
485
7abc6d45 486class EmployePiece(models.Model):
6e4600ef 487 """Documents relatifs à un employé.
7abc6d45 488 Ex.: CV...
489 """
6a7cc4dd
OL
490 employe = models.ForeignKey('Employe', db_column='employe',
491 related_name="pieces")
8c1ae2b3 492 nom = models.CharField(verbose_name="Nom", max_length=255)
ca1a7b76
EMS
493 fichier = models.FileField(verbose_name="Fichier",
494 upload_to=employe_piece_dispatch,
7abc6d45 495 storage=storage_prive)
496
6e4600ef 497 class Meta:
498 ordering = ['nom']
f9e54d59
PP
499 verbose_name = u"Employé pièce"
500 verbose_name_plural = u"Employé pièces"
501
6e4600ef 502 def __unicode__(self):
503 return u'%s' % (self.nom)
504
07b40eda 505class EmployeCommentaire(Commentaire):
8c1ae2b3 506 employe = models.ForeignKey('Employe', db_column='employe',
6e4600ef 507 related_name='+')
9afaa55e 508
b343eb3d
PP
509 class Meta:
510 verbose_name = u"Employé commentaire"
511 verbose_name_plural = u"Employé commentaires"
512
2d4d2fcf 513
e9bbd6ba 514LIEN_PARENTE_CHOICES = (
515 ('Conjoint', 'Conjoint'),
516 ('Conjointe', 'Conjointe'),
517 ('Fille', 'Fille'),
518 ('Fils', 'Fils'),
519)
520
d6985a3a 521class AyantDroit(AUFMetadata):
6e4600ef 522 """Personne en relation avec un Employe.
523 """
9afaa55e 524 # Identification
e9bbd6ba 525 nom = models.CharField(max_length=255)
8c1ae2b3 526 prenom = models.CharField(max_length=255,
c1195471 527 verbose_name = u"Prénom",)
ca1a7b76 528 nom_affichage = models.CharField(max_length=255,
c1195471 529 verbose_name = u"Nom d'affichage",
6e4600ef 530 null=True, blank=True)
ca1a7b76 531 nationalite = models.ForeignKey(ref.Pays, to_field='code',
6e4600ef 532 db_column='nationalite',
8c1ae2b3 533 related_name='ayantdroits_nationalite',
ca1a7b76
EMS
534 verbose_name = u"Nationalité",
535 null=True, blank=True)
a25e1d5c 536 date_naissance = models.DateField(verbose_name = u"Date de naissance",
cf022e27 537 help_text=HELP_TEXT_DATE,
25037368 538 validators=[validate_date_passee],
6e4600ef 539 null=True, blank=True)
2d4d2fcf 540 genre = models.CharField(max_length=1, choices=GENRE_CHOICES)
ca1a7b76 541
9afaa55e 542 # Relation
ca1a7b76 543 employe = models.ForeignKey('Employe', db_column='employe',
8c1ae2b3 544 related_name='ayantdroits',
c1195471 545 verbose_name = u"Employé")
ca1a7b76 546 lien_parente = models.CharField(max_length=10,
6e4600ef 547 choices=LIEN_PARENTE_CHOICES,
c1195471 548 verbose_name = u"Lien de parenté",
6e4600ef 549 null=True, blank=True)
550
551 class Meta:
a2c3ad52 552 ordering = ['nom', ]
c1195471
OL
553 verbose_name = u"Ayant droit"
554 verbose_name_plural = u"Ayants droit"
ca1a7b76 555
6e4600ef 556 def __unicode__(self):
a2c3ad52 557 return u'%s %s [%s]' % (self.nom.upper(), self.prenom, self.id)
83b7692b 558
aff1a4c6
PP
559 prefix_implantation = "employe__dossiers__poste__implantation__region"
560 def get_regions(self):
561 regions = []
562 for d in self.employe.dossiers.all():
563 regions.append(d.poste.implantation.region)
564 return regions
565
566
07b40eda 567class AyantDroitCommentaire(Commentaire):
8c1ae2b3 568 ayant_droit = models.ForeignKey('AyantDroit', db_column='ayant_droit',
6e4600ef 569 related_name='+')
83b7692b 570
2d4d2fcf 571
83b7692b 572### DOSSIER
573
574STATUT_RESIDENCE_CHOICES = (
575 ('local', 'Local'),
576 ('expat', 'Expatrié'),
577)
578
579COMPTE_COMPTA_CHOICES = (
580 ('coda', 'CODA'),
581 ('scs', 'SCS'),
582 ('aucun', 'Aucun'),
583)
584
b78bbaf3 585class Dossier_(AUFMetadata, DevisableMixin):
6e4600ef 586 """Le Dossier regroupe les informations relatives à l'occupation
587 d'un Poste par un Employe. Un seul Dossier existe par Poste occupé
588 par un Employe.
ca1a7b76 589
6e4600ef 590 Plusieurs Contrats peuvent être associés au Dossier.
591 Une structure de Remuneration est rattachée au Dossier. Un Poste pour
592 lequel aucun Dossier n'existe est un poste vacant.
593 """
3f5cbabe
OL
594
595 objects = DossierManager()
596
63e17dff 597 # TODO: OneToOne ??
eb6bf568 598 statut = models.ForeignKey('Statut', related_name='+', null=True)
ca1a7b76 599 organisme_bstg = models.ForeignKey('OrganismeBstg',
6e4600ef 600 db_column='organisme_bstg',
601 related_name='+',
ca1a7b76 602 verbose_name = u"Organisme",
8c1ae2b3 603 help_text="Si détaché (DET) ou \
6e4600ef 604 mis à disposition (MAD), \
605 préciser l'organisme.",
606 null=True, blank=True)
ca1a7b76 607
83b7692b 608 # Recrutement
2d4d2fcf 609 remplacement = models.BooleanField(default=False)
7c182958 610 remplacement_de = models.ForeignKey('self', related_name='+',
0b0545bd 611 help_text=u"Taper le nom de l'employé",
7c182958 612 null=True, blank=True)
ca1a7b76 613 statut_residence = models.CharField(max_length=10, default='local',
c1195471 614 verbose_name = u"Statut", null=True,
2d4d2fcf 615 choices=STATUT_RESIDENCE_CHOICES)
ca1a7b76 616
83b7692b 617 # Rémunération
ca1a7b76 618 classement = models.ForeignKey('Classement', db_column='classement',
6e4600ef 619 related_name='+',
620 null=True, blank=True)
8277a35b 621 regime_travail = models.DecimalField(max_digits=12, null=True,
2d4d2fcf 622 decimal_places=2,
623 default=REGIME_TRAVAIL_DEFAULT,
c1195471 624 verbose_name = u"Régime de travail",
8c1ae2b3 625 help_text="% du temps complet")
83b7692b 626 regime_travail_nb_heure_semaine = models.DecimalField(max_digits=12,
8277a35b 627 decimal_places=2, null=True,
2d4d2fcf 628 default=REGIME_TRAVAIL_NB_HEURE_SEMAINE_DEFAULT,
e2827532
OL
629 verbose_name=u"Nb. heures par semaine",
630 help_text=REGIME_TRAVAIL_NB_HEURE_SEMAINE_HELP_TEXT)
7abc6d45 631
632 # Occupation du Poste par cet Employe (anciennement "mandat")
c1195471 633 date_debut = models.DateField(verbose_name = u"Date de début d'occupation \
a25e1d5c 634 de poste",)
c1195471 635 date_fin = models.DateField(verbose_name = u"Date de fin d'occupation \
6e4600ef 636 de poste",
6e4600ef 637 null=True, blank=True)
ca1a7b76 638
2d4d2fcf 639 # Comptes
640 # TODO?
ca1a7b76 641
6e4600ef 642 class Meta:
37868f0b 643 abstract = True
49449367 644 ordering = ['employe__nom', ]
3f5f3898 645 verbose_name = u"Dossier"
8c1ae2b3 646 verbose_name_plural = "Dossiers"
ca1a7b76 647
65f9fac8 648 def salaire_theorique(self):
649 annee = date.today().year
650 coeff = self.classement.coefficient
651 implantation = self.poste.implantation
652 point = ValeurPoint.objects.get(implantation=implantation, annee=annee)
ca1a7b76 653
65f9fac8 654 montant = coeff * point.valeur
655 devise = point.devise
656 return {'montant':montant, 'devise':devise}
ca1a7b76 657
83b7692b 658 def __unicode__(self):
8c1ae2b3 659 poste = self.poste.nom
660 if self.employe.genre == 'F':
ca1a7b76 661 poste = self.poste.nom_feminin
8c1ae2b3 662 return u'%s - %s' % (self.employe, poste)
83b7692b 663
aff1a4c6
PP
664 prefix_implantation = "poste__implantation__region"
665 def get_regions(self):
666 return [self.poste.implantation.region]
667
37868f0b 668
3ebc0952 669 def remunerations(self):
838bc59d
OL
670 key = "%s_remunerations" % self._meta.app_label
671 remunerations = getattr(self, key)
672 return remunerations.all().order_by('-date_debut')
3ebc0952 673
02e69aa2 674 def remunerations_en_cours(self):
838bc59d
OL
675 q = Q(date_fin__exact=None) | Q(date_fin__gt=datetime.date.today())
676 return self.remunerations().all().filter(q).order_by('date_debut')
02e69aa2 677
09aa8374
OL
678 def get_salaire(self):
679 try:
680 return [r for r in self.remunerations().order_by('-date_debut') if r.type_id == 1][0]
681 except:
682 return None
3ebc0952 683
838bc59d
OL
684 def get_salaire_euros(self):
685 tx = self.taux_devise()
686 return (float)(tx) * (float)(self.salaire)
687
688 def get_remunerations_brutes(self):
689 """
690 1 Salaire de base
691 3 Indemnité de base
692 4 Indemnité d'expatriation
693 5 Indemnité pour frais
694 6 Indemnité de logement
695 7 Indemnité de fonction
696 8 Indemnité de responsabilité
697 9 Indemnité de transport
698 10 Indemnité compensatrice
699 11 Indemnité de subsistance
700 12 Indemnité différentielle
701 13 Prime d'installation
702 14 Billet d'avion
703 15 Déménagement
704 16 Indemnité de départ
705 18 Prime de 13ième mois
706 19 Prime d'intérim
707 """
708 ids = [1,3,4,5,6,7,8,9,10,11,12,13,14,15,16,18,19]
709 return [r for r in self.remunerations_en_cours().all() if r.type_id in ids]
710
711 def get_charges_salariales(self):
712 """
713 20 Charges salariales ?
714 """
715 ids = [20, ]
716 return [r for r in self.remunerations_en_cours().all() if r.type_id in ids]
717
838bc59d
OL
718 def get_charges_patronales(self):
719 """
720 17 Charges patronales
721 """
722 ids = [17, ]
723 return [r for r in self.remunerations_en_cours().all() if r.type_id in ids]
724
552d0db7
OL
725 def get_remunerations_tierces(self):
726 """
727 2 Salaire MAD
728 """
729 return [r for r in self.remunerations_en_cours().all() if r.type_id in (2, )]
730
731 # DEVISE LOCALE
732
733 def get_total_local_charges_salariales(self):
bc17b82c 734 devise = self.poste.get_devise()
552d0db7
OL
735 total = 0.0
736 for r in self.get_charges_salariales():
bc17b82c
OL
737 if r.devise != devise:
738 return None
739 total += float(r.montant)
552d0db7
OL
740 return total
741
742 def get_total_local_charges_patronales(self):
bc17b82c 743 devise = self.poste.get_devise()
552d0db7
OL
744 total = 0.0
745 for r in self.get_charges_patronales():
bc17b82c
OL
746 if r.devise != devise:
747 return None
552d0db7
OL
748 total += float(r.montant)
749 return total
750
751 def get_local_salaire_brut(self):
752 """
753 somme des rémuérations brutes
754 """
755 devise = self.poste.get_devise()
756 total = 0.0
757 for r in self.get_remunerations_brutes():
758 if r.devise != devise:
759 return None
760 total += float(r.montant)
761 return total
762
763 def get_local_salaire_net(self):
764 """
765 salaire brut - charges salariales
766 """
767 devise = self.poste.get_devise()
768 total_charges = 0.0
769 for r in self.get_charges_salariales():
770 if r.devise != devise:
771 return None
772 total_charges += float(r.montant)
773 return self.get_local_salaire_brut() - total_charges
774
775 def get_local_couts_auf(self):
776 """
777 salaire net + charges patronales
778 """
779 devise = self.poste.get_devise()
780 total_charges = 0.0
781 for r in self.get_charges_patronales():
782 if r.devise != devise:
783 return None
784 total_charges += float(r.montant)
785 return self.get_local_salaire_net() + total_charges
786
787 def get_total_local_remunerations_tierces(self):
788 devise = self.poste.get_devise()
789 total = 0.0
790 for r in self.get_remunerations_tierces():
791 if r.devise != devise:
792 return None
793 total += float(r.montant)
794 return total
795
796 # DEVISE EURO
797
798 def get_total_charges_salariales(self):
799 total = 0.0
800 for r in self.get_charges_salariales():
801 total += r.montant_euros()
802 return total
803
838bc59d
OL
804 def get_total_charges_patronales(self):
805 total = 0.0
806 for r in self.get_charges_patronales():
807 total += r.montant_euros()
808 return total
809
810 def get_salaire_brut(self):
811 """
812 somme des rémuérations brutes
813 """
814 total = 0.0
815 for r in self.get_remunerations_brutes():
816 total += r.montant_euros()
817 return total
818
819 def get_salaire_net(self):
820 """
821 salaire brut - charges salariales
822 """
823 total_charges = 0.0
824 for r in self.get_charges_salariales():
825 total_charges += r.montant_euros()
826 return self.get_salaire_brut() - total_charges
827
828 def get_couts_auf(self):
829 """
830 salaire net + charges patronales
831 """
832 total_charges = 0.0
833 for r in self.get_charges_patronales():
834 total_charges += r.montant_euros()
835 return self.get_salaire_net() + total_charges
836
838bc59d
OL
837 def get_total_remunerations_tierces(self):
838 total = 0.0
839 for r in self.get_remunerations_tierces():
840 total += r.montant_euros()
841 return total
842
22343fe7 843
37868f0b
NC
844class Dossier(Dossier_):
845 __doc__ = Dossier_.__doc__
0b0545bd
OL
846 poste = models.ForeignKey('%s.Poste' % app_context(),
847 db_column='poste',
848 related_name='%(app_label)s_dossiers',
849 help_text=u"Taper le nom du poste ou du type de poste",
850 )
851 employe = models.ForeignKey('Employe', db_column='employe',
852 help_text=u"Taper le nom de l'employé",
16b1454e
OL
853 related_name='%(app_label)s_dossiers',
854 verbose_name=u"Employé")
6bee05ff
OL
855 principal = models.BooleanField(verbose_name=u"Principal?", default=True,
856 help_text=u"Ce Dossier est pour le principal Poste occupé par l'Employé")
37868f0b
NC
857
858
fc917340 859class DossierPiece_(models.Model):
7abc6d45 860 """Documents relatifs au Dossier (à l'occupation de ce poste par employé).
861 Ex.: Lettre de motivation.
862 """
22343fe7 863 dossier = models.ForeignKey('%s.Dossier' % app_context(), db_column='dossier', related_name='%(app_label)s_dossierpieces')
c1195471 864 nom = models.CharField(verbose_name = u"Nom", max_length=255)
ca1a7b76
EMS
865 fichier = models.FileField(verbose_name = u"Fichier",
866 upload_to=dossier_piece_dispatch,
83b7692b 867 storage=storage_prive)
868
6e4600ef 869 class Meta:
fc917340 870 abstract = True
6e4600ef 871 ordering = ['nom']
ca1a7b76 872
6e4600ef 873 def __unicode__(self):
874 return u'%s' % (self.nom)
875
fc917340 876class DossierPiece(DossierPiece_):
a4125771 877 pass
fc917340
OL
878
879class DossierCommentaire_(Commentaire):
a4125771 880 dossier = models.ForeignKey('%s.Dossier' % app_context(), db_column='dossier', related_name='+')
fc917340
OL
881 class Meta:
882 abstract = True
883
884class DossierCommentaire(DossierCommentaire_):
a4125771 885 pass
fc917340 886
e84c8ef1 887class DossierComparaison_(models.Model, DevisableMixin):
1d0f4eef
OL
888 """
889 Photo d'une comparaison salariale au moment de l'embauche.
890 """
a4125771 891 dossier = models.ForeignKey('%s.Dossier' % app_context(), related_name='%(app_label)s_comparaisons')
16b1454e
OL
892 objects = DossierComparaisonManager()
893
8c6269cc 894 implantation = models.ForeignKey(ref.Implantation, related_name="+", null=True, blank=True)
1d0f4eef
OL
895 poste = models.CharField(max_length=255, null=True, blank=True)
896 personne = models.CharField(max_length=255, null=True, blank=True)
897 montant = models.IntegerField(null=True)
eb6bf568 898 devise = models.ForeignKey('Devise', related_name='+', null=True, blank=True)
1d0f4eef 899
fc917340
OL
900 class Meta:
901 abstract = True
902
3b14230d
OL
903 def __unicode__(self):
904 return "%s (%s)" % (self.poste, self.personne)
905
1d0f4eef 906
fc917340 907class DossierComparaison(DossierComparaison_):
a4125771 908 pass
2d4d2fcf 909
07b40eda 910### RÉMUNÉRATION
ca1a7b76 911
d6985a3a 912class RemunerationMixin(AUFMetadata):
a4125771 913 dossier = models.ForeignKey('%s.Dossier' % app_context(), db_column='dossier', related_name='%(app_label)s_remunerations')
9afaa55e 914 # Identification
83b7692b 915 type = models.ForeignKey('TypeRemuneration', db_column='type',
8c1ae2b3 916 related_name='+',
c1195471 917 verbose_name = u"Type de rémunération")
ca1a7b76
EMS
918 type_revalorisation = models.ForeignKey('TypeRevalorisation',
919 db_column='type_revalorisation',
6e4600ef 920 related_name='+',
c1195471 921 verbose_name = u"Type de revalorisation",
7abc6d45 922 null=True, blank=True)
bc62f4a9 923 montant = models.DecimalField(null=True, blank=True,
4ce980ae 924 default=0, max_digits=12, decimal_places=2)
2d4d2fcf 925 # Annuel (12 mois, 52 semaines, 364 jours?)
a2c3ad52 926 devise = models.ForeignKey('Devise', db_column='devise', related_name='+',)
2d4d2fcf 927 # commentaire = precision
928 commentaire = models.CharField(max_length=255, null=True, blank=True)
929 # date_debut = anciennement date_effectif
a25e1d5c 930 date_debut = models.DateField(verbose_name = u"Date de début",
6e4600ef 931 null=True, blank=True)
a25e1d5c 932 date_fin = models.DateField(verbose_name = u"Date de fin",
6e4600ef 933 null=True, blank=True)
ca1a7b76
EMS
934
935 class Meta:
2d4d2fcf 936 abstract = True
6e4600ef 937 ordering = ['type__nom', '-date_fin']
ca1a7b76 938
6e4600ef 939 def __unicode__(self):
940 return u'%s %s (%s)' % (self.montant, self.devise.code, self.type.nom)
ca1a7b76 941
e84c8ef1 942class Remuneration_(RemunerationMixin, DevisableMixin):
2d4d2fcf 943 """Structure de rémunération (données budgétaires) en situation normale
ca1a7b76
EMS
944 pour un Dossier. Si un Evenement existe, utiliser la structure de
945 rémunération EvenementRemuneration de cet événement.
2d4d2fcf 946 """
83b7692b 947
948 def montant_mois(self):
949 return round(self.montant / 12, 2)
950
626beb4d
JPC
951 def montant_avec_regime(self):
952 return round(self.montant * (self.dossier.regime_travail/100), 2)
953
83b7692b 954 def montant_euro_mois(self):
e84c8ef1 955 return round(self.montant_euros() / 12, 2)
ca1a7b76 956
9afaa55e 957 def __unicode__(self):
958 try:
959 devise = self.devise.code
960 except:
961 devise = "???"
962 return "%s %s" % (self.montant, devise)
83b7692b 963
6e7c919b
NC
964 class Meta:
965 abstract = True
c1195471
OL
966 verbose_name = u"Rémunération"
967 verbose_name_plural = u"Rémunérations"
6e7c919b
NC
968
969
970class Remuneration(Remuneration_):
a4125771 971 pass
6e7c919b 972
2d4d2fcf 973
974### CONTRATS
c41b7fcc
OL
975
976class ContratManager(NoDeleteManager):
977 def get_query_set(self):
978 return super(ContratManager, self).get_query_set().select_related('dossier', 'dossier__poste')
979
2d4d2fcf 980
fc917340 981class Contrat_(AUFMetadata):
2d4d2fcf 982 """Document juridique qui encadre la relation de travail d'un Employe
ca1a7b76 983 pour un Poste particulier. Pour un Dossier (qui documente cette
2d4d2fcf 984 relation de travail) plusieurs contrats peuvent être associés.
985 """
c41b7fcc 986 objects = ContratManager()
865ca9e0 987 dossier = models.ForeignKey('%s.Dossier' % app_context(), db_column='dossier', related_name='%(app_label)s_contrats')
6e4600ef 988 type_contrat = models.ForeignKey('TypeContrat', db_column='type_contrat',
8c1ae2b3 989 related_name='+',
7baa5523 990 verbose_name = u"type de contrat")
a25e1d5c
OL
991 date_debut = models.DateField(verbose_name = u"Date de début")
992 date_fin = models.DateField(verbose_name = u"Date de fin",
6e4600ef 993 null=True, blank=True)
4ba73558
OL
994 fichier = models.FileField(verbose_name = u"Fichier",
995 upload_to=contrat_dispatch,
996 storage=storage_prive,
997 null=True, blank=True)
6e4600ef 998
999 class Meta:
fc917340 1000 abstract = True
a2c3ad52 1001 ordering = ['dossier__employe__nom']
c1195471
OL
1002 verbose_name = u"Contrat"
1003 verbose_name_plural = u"Contrats"
ca1a7b76 1004
6e4600ef 1005 def __unicode__(self):
8c1ae2b3 1006 return u'%s - %s' % (self.dossier, self.id)
fc917340
OL
1007
1008class Contrat(Contrat_):
a4125771 1009 pass
6e4600ef 1010
2d4d2fcf 1011
1012### ÉVÉNEMENTS
1013
a4125771
OL
1014#class Evenement_(AUFMetadata):
1015# """Un Evenement sert à déclarer une situation temporaire (exceptionnelle)
1016# d'un Dossier qui vient altérer des informations normales liées à un Dossier
1017# (ex.: la Remuneration).
1018#
1019# Ex.: congé de maternité, maladie...
1020#
1021# Lors de ces situations exceptionnelles, l'Employe a un régime de travail
1022# différent et une rémunération en conséquence. On souhaite toutefois
1023# conserver le Dossier intact afin d'éviter une re-saisie des données lors
1024# du retour à la normale.
1025# """
1026# dossier = models.ForeignKey('%s.Dossier' % app_context(), db_column='dossier',
1027# related_name='+')
1028# nom = models.CharField(max_length=255)
1029# date_debut = models.DateField(verbose_name = u"Date de début")
1030# date_fin = models.DateField(verbose_name = u"Date de fin",
1031# null=True, blank=True)
1032#
1033# class Meta:
1034# abstract = True
1035# ordering = ['nom']
1036# verbose_name = u"Évènement"
1037# verbose_name_plural = u"Évènements"
1038#
1039# def __unicode__(self):
1040# return u'%s' % (self.nom)
1041#
1042#
1043#class Evenement(Evenement_):
1044# __doc__ = Evenement_.__doc__
1045#
1046#
1047#class EvenementRemuneration_(RemunerationMixin):
1048# """Structure de rémunération liée à un Evenement qui remplace
1049# temporairement la Remuneration normale d'un Dossier, pour toute la durée
1050# de l'Evenement.
1051# """
1052# evenement = models.ForeignKey("Evenement", db_column='evenement',
1053# related_name='+',
1054# verbose_name = u"Évènement")
1055# # TODO : le champ dossier hérité de Remuneration doit être dérivé
1056# # de l'Evenement associé
1057#
1058# class Meta:
1059# abstract = True
1060# ordering = ['evenement', 'type__nom', '-date_fin']
1061# verbose_name = u"Évènement - rémunération"
1062# verbose_name_plural = u"Évènements - rémunérations"
1063#
1064#
1065#class EvenementRemuneration(EvenementRemuneration_):
1066# __doc__ = EvenementRemuneration_.__doc__
1067#
1068# class Meta:
1069# abstract = True
1070#
1071#
1072#class EvenementRemuneration(EvenementRemuneration_):
1073# __doc__ = EvenementRemuneration_.__doc__
865ca9e0 1074# TODO? class ContratPiece(models.Model):
f31ddfa0 1075
83b7692b 1076
ca1a7b76 1077### RÉFÉRENCES RH
83b7692b 1078
d6985a3a 1079class FamilleEmploi(AUFMetadata):
6e4600ef 1080 """Catégorie utilisée dans la gestion des Postes.
1081 Catégorie supérieure à TypePoste.
1082 """
e9bbd6ba 1083 nom = models.CharField(max_length=255)
ca1a7b76 1084
8c1ae2b3 1085 class Meta:
1086 ordering = ['nom']
c1195471
OL
1087 verbose_name = u"Famille d'emploi"
1088 verbose_name_plural = u"Familles d'emploi"
ca1a7b76 1089
6e4600ef 1090 def __unicode__(self):
1091 return u'%s' % (self.nom)
e9bbd6ba 1092
d6985a3a 1093class TypePoste(AUFMetadata):
6e4600ef 1094 """Catégorie de Poste.
1095 """
e9bbd6ba 1096 nom = models.CharField(max_length=255)
8c1ae2b3 1097 nom_feminin = models.CharField(max_length=255,
c1195471 1098 verbose_name = u"Nom féminin")
ca1a7b76 1099
8c1ae2b3 1100 is_responsable = models.BooleanField(default=False,
c1195471 1101 verbose_name = u"Poste de responsabilité")
ca1a7b76 1102 famille_emploi = models.ForeignKey('FamilleEmploi',
6e4600ef 1103 db_column='famille_emploi',
8c1ae2b3 1104 related_name='+',
ec1b4c33 1105 verbose_name = u"famille d'emploi")
e9bbd6ba 1106
6e4600ef 1107 class Meta:
1108 ordering = ['nom']
c1195471
OL
1109 verbose_name = u"Type de poste"
1110 verbose_name_plural = u"Types de poste"
ca1a7b76 1111
e9bbd6ba 1112 def __unicode__(self):
6e4600ef 1113 return u'%s' % (self.nom)
e9bbd6ba 1114
1115
1116TYPE_PAIEMENT_CHOICES = (
a3e3bde0
JPC
1117 (u'Régulier', u'Régulier'),
1118 (u'Ponctuel', u'Ponctuel'),
e9bbd6ba 1119)
1120
1121NATURE_REMUNERATION_CHOICES = (
a3e3bde0
JPC
1122 (u'Accessoire', u'Accessoire'),
1123 (u'Charges', u'Charges'),
1124 (u'Indemnité', u'Indemnité'),
1125 (u'RAS', u'Rémunération autre source'),
1126 (u'Traitement', u'Traitement'),
e9bbd6ba 1127)
1128
d6985a3a 1129class TypeRemuneration(AUFMetadata):
6e4600ef 1130 """Catégorie de Remuneration.
1131 """
7ba822a6
OL
1132 objects = TypeRemunerationManager()
1133
e9bbd6ba 1134 nom = models.CharField(max_length=255)
ca1a7b76 1135 type_paiement = models.CharField(max_length=30,
8c1ae2b3 1136 choices=TYPE_PAIEMENT_CHOICES,
c1195471 1137 verbose_name = u"Type de paiement")
ca1a7b76 1138 nature_remuneration = models.CharField(max_length=30,
8c1ae2b3 1139 choices=NATURE_REMUNERATION_CHOICES,
c1195471 1140 verbose_name = u"Nature de la rémunération")
7ba822a6 1141 archive = models.BooleanField(verbose_name=u"Archivé", default=False)
ca1a7b76 1142
8c1ae2b3 1143 class Meta:
1144 ordering = ['nom']
c1195471
OL
1145 verbose_name = u"Type de rémunération"
1146 verbose_name_plural = u"Types de rémunération"
9afaa55e 1147
1148 def __unicode__(self):
7ba822a6
OL
1149 if self.archive:
1150 archive = u"(archivé)"
1151 else:
1152 archive = ""
1153 return u'%s %s' % (self.nom, archive)
ca1a7b76 1154
d6985a3a 1155class TypeRevalorisation(AUFMetadata):
7abc6d45 1156 """Justification du changement de la Remuneration.
6e4600ef 1157 (Actuellement utilisé dans aucun traitement informatique.)
7abc6d45 1158 """
e9bbd6ba 1159 nom = models.CharField(max_length=255)
ca1a7b76 1160
8c1ae2b3 1161 class Meta:
1162 ordering = ['nom']
c1195471
OL
1163 verbose_name = u"Type de revalorisation"
1164 verbose_name_plural = u"Types de revalorisation"
e9bbd6ba 1165
1166 def __unicode__(self):
6e4600ef 1167 return u'%s' % (self.nom)
ca1a7b76 1168
d6985a3a 1169class Service(AUFMetadata):
6e4600ef 1170 """Unité administrative où les Postes sont rattachés.
1171 """
7ba822a6
OL
1172 objects = ServiceManager()
1173
f614ca5c 1174 archive = models.BooleanField(verbose_name=u"Archivé", default=False)
6e4600ef 1175 nom = models.CharField(max_length=255)
ca1a7b76 1176
9afaa55e 1177 class Meta:
1178 ordering = ['nom']
c1195471
OL
1179 verbose_name = u"Service"
1180 verbose_name_plural = u"Services"
e9bbd6ba 1181
6e4600ef 1182 def __unicode__(self):
41ced34a
OL
1183 if self.archive:
1184 archive = u"(archivé)"
1185 else:
1186 archive = ""
1187 return u'%s %s' % (self.nom, archive)
6e4600ef 1188
e9bbd6ba 1189
1190TYPE_ORGANISME_CHOICES = (
1191 ('MAD', 'Mise à disposition'),
1192 ('DET', 'Détachement'),
1193)
1194
d6985a3a 1195class OrganismeBstg(AUFMetadata):
ca1a7b76 1196 """Organisation d'où provient un Employe mis à disposition (MAD) de
6e4600ef 1197 ou détaché (DET) à l'AUF à titre gratuit.
ca1a7b76 1198
6e4600ef 1199 (BSTG = bien et service à titre gratuit.)
1200 """
e9bbd6ba 1201 nom = models.CharField(max_length=255)
1202 type = models.CharField(max_length=10, choices=TYPE_ORGANISME_CHOICES)
ca1a7b76 1203 pays = models.ForeignKey(ref.Pays, to_field='code',
6e4600ef 1204 db_column='pays',
1205 related_name='organismes_bstg',
1206 null=True, blank=True)
9afaa55e 1207
1208 class Meta:
1209 ordering = ['type', 'nom']
c1195471
OL
1210 verbose_name = u"Organisme BSTG"
1211 verbose_name_plural = u"Organismes BSTG"
9afaa55e 1212
6e4600ef 1213 def __unicode__(self):
8c1ae2b3 1214 return u'%s (%s)' % (self.nom, self.get_type_display())
83b7692b 1215
aff1a4c6
PP
1216 prefix_implantation = "pays__region"
1217 def get_regions(self):
1218 return [self.pays.region]
1219
1220
d6985a3a 1221class Statut(AUFMetadata):
6e4600ef 1222 """Statut de l'Employe dans le cadre d'un Dossier particulier.
1223 """
9afaa55e 1224 # Identification
a122050d 1225 code = models.CharField(max_length=25, unique=True, help_text="Saisir un code court mais lisible pour ce statut : le code est utilisé pour associer les statuts aux autres données tout en demeurant plus lisible qu'un identifiant numérique.")
e9bbd6ba 1226 nom = models.CharField(max_length=255)
e9bbd6ba 1227
6e4600ef 1228 class Meta:
1229 ordering = ['code']
c1195471
OL
1230 verbose_name = u"Statut d'employé"
1231 verbose_name_plural = u"Statuts d'employé"
ca1a7b76 1232
9afaa55e 1233 def __unicode__(self):
1234 return u'%s : %s' % (self.code, self.nom)
1235
83b7692b 1236
e9bbd6ba 1237TYPE_CLASSEMENT_CHOICES = (
6e4600ef 1238 ('S', 'S -Soutien'),
1239 ('T', 'T - Technicien'),
1240 ('P', 'P - Professionel'),
1241 ('C', 'C - Cadre'),
1242 ('D', 'D - Direction'),
1243 ('SO', 'SO - Sans objet [expatriés]'),
1244 ('HG', 'HG - Hors grille [direction]'),
e9bbd6ba 1245)
83b7692b 1246
952ecb37
OL
1247class ClassementManager(models.Manager):
1248 """
1249 Ordonner les spcéfiquement les classements.
1250 """
1251 def get_query_set(self):
1252 qs = super(self.__class__, self).get_query_set()
1253 qs = qs.extra(select={'ponderation': 'FIND_IN_SET(type,"SO,HG,S,T,P,C,D")'})
6559f73b 1254 qs = qs.extra(order_by=('ponderation', 'echelon', 'degre', ))
952ecb37
OL
1255 return qs.all()
1256
6e7c919b 1257
d6985a3a 1258class Classement_(AUFMetadata):
ca1a7b76 1259 """Éléments de classement de la
6e4600ef 1260 "Grille générique de classement hiérarchique".
ca1a7b76
EMS
1261
1262 Utile pour connaître, pour un Dossier, le salaire de base théorique lié au
6e4600ef 1263 classement dans la grille. Le classement donne le coefficient utilisé dans:
1264
1265 salaire de base = coefficient * valeur du point de l'Implantation du Poste
1266 """
952ecb37
OL
1267 objects = ClassementManager()
1268
9afaa55e 1269 # Identification
e9bbd6ba 1270 type = models.CharField(max_length=10, choices=TYPE_CLASSEMENT_CHOICES)
ca1a7b76
EMS
1271 echelon = models.IntegerField(verbose_name=u"Échelon", blank=True, default=0)
1272 degre = models.IntegerField(verbose_name=u"Degré", blank=True, default=0)
1273 coefficient = models.FloatField(default=0, verbose_name=u"Coefficient",
8277a35b 1274 null=True)
9afaa55e 1275 # Méta
6e4600ef 1276 # annee # au lieu de date_debut et date_fin
1277 commentaire = models.TextField(null=True, blank=True)
ca1a7b76 1278
6e4600ef 1279 class Meta:
6e7c919b 1280 abstract = True
6e4600ef 1281 ordering = ['type','echelon','degre','coefficient']
c1195471
OL
1282 verbose_name = u"Classement"
1283 verbose_name_plural = u"Classements"
e9bbd6ba 1284
1285 def __unicode__(self):
22343fe7 1286 return u'%s.%s.%s' % (self.type, self.echelon, self.degre, )
e9bbd6ba 1287
6e7c919b
NC
1288class Classement(Classement_):
1289 __doc__ = Classement_.__doc__
1290
1291
d6985a3a 1292class TauxChange_(AUFMetadata):
ca1a7b76 1293 """Taux de change de la devise vers l'euro (EUR)
6e4600ef 1294 pour chaque année budgétaire.
7abc6d45 1295 """
9afaa55e 1296 # Identification
8d3e2fff 1297 devise = models.ForeignKey('Devise', db_column='devise')
c1195471
OL
1298 annee = models.IntegerField(verbose_name = u"Année")
1299 taux = models.FloatField(verbose_name = u"Taux vers l'euro")
6e7c919b 1300
6e4600ef 1301 class Meta:
6e7c919b 1302 abstract = True
8c1ae2b3 1303 ordering = ['-annee', 'devise__code']
c1195471
OL
1304 verbose_name = u"Taux de change"
1305 verbose_name_plural = u"Taux de change"
ca1a7b76 1306
6e4600ef 1307 def __unicode__(self):
8c1ae2b3 1308 return u'%s : %s € (%s)' % (self.devise, self.taux, self.annee)
e9bbd6ba 1309
6e7c919b
NC
1310
1311class TauxChange(TauxChange_):
1312 __doc__ = TauxChange_.__doc__
1313
701f3bea 1314class ValeurPointManager(NoDeleteManager):
105dd778 1315
701f3bea
OL
1316 def get_query_set(self):
1317 return super(ValeurPointManager, self).get_query_set().select_related('devise', 'implantation')
1318
6e7c919b 1319
d6985a3a 1320class ValeurPoint_(AUFMetadata):
ca1a7b76
EMS
1321 """Utile pour connaître, pour un Dossier, le salaire de base théorique lié
1322 au classement dans la grille. La ValeurPoint s'obtient par l'implantation
8c1ae2b3 1323 du Poste de ce Dossier : dossier.poste.implantation (pseudo code).
6e4600ef 1324
1325 salaire de base = coefficient * valeur du point de l'Implantation du Poste
1326 """
ca1a7b76 1327
09aa8374 1328 actuelles = ValeurPointManager()
701f3bea 1329
8277a35b 1330 valeur = models.FloatField(null=True)
f614ca5c 1331 devise = models.ForeignKey('Devise', db_column='devise', related_name='+',)
ca1a7b76 1332 implantation = models.ForeignKey(ref.Implantation,
6e7c919b
NC
1333 db_column='implantation',
1334 related_name='%(app_label)s_valeur_point')
9afaa55e 1335 # Méta
e9bbd6ba 1336 annee = models.IntegerField()
9afaa55e 1337
6e4600ef 1338 class Meta:
701f3bea 1339 ordering = ['-annee', 'implantation__nom']
6e7c919b 1340 abstract = True
c1195471
OL
1341 verbose_name = u"Valeur du point"
1342 verbose_name_plural = u"Valeurs du point"
6e0bbb73 1343
9afaa55e 1344 def __unicode__(self):
24eb08a3 1345 return u'%s %s %s [%s] %s' % (self.devise.code, self.annee, self.valeur, self.implantation.nom_court, self.devise.nom)
6e7c919b
NC
1346
1347
1348class ValeurPoint(ValeurPoint_):
1349 __doc__ = ValeurPoint_.__doc__
1350
e9bbd6ba 1351
105dd778 1352
d6985a3a 1353class Devise(AUFMetadata):
6e4600ef 1354 """Devise monétaire.
1355 """
4bdadf8b
OL
1356
1357 objects = DeviseManager()
1358
edb35076 1359 archive = models.BooleanField(verbose_name=u"Archivé", default=False)
e9bbd6ba 1360 code = models.CharField(max_length=10, unique=True)
1361 nom = models.CharField(max_length=255)
1362
6e4600ef 1363 class Meta:
1364 ordering = ['code']
c1195471
OL
1365 verbose_name = u"Devise"
1366 verbose_name_plural = u"Devises"
ca1a7b76 1367
e9bbd6ba 1368 def __unicode__(self):
1369 return u'%s - %s' % (self.code, self.nom)
1370
d6985a3a 1371class TypeContrat(AUFMetadata):
6e4600ef 1372 """Type de contrat.
1373 """
e9bbd6ba 1374 nom = models.CharField(max_length=255)
6e4600ef 1375 nom_long = models.CharField(max_length=255)
49f9f116 1376
8c1ae2b3 1377 class Meta:
1378 ordering = ['nom']
c1195471
OL
1379 verbose_name = u"Type de contrat"
1380 verbose_name_plural = u"Types de contrat"
8c1ae2b3 1381
9afaa55e 1382 def __unicode__(self):
1383 return u'%s' % (self.nom)
ca1a7b76
EMS
1384
1385
2d4d2fcf 1386### AUTRES
1387
8c8ffc4f
OL
1388class ResponsableImplantationProxy(ref.Implantation):
1389 class Meta:
1390 proxy = True
1391 verbose_name = u"Responsable d'implantation"
1392 verbose_name_plural = u"Responsables d'implantation"
1393
1394
1395class ResponsableImplantation(models.Model):
ca1a7b76 1396 """Le responsable d'une implantation.
30be56d5 1397 Anciennement géré sur le Dossier du responsable.
1398 """
ca1a7b76 1399 employe = models.ForeignKey('Employe', db_column='employe',
6e4600ef 1400 related_name='+',
1401 null=True, blank=True)
8c8ffc4f
OL
1402 implantation = models.OneToOneField("ResponsableImplantationProxy",
1403 db_column='implantation', related_name='responsable',
6e4600ef 1404 unique=True)
30be56d5 1405
1406 def __unicode__(self):
1407 return u'%s : %s' % (self.implantation, self.employe)
ca1a7b76 1408
30be56d5 1409 class Meta:
1410 ordering = ['implantation__nom']
8c1ae2b3 1411 verbose_name = "Responsable d'implantation"
1412 verbose_name_plural = "Responsables d'implantation"
4c53dda4 1413