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