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