small fix
[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
d104b0ae 7import reversion
fa1f7426
EMS
8from auf.django.emploi.models import \
9 GENRE_CHOICES, SITUATION_CHOICES # devrait plutot être dans references
fa1f7426 10from auf.django.references import models as ref
e8b6a20c 11from django.contrib.auth.models import User
d104b0ae
EMS
12from django.core.files.storage import FileSystemStorage
13from django.db import models
14from django.db.models import Q
343cfd9c 15from django.db.models.signals import post_save, pre_save
d104b0ae 16from django.conf import settings
c267f20c 17
75f0e87b 18from project.rh.change_list import \
fa1f7426
EMS
19 RechercheTemporelle, KEY_STATUT, STATUT_ACTIF, STATUT_INACTIF, \
20 STATUT_FUTUR
e8b6a20c 21from project import groups
15659516
BS
22from project.rh.managers import (
23 PosteManager,
24 DossierManager,
25 EmployeManager,
26 DossierComparaisonManager,
27 PosteComparaisonManager,
28 ContratManager,
29 RemunerationManager,
30 ArchivableManager,
31 )
32
33
75f0e87b 34from project.rh.validators import validate_date_passee
a4125771 35
52f4c1e7
OL
36# import pour relocaliser le modèle selon la convention (models.py pour
37# introspection)
edbc9e37 38from project.rh.historique import ModificationTraite
a4125771 39
2d4d2fcf 40# Constantes
4047b783 41HELP_TEXT_DATE = "format: jj-mm-aaaa"
ca1a7b76
EMS
42REGIME_TRAVAIL_DEFAULT = Decimal('100.00')
43REGIME_TRAVAIL_NB_HEURE_SEMAINE_DEFAULT = Decimal('35.00')
fa1f7426
EMS
44REGIME_TRAVAIL_NB_HEURE_SEMAINE_HELP_TEXT = \
45 "Saisir le nombre d'heure de travail à temps complet (100%), " \
46 "sans tenir compte du régime de travail"
2d4d2fcf 47
83b7692b 48# Upload de fichiers
ca1a7b76 49storage_prive = FileSystemStorage(settings.PRIVE_MEDIA_ROOT,
83b7692b 50 base_url=settings.PRIVE_MEDIA_URL)
51
fa1f7426 52
a6ed66f9
OL
53class RemunIntegrityException(Exception):
54 pass
55
83b7692b 56def poste_piece_dispatch(instance, filename):
fa1f7426
EMS
57 path = "%s/poste/%s/%s" % (
58 instance._meta.app_label, instance.poste_id, filename
59 )
83b7692b 60 return path
61
fa1f7426 62
83b7692b 63def dossier_piece_dispatch(instance, filename):
fa1f7426
EMS
64 path = "%s/dossier/%s/%s" % (
65 instance._meta.app_label, instance.dossier_id, filename
66 )
83b7692b 67 return path
68
fa1f7426 69
5ea6b5bb 70def employe_piece_dispatch(instance, filename):
fa1f7426
EMS
71 path = "%s/employe/%s/%s" % (
72 instance._meta.app_label, instance.employe_id, filename
73 )
5ea6b5bb 74 return path
75
fa1f7426 76
4ba73558 77def contrat_dispatch(instance, filename):
fa1f7426
EMS
78 path = "%s/contrat/%s/%s" % (
79 instance._meta.app_label, instance.dossier_id, filename
80 )
4ba73558
OL
81 return path
82
5f5a4f06 83
52f4c1e7
OL
84class DateActiviteMixin(models.Model):
85 """
86 Mixin pour mettre à jour l'activité d'un modèle
87 """
88 class Meta:
89 abstract = True
90 date_creation = models.DateTimeField(auto_now_add=True,
91 null=True, blank=True,
92 verbose_name=u"Date de création",)
93 date_modification = models.DateTimeField(auto_now=True,
94 null=True, blank=True,
95 verbose_name=u"Date de modification",)
96
97
7013d234
EMS
98class Archivable(models.Model):
99 archive = models.BooleanField(u'archivé', default=False)
100
101 objects = ArchivableManager()
102 avec_archives = models.Manager()
103
104 class Meta:
105 abstract = True
106
107
e84c8ef1
OL
108class DevisableMixin(object):
109
110 def get_annee_pour_taux_devise(self):
5a165e95 111 return datetime.datetime.now().year
e84c8ef1 112
03ff41e3
OL
113 def taux_devise(self, devise=None):
114 if devise is None:
115 devise = self.devise
116
117 if devise is None:
e84c8ef1 118 return None
03ff41e3 119 if devise.code == "EUR":
e84c8ef1
OL
120 return 1
121
122 annee = self.get_annee_pour_taux_devise()
6e6bc910
EMS
123 taux = TauxChange.objects.filter(devise=devise, annee__lte=annee) \
124 .order_by('-annee')
125 return taux[0].taux
e84c8ef1
OL
126
127 def montant_euros(self):
128 try:
129 taux = self.taux_devise()
130 except Exception, e:
131 return e
132 if not taux:
133 return None
134 return int(round(float(self.montant) * float(taux), 2))
135
136
45066657 137class Commentaire(models.Model):
2d4d2fcf 138 texte = models.TextField()
fa1f7426
EMS
139 owner = models.ForeignKey(
140 'auth.User', db_column='owner', related_name='+',
141 verbose_name=u"Commentaire de"
142 )
45066657
EMS
143 date_creation = models.DateTimeField(
144 u'date', auto_now_add=True, blank=True, null=True
145 )
ca1a7b76 146
2d4d2fcf 147 class Meta:
148 abstract = True
6e4600ef 149 ordering = ['-date_creation']
ca1a7b76 150
6e4600ef 151 def __unicode__(self):
152 return u'%s' % (self.texte)
07b40eda 153
83b7692b 154
155### POSTE
156
157POSTE_APPEL_CHOICES = (
158 ('interne', 'Interne'),
159 ('externe', 'Externe'),
160)
161
fa1f7426 162
52f4c1e7 163class Poste_( DateActiviteMixin, models.Model,):
fa1f7426
EMS
164 """
165 Un Poste est un emploi (job) à combler dans une implantation.
6e4600ef 166 Un Poste peut être comblé par un Employe, auquel cas un Dossier est créé.
167 Si on veut recruter 2 jardiniers, 2 Postes distincts existent.
168 """
1f2979b8
OL
169
170 objects = PosteManager()
171
83b7692b 172 # Identification
fa1f7426
EMS
173 nom = models.CharField(u"Titre du poste", max_length=255)
174 nom_feminin = models.CharField(
175 u"Titre du poste (au féminin)", max_length=255, null=True
176 )
177 implantation = models.ForeignKey(
178 ref.Implantation,
179 help_text=u"Taper le nom de l'implantation ou sa région",
180 db_column='implantation', related_name='+'
181 )
182 type_poste = models.ForeignKey(
183 'TypePoste', db_column='type_poste',
184 help_text=u"Taper le nom du type de poste", related_name='+',
185 null=True, verbose_name=u"type de poste"
186 )
187 service = models.ForeignKey(
f7badf51 188 'Service', db_column='service', related_name='%(app_label)s_postes',
fa1f7426
EMS
189 verbose_name=u"direction/service/pôle support", null=True
190 )
191 responsable = models.ForeignKey(
192 'Poste', db_column='responsable',
193 related_name='+', null=True,
194 help_text=u"Taper le nom du poste ou du type de poste",
195 verbose_name=u"Poste du responsable"
196 )
197
83b7692b 198 # Contrat
fa1f7426
EMS
199 regime_travail = models.DecimalField(
200 u"temps de travail", max_digits=12, decimal_places=2,
201 default=REGIME_TRAVAIL_DEFAULT, null=True,
202 help_text="% du temps complet"
203 )
204 regime_travail_nb_heure_semaine = models.DecimalField(
205 u"nb. heures par semaine", max_digits=12, decimal_places=2,
206 null=True, default=REGIME_TRAVAIL_NB_HEURE_SEMAINE_DEFAULT,
207 help_text=REGIME_TRAVAIL_NB_HEURE_SEMAINE_HELP_TEXT
208 )
83b7692b 209
210 # Recrutement
fa1f7426
EMS
211 local = models.NullBooleanField(
212 u"local", default=True, null=True, blank=True
213 )
214 expatrie = models.NullBooleanField(
215 u"expatrié", default=False, null=True, blank=True
216 )
8277a35b 217 mise_a_disposition = models.NullBooleanField(
fa1f7426
EMS
218 u"mise à disposition", null=True, default=False
219 )
220 appel = models.CharField(
221 u"Appel à candidature", max_length=10, null=True,
222 choices=POSTE_APPEL_CHOICES, default='interne'
223 )
83b7692b 224
225 # Rémunération
fa1f7426
EMS
226 classement_min = models.ForeignKey(
227 'Classement', db_column='classement_min', related_name='+',
228 null=True, blank=True
229 )
230 classement_max = models.ForeignKey(
231 'Classement', db_column='classement_max', related_name='+',
232 null=True, blank=True
233 )
234 valeur_point_min = models.ForeignKey(
235 'ValeurPoint',
236 help_text=u"Taper le code ou le nom de l'implantation",
237 db_column='valeur_point_min', related_name='+', null=True,
238 blank=True
239 )
240 valeur_point_max = models.ForeignKey(
241 'ValeurPoint',
242 help_text=u"Taper le code ou le nom de l'implantation",
243 db_column='valeur_point_max', related_name='+', null=True,
244 blank=True
245 )
246 devise_min = models.ForeignKey(
247 'Devise', db_column='devise_min', null=True, related_name='+'
248 )
249 devise_max = models.ForeignKey(
250 'Devise', db_column='devise_max', null=True, related_name='+'
251 )
252 salaire_min = models.DecimalField(
7ad3b930 253 max_digits=12, decimal_places=2, default=0,
fa1f7426
EMS
254 )
255 salaire_max = models.DecimalField(
7ad3b930 256 max_digits=12, decimal_places=2, default=0,
fa1f7426
EMS
257 )
258 indemn_min = models.DecimalField(
7ad3b930 259 max_digits=12, decimal_places=2, default=0,
fa1f7426
EMS
260 )
261 indemn_max = models.DecimalField(
7ad3b930 262 max_digits=12, decimal_places=2, default=0,
fa1f7426
EMS
263 )
264 autre_min = models.DecimalField(
7ad3b930 265 max_digits=12, decimal_places=2, default=0,
fa1f7426
EMS
266 )
267 autre_max = models.DecimalField(
7ad3b930 268 max_digits=12, decimal_places=2, default=0,
fa1f7426 269 )
83b7692b 270
271 # Comparatifs de rémunération
fa1f7426
EMS
272 devise_comparaison = models.ForeignKey(
273 'Devise', null=True, blank=True, db_column='devise_comparaison',
274 related_name='+'
275 )
276 comp_locale_min = models.DecimalField(
277 max_digits=12, decimal_places=2, null=True, blank=True
278 )
279 comp_locale_max = models.DecimalField(
280 max_digits=12, decimal_places=2, null=True, blank=True
281 )
282 comp_universite_min = models.DecimalField(
283 max_digits=12, decimal_places=2, null=True, blank=True
284 )
285 comp_universite_max = models.DecimalField(
286 max_digits=12, decimal_places=2, null=True, blank=True
287 )
288 comp_fonctionpub_min = models.DecimalField(
289 max_digits=12, decimal_places=2, null=True, blank=True
290 )
291 comp_fonctionpub_max = models.DecimalField(
292 max_digits=12, decimal_places=2, null=True, blank=True
293 )
294 comp_ong_min = models.DecimalField(
295 max_digits=12, decimal_places=2, null=True, blank=True
296 )
297 comp_ong_max = models.DecimalField(
298 max_digits=12, decimal_places=2, null=True, blank=True
299 )
300 comp_autre_min = models.DecimalField(
301 max_digits=12, decimal_places=2, null=True, blank=True
302 )
303 comp_autre_max = models.DecimalField(
304 max_digits=12, decimal_places=2, null=True, blank=True
305 )
83b7692b 306
307 # Justification
6e4600ef 308 justification = models.TextField(null=True, blank=True)
83b7692b 309
2d4d2fcf 310 # Autres Metadata
fa1f7426 311 date_debut = models.DateField(
8bb6f549
EMS
312 u"date de début", help_text=HELP_TEXT_DATE, null=True, blank=True,
313 db_index=True
fa1f7426
EMS
314 )
315 date_fin = models.DateField(
8bb6f549
EMS
316 u"date de fin", help_text=HELP_TEXT_DATE, null=True, blank=True,
317 db_index=True
fa1f7426 318 )
6e4600ef 319
320 class Meta:
37868f0b 321 abstract = True
6e4600ef 322 ordering = ['implantation__nom', 'nom']
c1195471
OL
323 verbose_name = u"Poste"
324 verbose_name_plural = u"Postes"
30e3cf7c 325 ordering = ["nom"]
6e4600ef 326
83b7692b 327 def __unicode__(self):
fa1f7426
EMS
328 representation = u'%s - %s [%s]' % (
329 self.implantation, self.nom, self.id
330 )
8c1ae2b3 331 return representation
ca1a7b76 332
b0cf30b8 333 prefix_implantation = "implantation__zone_administrative"
fa1f7426 334
b0cf30b8
EMS
335 def get_zones_administratives(self):
336 return [self.implantation.zone_administrative]
4c53dda4 337
552d0db7 338 def get_devise(self):
fa1f7426
EMS
339 vp = ValeurPoint.objects.filter(
340 implantation=self.implantation, devise__archive=False
341 ).order_by('annee')
523c8c0f
EMS
342 if len(vp) > 0:
343 return vp[0].devise
344 else:
345 return Devise.objects.get(code='EUR')
4c53dda4 346
fa1f7426 347
4c53dda4
OL
348class Poste(Poste_):
349 __doc__ = Poste_.__doc__
350
351 # meta dématérialisation : pour permettre le filtrage
fa1f7426 352 vacant = models.NullBooleanField(u"vacant", null=True, blank=True)
4c53dda4 353
8c1ae2b3 354 def is_vacant(self):
23102192
DB
355 vacant = True
356 if self.occupe_par():
357 vacant = False
358 return vacant
359
360 def occupe_par(self):
fa1f7426
EMS
361 """
362 Retourne la liste d'employé occupant ce poste.
23102192
DB
363 Généralement, retourne une liste d'un élément.
364 Si poste inoccupé, retourne liste vide.
4c53dda4 365 UTILISE pour mettre a jour le flag vacant
23102192 366 """
fa1f7426 367 return [
45066657
EMS
368 d.employe
369 for d in self.rh_dossiers.exclude(date_fin__lt=date.today())
fa1f7426 370 ]
83b7692b 371
d104b0ae
EMS
372reversion.register(Poste, format='xml', follow=[
373 'rh_financements', 'rh_pieces', 'rh_comparaisons_internes',
45066657 374 'commentaires'
d104b0ae
EMS
375])
376
37868f0b 377
83b7692b 378POSTE_FINANCEMENT_CHOICES = (
379 ('A', 'A - Frais de personnel'),
380 ('B', 'B - Projet(s)-Titre(s)'),
381 ('C', 'C - Autre')
382)
383
6e7c919b
NC
384
385class PosteFinancement_(models.Model):
fa1f7426
EMS
386 """
387 Pour un Poste, structure d'informations décrivant comment on prévoit
6e4600ef 388 financer ce Poste.
389 """
83b7692b 390 type = models.CharField(max_length=1, choices=POSTE_FINANCEMENT_CHOICES)
fa1f7426
EMS
391 pourcentage = models.DecimalField(
392 max_digits=12, decimal_places=2,
393 help_text="ex.: 33.33 % (décimale avec point)"
394 )
83b7692b 395 commentaire = models.TextField(
fa1f7426
EMS
396 help_text="Spécifiez la source de financement."
397 )
83b7692b 398
399 class Meta:
6e7c919b 400 abstract = True
83b7692b 401 ordering = ['type']
ca1a7b76 402
6e4600ef 403 def __unicode__(self):
a184c555 404 return u'%s : %s %%' % (self.type, self.pourcentage)
83b7692b 405
abf91905
JPC
406 def choix(self):
407 return u"%s" % dict(POSTE_FINANCEMENT_CHOICES)[self.type]
408
6e7c919b
NC
409
410class PosteFinancement(PosteFinancement_):
4ba84959
EMS
411 poste = models.ForeignKey(
412 Poste, db_column='poste', related_name='rh_financements'
413 )
6e7c919b 414
d104b0ae
EMS
415reversion.register(PosteFinancement, format='xml')
416
6e7c919b 417
317ce433 418class PostePiece_(models.Model):
fa1f7426
EMS
419 """
420 Documents relatifs au Poste.
7abc6d45 421 Ex.: Description de poste
422 """
fa1f7426
EMS
423 nom = models.CharField(u"Nom", max_length=255)
424 fichier = models.FileField(
425 u"Fichier", upload_to=poste_piece_dispatch, storage=storage_prive
426 )
83b7692b 427
6e4600ef 428 class Meta:
317ce433 429 abstract = True
6e4600ef 430 ordering = ['nom']
ca1a7b76 431
6e4600ef 432 def __unicode__(self):
433 return u'%s' % (self.nom)
434
fa1f7426 435
317ce433 436class PostePiece(PostePiece_):
4ba84959
EMS
437 poste = models.ForeignKey(
438 Poste, db_column='poste', related_name='rh_pieces'
439 )
317ce433 440
d104b0ae
EMS
441reversion.register(PostePiece, format='xml')
442
fa1f7426 443
45066657 444class PosteComparaison_(models.Model, DevisableMixin):
068d1462 445 """
fa1f7426
EMS
446 De la même manière qu'un dossier, un poste peut-être comparé à un autre
447 poste.
068d1462 448 """
16b1454e
OL
449 objects = PosteComparaisonManager()
450
fa1f7426
EMS
451 implantation = models.ForeignKey(
452 ref.Implantation, null=True, blank=True, related_name="+"
453 )
454 nom = models.CharField(u"Poste", max_length=255, null=True, blank=True)
068d1462 455 montant = models.IntegerField(null=True)
fa1f7426
EMS
456 devise = models.ForeignKey(
457 "Devise", related_name='+', null=True, blank=True
458 )
1d0f4eef 459
317ce433
OL
460 class Meta:
461 abstract = True
462
783e077a
JPC
463 def __unicode__(self):
464 return self.nom
465
fa1f7426 466
317ce433 467class PosteComparaison(PosteComparaison_):
4ba84959
EMS
468 poste = models.ForeignKey(
469 Poste, related_name='rh_comparaisons_internes'
470 )
471
d104b0ae
EMS
472reversion.register(PosteComparaison, format='xml')
473
fa1f7426 474
4ba84959 475class PosteCommentaire(Commentaire):
fa1f7426 476 poste = models.ForeignKey(
4ba84959 477 Poste, db_column='poste', related_name='commentaires'
fa1f7426 478 )
83b7692b 479
d104b0ae 480reversion.register(PosteCommentaire, format='xml')
2d4d2fcf 481
83b7692b 482### EMPLOYÉ/PERSONNE
e9bbd6ba 483
45066657 484class Employe(models.Model):
fa1f7426
EMS
485 """
486 Personne occupant ou ayant occupé un Poste. Un Employe aura autant de
6e4600ef 487 Dossiers qu'il occupe ou a occupé de Postes.
ca1a7b76
EMS
488
489 Cette classe aurait pu avantageusement s'appeler Personne car la notion
6e4600ef 490 d'employé n'a pas de sens si aucun Dossier n'existe pour une personne.
491 """
6fb68b2f
DB
492
493 objects = EmployeManager()
d104b0ae 494
9afaa55e 495 # Identification
e9bbd6ba 496 nom = models.CharField(max_length=255)
fa1f7426
EMS
497 prenom = models.CharField(u"prénom", max_length=255)
498 nom_affichage = models.CharField(
499 u"nom d'affichage", max_length=255, null=True, blank=True
500 )
501 nationalite = models.ForeignKey(
502 ref.Pays, to_field='code', db_column='nationalite',
503 related_name='employes_nationalite', verbose_name=u"nationalité",
504 blank=True, null=True
505 )
506 date_naissance = models.DateField(
507 u"date de naissance", help_text=HELP_TEXT_DATE,
508 validators=[validate_date_passee], null=True, blank=True
509 )
2d4d2fcf 510 genre = models.CharField(max_length=1, choices=GENRE_CHOICES)
ca1a7b76 511
9afaa55e 512 # Infos personnelles
fa1f7426
EMS
513 situation_famille = models.CharField(
514 u"situation familiale", max_length=1, choices=SITUATION_CHOICES,
515 null=True, blank=True
516 )
517 date_entree = models.DateField(
518 u"date d'entrée à l'AUF", help_text=HELP_TEXT_DATE, null=True,
519 blank=True
520 )
ca1a7b76 521
9afaa55e 522 # Coordonnées
fa1f7426
EMS
523 tel_domicile = models.CharField(
524 u"tél. domicile", max_length=255, null=True, blank=True
525 )
526 tel_cellulaire = models.CharField(
527 u"tél. cellulaire", max_length=255, null=True, blank=True
528 )
e9bbd6ba 529 adresse = models.CharField(max_length=255, null=True, blank=True)
e9bbd6ba 530 ville = models.CharField(max_length=255, null=True, blank=True)
531 province = models.CharField(max_length=255, null=True, blank=True)
532 code_postal = models.CharField(max_length=255, null=True, blank=True)
fa1f7426
EMS
533 pays = models.ForeignKey(
534 ref.Pays, to_field='code', db_column='pays',
535 related_name='employes', null=True, blank=True
536 )
89a8df07
EMS
537 courriel_perso = models.EmailField(
538 u'adresse courriel personnelle', blank=True
539 )
9afaa55e 540
a45e414b 541 # meta dématérialisation : pour permettre le filtrage
fa1f7426 542 nb_postes = models.IntegerField(u"nombre de postes", null=True, blank=True)
a45e414b 543
6e4600ef 544 class Meta:
fa1f7426 545 ordering = ['nom', 'prenom']
c1195471
OL
546 verbose_name = u"Employé"
547 verbose_name_plural = u"Employés"
ca1a7b76 548
9afaa55e 549 def __unicode__(self):
a2c3ad52 550 return u'%s %s [%s]' % (self.nom.upper(), self.prenom, self.id)
ca1a7b76 551
d04d084c 552 def civilite(self):
553 civilite = u''
554 if self.genre.upper() == u'M':
555 civilite = u'M.'
556 elif self.genre.upper() == u'F':
557 civilite = u'Mme'
558 return civilite
ca1a7b76 559
5ea6b5bb 560 def url_photo(self):
fa1f7426
EMS
561 """
562 Retourne l'URL du service retournant la photo de l'Employe.
5ea6b5bb 563 Équivalent reverse url 'rh_photo' avec id en param.
564 """
565 from django.core.urlresolvers import reverse
fa1f7426 566 return reverse('rh_photo', kwargs={'id': self.id})
ca1a7b76 567
c267f20c 568 def dossiers_passes(self):
6bee05ff 569 params = {KEY_STATUT: STATUT_INACTIF, }
da202402 570 search = RechercheTemporelle(params, Dossier)
6bee05ff 571 search.purge_params(params)
dcd1b959
OL
572 q = search.get_q_temporel(self.rh_dossiers)
573 return self.rh_dossiers.filter(q)
ca1a7b76 574
c267f20c 575 def dossiers_futurs(self):
6bee05ff 576 params = {KEY_STATUT: STATUT_FUTUR, }
da202402 577 search = RechercheTemporelle(params, Dossier)
6bee05ff 578 search.purge_params(params)
dcd1b959
OL
579 q = search.get_q_temporel(self.rh_dossiers)
580 return self.rh_dossiers.filter(q)
ca1a7b76 581
c267f20c 582 def dossiers_encours(self):
6bee05ff 583 params = {KEY_STATUT: STATUT_ACTIF, }
da202402 584 search = RechercheTemporelle(params, Dossier)
6bee05ff 585 search.purge_params(params)
dcd1b959
OL
586 q = search.get_q_temporel(self.rh_dossiers)
587 return self.rh_dossiers.filter(q)
ca1a7b76 588
5db1c5a3 589 def dossier_principal(self):
db265492
EMS
590 """
591 Retourne le dossier principal (ou le plus ancien si il y en a
592 plusieurs)
5db1c5a3
DB
593 """
594 try:
db265492
EMS
595 dossier = self.rh_dossiers \
596 .filter(principal=True).order_by('date_debut')[0]
5db1c5a3
DB
597 except IndexError, Dossier.DoesNotExist:
598 dossier = None
599 return dossier
600
35c0c2fe 601 def postes_encours(self):
602 postes_encours = set()
603 for d in self.dossiers_encours():
604 postes_encours.add(d.poste)
605 return postes_encours
ca1a7b76 606
35c0c2fe 607 def poste_principal(self):
65f9fac8 608 """
609 Retourne le Poste du premier Dossier créé parmi les Dossiers en cours.
ca1a7b76 610 Idée derrière :
65f9fac8 611 si on ajout d'autre Dossiers, c'est pour des Postes secondaires.
612 """
5db1c5a3 613 # DEPRECATED : on a maintenant Dossier.principal
65f9fac8 614 poste = Poste.objects.none()
615 try:
616 poste = self.dossiers_encours().order_by('date_debut')[0].poste
617 except:
618 pass
619 return poste
9afaa55e 620
b0cf30b8
EMS
621 prefix_implantation = \
622 "rh_dossiers__poste__implantation__zone_administrative"
fa1f7426 623
b0cf30b8
EMS
624 def get_zones_administratives(self):
625 return [
626 d.poste.implantation.zone_administrative
627 for d in self.dossiers.all()
628 ]
aff1a4c6 629
d104b0ae 630reversion.register(Employe, format='xml', follow=[
45066657 631 'pieces', 'commentaires', 'ayantdroits'
d104b0ae
EMS
632])
633
aff1a4c6 634
7abc6d45 635class EmployePiece(models.Model):
fa1f7426
EMS
636 """
637 Documents relatifs à un employé.
7abc6d45 638 Ex.: CV...
639 """
fa1f7426
EMS
640 employe = models.ForeignKey(
641 'Employe', db_column='employe', related_name="pieces",
642 verbose_name=u"employé"
643 )
644 nom = models.CharField(max_length=255)
645 fichier = models.FileField(
646 u"fichier", upload_to=employe_piece_dispatch, storage=storage_prive
647 )
7abc6d45 648
6e4600ef 649 class Meta:
650 ordering = ['nom']
f9e54d59
PP
651 verbose_name = u"Employé pièce"
652 verbose_name_plural = u"Employé pièces"
653
6e4600ef 654 def __unicode__(self):
655 return u'%s' % (self.nom)
656
d104b0ae
EMS
657reversion.register(EmployePiece, format='xml')
658
fa1f7426 659
07b40eda 660class EmployeCommentaire(Commentaire):
fa1f7426 661 employe = models.ForeignKey(
d104b0ae 662 'Employe', db_column='employe', related_name='commentaires'
fa1f7426 663 )
9afaa55e 664
b343eb3d
PP
665 class Meta:
666 verbose_name = u"Employé commentaire"
667 verbose_name_plural = u"Employé commentaires"
668
d104b0ae
EMS
669reversion.register(EmployeCommentaire, format='xml')
670
2d4d2fcf 671
e9bbd6ba 672LIEN_PARENTE_CHOICES = (
673 ('Conjoint', 'Conjoint'),
674 ('Conjointe', 'Conjointe'),
675 ('Fille', 'Fille'),
676 ('Fils', 'Fils'),
677)
678
fa1f7426 679
45066657 680class AyantDroit(models.Model):
fa1f7426
EMS
681 """
682 Personne en relation avec un Employe.
6e4600ef 683 """
9afaa55e 684 # Identification
e9bbd6ba 685 nom = models.CharField(max_length=255)
fa1f7426
EMS
686 prenom = models.CharField(u"prénom", max_length=255)
687 nom_affichage = models.CharField(
688 u"nom d'affichage", max_length=255, null=True, blank=True
689 )
690 nationalite = models.ForeignKey(
691 ref.Pays, to_field='code', db_column='nationalite',
692 related_name='ayantdroits_nationalite',
693 verbose_name=u"nationalité", null=True, blank=True
694 )
695 date_naissance = models.DateField(
696 u"Date de naissance", help_text=HELP_TEXT_DATE,
697 validators=[validate_date_passee], null=True, blank=True
698 )
2d4d2fcf 699 genre = models.CharField(max_length=1, choices=GENRE_CHOICES)
ca1a7b76 700
9afaa55e 701 # Relation
fa1f7426
EMS
702 employe = models.ForeignKey(
703 'Employe', db_column='employe', related_name='ayantdroits',
704 verbose_name=u"Employé"
705 )
706 lien_parente = models.CharField(
707 u"lien de parenté", max_length=10, choices=LIEN_PARENTE_CHOICES,
708 null=True, blank=True
709 )
6e4600ef 710
711 class Meta:
a2c3ad52 712 ordering = ['nom', ]
c1195471
OL
713 verbose_name = u"Ayant droit"
714 verbose_name_plural = u"Ayants droit"
ca1a7b76 715
6e4600ef 716 def __unicode__(self):
2de29065 717 return u'%s %s' % (self.nom.upper(), self.prenom, )
83b7692b 718
b0cf30b8
EMS
719 prefix_implantation = \
720 "employe__dossiers__poste__implantation__zone_administrative"
fa1f7426 721
b0cf30b8
EMS
722 def get_zones_administratives(self):
723 return [
724 d.poste.implantation.zone_administrative
725 for d in self.employe.dossiers.all()
726 ]
aff1a4c6 727
d104b0ae
EMS
728reversion.register(AyantDroit, format='xml', follow=['commentaires'])
729
aff1a4c6 730
07b40eda 731class AyantDroitCommentaire(Commentaire):
fa1f7426 732 ayant_droit = models.ForeignKey(
d104b0ae 733 'AyantDroit', db_column='ayant_droit', related_name='commentaires'
fa1f7426 734 )
83b7692b 735
d104b0ae
EMS
736reversion.register(AyantDroitCommentaire, format='xml')
737
2d4d2fcf 738
83b7692b 739### DOSSIER
740
741STATUT_RESIDENCE_CHOICES = (
742 ('local', 'Local'),
743 ('expat', 'Expatrié'),
744)
745
746COMPTE_COMPTA_CHOICES = (
747 ('coda', 'CODA'),
748 ('scs', 'SCS'),
749 ('aucun', 'Aucun'),
750)
751
fa1f7426 752
52f4c1e7 753class Dossier_(DateActiviteMixin, models.Model, DevisableMixin,):
fa1f7426
EMS
754 """
755 Le Dossier regroupe les informations relatives à l'occupation
6e4600ef 756 d'un Poste par un Employe. Un seul Dossier existe par Poste occupé
757 par un Employe.
ca1a7b76 758
6e4600ef 759 Plusieurs Contrats peuvent être associés au Dossier.
760 Une structure de Remuneration est rattachée au Dossier. Un Poste pour
761 lequel aucun Dossier n'existe est un poste vacant.
762 """
3f5cbabe
OL
763
764 objects = DossierManager()
765
63e17dff 766 # TODO: OneToOne ??
eb6bf568 767 statut = models.ForeignKey('Statut', related_name='+', null=True)
fa1f7426
EMS
768 organisme_bstg = models.ForeignKey(
769 'OrganismeBstg', db_column='organisme_bstg', related_name='+',
770 verbose_name=u"organisme",
771 help_text=(
772 u"Si détaché (DET) ou mis à disposition (MAD), "
773 u"préciser l'organisme."
774 ), null=True, blank=True
775 )
ca1a7b76 776
83b7692b 777 # Recrutement
2d4d2fcf 778 remplacement = models.BooleanField(default=False)
fa1f7426
EMS
779 remplacement_de = models.ForeignKey(
780 'self', related_name='+', help_text=u"Taper le nom de l'employé",
781 null=True, blank=True
782 )
783 statut_residence = models.CharField(
784 u"statut", max_length=10, default='local', null=True,
785 choices=STATUT_RESIDENCE_CHOICES
786 )
ca1a7b76 787
83b7692b 788 # Rémunération
fa1f7426
EMS
789 classement = models.ForeignKey(
790 'Classement', db_column='classement', related_name='+', null=True,
791 blank=True
792 )
793 regime_travail = models.DecimalField(
794 u"régime de travail", max_digits=12, null=True, decimal_places=2,
795 default=REGIME_TRAVAIL_DEFAULT, help_text="% du temps complet"
796 )
797 regime_travail_nb_heure_semaine = models.DecimalField(
798 u"nb. heures par semaine", max_digits=12,
799 decimal_places=2, null=True,
800 default=REGIME_TRAVAIL_NB_HEURE_SEMAINE_DEFAULT,
801 help_text=REGIME_TRAVAIL_NB_HEURE_SEMAINE_HELP_TEXT
802 )
7abc6d45 803
804 # Occupation du Poste par cet Employe (anciennement "mandat")
8bb6f549
EMS
805 date_debut = models.DateField(
806 u"date de début d'occupation de poste", db_index=True
807 )
fa1f7426 808 date_fin = models.DateField(
8bb6f549
EMS
809 u"Date de fin d'occupation de poste", null=True, blank=True,
810 db_index=True
fa1f7426 811 )
ca1a7b76 812
84934747
BS
813 # Meta-data:
814 est_cadre = models.BooleanField(
9623a926 815 u"Est un cadre?",
84934747
BS
816 default=False,
817 )
818
2d4d2fcf 819 # Comptes
820 # TODO?
ca1a7b76 821
6e4600ef 822 class Meta:
37868f0b 823 abstract = True
49449367 824 ordering = ['employe__nom', ]
3f5f3898 825 verbose_name = u"Dossier"
8c1ae2b3 826 verbose_name_plural = "Dossiers"
ca1a7b76 827
65f9fac8 828 def salaire_theorique(self):
829 annee = date.today().year
830 coeff = self.classement.coefficient
831 implantation = self.poste.implantation
832 point = ValeurPoint.objects.get(implantation=implantation, annee=annee)
ca1a7b76 833
65f9fac8 834 montant = coeff * point.valeur
835 devise = point.devise
fa1f7426 836 return {'montant': montant, 'devise': devise}
ca1a7b76 837
83b7692b 838 def __unicode__(self):
8c1ae2b3 839 poste = self.poste.nom
840 if self.employe.genre == 'F':
ca1a7b76 841 poste = self.poste.nom_feminin
8c1ae2b3 842 return u'%s - %s' % (self.employe, poste)
83b7692b 843
b0cf30b8 844 prefix_implantation = "poste__implantation__zone_administrative"
fa1f7426 845
b0cf30b8
EMS
846 def get_zones_administratives(self):
847 return [self.poste.implantation.zone_administrative]
aff1a4c6 848
3ebc0952 849 def remunerations(self):
838bc59d
OL
850 key = "%s_remunerations" % self._meta.app_label
851 remunerations = getattr(self, key)
852 return remunerations.all().order_by('-date_debut')
3ebc0952 853
02e69aa2 854 def remunerations_en_cours(self):
838bc59d
OL
855 q = Q(date_fin__exact=None) | Q(date_fin__gt=datetime.date.today())
856 return self.remunerations().all().filter(q).order_by('date_debut')
02e69aa2 857
09aa8374
OL
858 def get_salaire(self):
859 try:
fa1f7426
EMS
860 return [r for r in self.remunerations().order_by('-date_debut')
861 if r.type_id == 1][0]
09aa8374
OL
862 except:
863 return None
3ebc0952 864
838bc59d
OL
865 def get_salaire_euros(self):
866 tx = self.taux_devise()
867 return (float)(tx) * (float)(self.salaire)
868
869 def get_remunerations_brutes(self):
870 """
871 1 Salaire de base
872 3 Indemnité de base
873 4 Indemnité d'expatriation
874 5 Indemnité pour frais
875 6 Indemnité de logement
876 7 Indemnité de fonction
877 8 Indemnité de responsabilité
878 9 Indemnité de transport
879 10 Indemnité compensatrice
880 11 Indemnité de subsistance
881 12 Indemnité différentielle
882 13 Prime d'installation
883 14 Billet d'avion
884 15 Déménagement
885 16 Indemnité de départ
886 18 Prime de 13ième mois
887 19 Prime d'intérim
888 """
fa1f7426
EMS
889 ids = [1, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 18, 19]
890 return [r for r in self.remunerations_en_cours().all()
891 if r.type_id in ids]
838bc59d
OL
892
893 def get_charges_salariales(self):
894 """
895 20 Charges salariales ?
896 """
fa1f7426
EMS
897 ids = [20]
898 return [r for r in self.remunerations_en_cours().all()
899 if r.type_id in ids]
838bc59d 900
838bc59d
OL
901 def get_charges_patronales(self):
902 """
903 17 Charges patronales
904 """
fa1f7426
EMS
905 ids = [17]
906 return [r for r in self.remunerations_en_cours().all()
907 if r.type_id in ids]
838bc59d 908
552d0db7
OL
909 def get_remunerations_tierces(self):
910 """
911 2 Salaire MAD
912 """
fa1f7426
EMS
913 return [r for r in self.remunerations_en_cours().all()
914 if r.type_id in (2,)]
552d0db7
OL
915
916 # DEVISE LOCALE
917
918 def get_total_local_charges_salariales(self):
bc17b82c 919 devise = self.poste.get_devise()
552d0db7
OL
920 total = 0.0
921 for r in self.get_charges_salariales():
bc17b82c
OL
922 if r.devise != devise:
923 return None
924 total += float(r.montant)
552d0db7
OL
925 return total
926
927 def get_total_local_charges_patronales(self):
bc17b82c 928 devise = self.poste.get_devise()
552d0db7
OL
929 total = 0.0
930 for r in self.get_charges_patronales():
bc17b82c
OL
931 if r.devise != devise:
932 return None
552d0db7
OL
933 total += float(r.montant)
934 return total
935
936 def get_local_salaire_brut(self):
937 """
938 somme des rémuérations brutes
939 """
940 devise = self.poste.get_devise()
941 total = 0.0
942 for r in self.get_remunerations_brutes():
943 if r.devise != devise:
944 return None
945 total += float(r.montant)
946 return total
947
948 def get_local_salaire_net(self):
949 """
950 salaire brut - charges salariales
951 """
952 devise = self.poste.get_devise()
953 total_charges = 0.0
954 for r in self.get_charges_salariales():
955 if r.devise != devise:
956 return None
957 total_charges += float(r.montant)
958 return self.get_local_salaire_brut() - total_charges
959
960 def get_local_couts_auf(self):
961 """
962 salaire net + charges patronales
963 """
964 devise = self.poste.get_devise()
965 total_charges = 0.0
966 for r in self.get_charges_patronales():
967 if r.devise != devise:
968 return None
969 total_charges += float(r.montant)
970 return self.get_local_salaire_net() + total_charges
971
972 def get_total_local_remunerations_tierces(self):
973 devise = self.poste.get_devise()
974 total = 0.0
975 for r in self.get_remunerations_tierces():
976 if r.devise != devise:
977 return None
978 total += float(r.montant)
979 return total
980
981 # DEVISE EURO
982
983 def get_total_charges_salariales(self):
984 total = 0.0
985 for r in self.get_charges_salariales():
986 total += r.montant_euros()
987 return total
988
838bc59d
OL
989 def get_total_charges_patronales(self):
990 total = 0.0
991 for r in self.get_charges_patronales():
992 total += r.montant_euros()
993 return total
994
995 def get_salaire_brut(self):
996 """
997 somme des rémuérations brutes
998 """
999 total = 0.0
1000 for r in self.get_remunerations_brutes():
1001 total += r.montant_euros()
1002 return total
1003
1004 def get_salaire_net(self):
1005 """
1006 salaire brut - charges salariales
1007 """
1008 total_charges = 0.0
1009 for r in self.get_charges_salariales():
1010 total_charges += r.montant_euros()
1011 return self.get_salaire_brut() - total_charges
1012
1013 def get_couts_auf(self):
1014 """
1015 salaire net + charges patronales
1016 """
1017 total_charges = 0.0
1018 for r in self.get_charges_patronales():
1019 total_charges += r.montant_euros()
1020 return self.get_salaire_net() + total_charges
1021
838bc59d
OL
1022 def get_total_remunerations_tierces(self):
1023 total = 0.0
1024 for r in self.get_remunerations_tierces():
1025 total += r.montant_euros()
1026 return total
1027
5db1c5a3
DB
1028 def premier_contrat(self):
1029 """contrat avec plus petite date de début"""
1030 try:
db265492
EMS
1031 contrat = self.rh_contrats.exclude(date_debut=None) \
1032 .order_by('date_debut')[0]
5db1c5a3
DB
1033 except IndexError, Contrat.DoesNotExist:
1034 contrat = None
1035 return contrat
db265492 1036
5db1c5a3
DB
1037 def dernier_contrat(self):
1038 """contrat avec plus grande date de fin"""
1039 try:
db265492
EMS
1040 contrat = self.rh_contrats.exclude(date_debut=None) \
1041 .order_by('-date_debut')[0]
5db1c5a3
DB
1042 except IndexError, Contrat.DoesNotExist:
1043 contrat = None
1044 return contrat
1045
bfb5e43e
EMS
1046 def actif(self):
1047 today = date.today()
1048 return (self.date_debut is None or self.date_debut <= today) \
1049 and (self.date_fin is None or self.date_fin >= today) \
1050 and not (self.date_fin is None and self.date_debut is None)
1051
22343fe7 1052
37868f0b
NC
1053class Dossier(Dossier_):
1054 __doc__ = Dossier_.__doc__
4ba84959
EMS
1055 poste = models.ForeignKey(
1056 Poste, db_column='poste', related_name='rh_dossiers',
0b0545bd 1057 help_text=u"Taper le nom du poste ou du type de poste",
4ba84959 1058 )
fa1f7426
EMS
1059 employe = models.ForeignKey(
1060 'Employe', db_column='employe',
1061 help_text=u"Taper le nom de l'employé",
4ba84959
EMS
1062 related_name='rh_dossiers', verbose_name=u"employé"
1063 )
fa1f7426 1064 principal = models.BooleanField(
c1f5d83c 1065 u"dossier principal", default=True,
fa1f7426
EMS
1066 help_text=(
1067 u"Ce dossier est pour le principal poste occupé par l'employé"
1068 )
1069 )
343cfd9c 1070
37868f0b 1071
d104b0ae
EMS
1072reversion.register(Dossier, format='xml', follow=[
1073 'rh_dossierpieces', 'rh_comparaisons', 'rh_remunerations',
1074 'rh_contrats', 'commentaires'
1075])
1076
37868f0b 1077
9388fbac
BS
1078class RHDossierClassementRecord(models.Model):
1079 classement = models.ForeignKey(
1080 'Classement',
1081 related_name='classement_records',
1082 )
1083 dossier = models.ForeignKey(
1084 'Dossier',
1085 related_name='classement_records',
1086 )
1087 date_debut = models.DateField(
1088 u"date de début",
1089 help_text=HELP_TEXT_DATE,
1090 null=True,
1091 blank=True,
1092 db_index=True
1093 )
1094 date_fin = models.DateField(
1095 u"date de fin",
1096 help_text=HELP_TEXT_DATE,
1097 null=True,
1098 blank=True,
1099 db_index=True
1100 )
1101
1102 def __unicode__(self):
1103 return self.classement.__unicode__()
1104
1105 class Meta:
1106 verbose_name = u"Element d'historique de classement"
1107 verbose_name_plural = u"Historique de classement"
1108
1109 @classmethod
1110 def post_save_handler(cls,
1111 sender,
1112 instance,
1113 created,
1114 using,
1115 **kw):
1116
990c9e12
BS
1117 today = date.today()
1118 previous_record = None
9388fbac
BS
1119 previous_classement = None
1120 has_changed = False
1121
990c9e12
BS
1122 # Premièrement, pour les nouvelles instances:
1123 if created:
1124 if not self.classement:
1125 return
1126 else:
1127 cls.objects.create(
1128 date_debut=instance.date_debut,
1129 classement=instance.classement,
9388fbac 1130 dossier=instance,
9388fbac 1131 )
990c9e12 1132 return
9388fbac 1133
990c9e12 1134 # Deuxièmement, pour les instances existantes:
9388fbac 1135
990c9e12
BS
1136 # Détermine si:
1137 # 1. Est-ce que le classement a changé?
1138 # 2. Est-ce qu'une historique de classement existe déjà
1139 try:
1140 previous_record = cls.objects.get(
9388fbac 1141 dossier=instance,
990c9e12
BS
1142 classement=instance.before_save.classement,
1143 date_fin=None,
9388fbac 1144 )
990c9e12
BS
1145 except cls.DoesNotExist:
1146 if instance.before_save.classement:
1147 # Il était censé avoir une historique de classement
1148 # donc on le créé.
1149 previous_record = cls.objects.create(
1150 date_debut=instance.before_save.date_debut,
1151 classement=instance.before_save.classement,
1152 dossier=instance,
1153 )
1154 previous_classement = instance.before_save.classement
9388fbac 1155
990c9e12
BS
1156 else:
1157 previous_classement = previous_record.classement
9388fbac 1158
990c9e12
BS
1159 has_changed = (
1160 instance.classement !=
1161 previous_classement
1162 )
1163
1164 # Cas aucun changement:
1165 if not has_changed:
1166 return
1167
1168 else:
1169 # Classement a changé
1170 if previous_record:
1171 previous_record.date_fin = today
1172 previous_record.save()
9388fbac 1173
990c9e12
BS
1174 if instance.classement:
1175 cls.objects.create(
1176 date_debut=today,
1177 classement=instance.classement,
1178 dossier=instance,
1179 )
9388fbac
BS
1180
1181
fc917340 1182class DossierPiece_(models.Model):
fa1f7426
EMS
1183 """
1184 Documents relatifs au Dossier (à l'occupation de ce poste par employé).
7abc6d45 1185 Ex.: Lettre de motivation.
1186 """
fa1f7426
EMS
1187 nom = models.CharField(max_length=255)
1188 fichier = models.FileField(
1189 upload_to=dossier_piece_dispatch, storage=storage_prive
1190 )
83b7692b 1191
6e4600ef 1192 class Meta:
fc917340 1193 abstract = True
6e4600ef 1194 ordering = ['nom']
ca1a7b76 1195
6e4600ef 1196 def __unicode__(self):
1197 return u'%s' % (self.nom)
1198
fa1f7426 1199
fc917340 1200class DossierPiece(DossierPiece_):
fa1f7426 1201 dossier = models.ForeignKey(
4ba84959 1202 Dossier, db_column='dossier', related_name='rh_dossierpieces'
fa1f7426
EMS
1203 )
1204
d104b0ae 1205reversion.register(DossierPiece, format='xml')
fc917340 1206
4ba84959
EMS
1207class DossierCommentaire(Commentaire):
1208 dossier = models.ForeignKey(
1209 Dossier, db_column='dossier', related_name='commentaires'
1210 )
fc917340 1211
d104b0ae
EMS
1212reversion.register(DossierCommentaire, format='xml')
1213
fa1f7426 1214
e84c8ef1 1215class DossierComparaison_(models.Model, DevisableMixin):
1d0f4eef
OL
1216 """
1217 Photo d'une comparaison salariale au moment de l'embauche.
1218 """
16b1454e
OL
1219 objects = DossierComparaisonManager()
1220
fa1f7426
EMS
1221 implantation = models.ForeignKey(
1222 ref.Implantation, related_name="+", null=True, blank=True
1223 )
1d0f4eef
OL
1224 poste = models.CharField(max_length=255, null=True, blank=True)
1225 personne = models.CharField(max_length=255, null=True, blank=True)
1226 montant = models.IntegerField(null=True)
fa1f7426
EMS
1227 devise = models.ForeignKey(
1228 'Devise', related_name='+', null=True, blank=True
1229 )
1d0f4eef 1230
fc917340
OL
1231 class Meta:
1232 abstract = True
1233
3b14230d
OL
1234 def __unicode__(self):
1235 return "%s (%s)" % (self.poste, self.personne)
1236
1d0f4eef 1237
fc917340 1238class DossierComparaison(DossierComparaison_):
4ba84959
EMS
1239 dossier = models.ForeignKey(
1240 Dossier, related_name='rh_comparaisons'
1241 )
2d4d2fcf 1242
d104b0ae
EMS
1243reversion.register(DossierComparaison, format='xml')
1244
fa1f7426 1245
07b40eda 1246### RÉMUNÉRATION
ca1a7b76 1247
45066657 1248class RemunerationMixin(models.Model):
fa1f7426 1249
9afaa55e 1250 # Identification
fa1f7426
EMS
1251 type = models.ForeignKey(
1252 'TypeRemuneration', db_column='type', related_name='+',
1253 verbose_name=u"type de rémunération"
1254 )
1255 type_revalorisation = models.ForeignKey(
1256 'TypeRevalorisation', db_column='type_revalorisation',
1257 related_name='+', verbose_name=u"type de revalorisation",
1258 null=True, blank=True
1259 )
1260 montant = models.DecimalField(
d104b0ae 1261 null=True, blank=True, max_digits=12, decimal_places=2
fa1f7426
EMS
1262 ) # Annuel (12 mois, 52 semaines, 364 jours?)
1263 devise = models.ForeignKey('Devise', db_column='devise', related_name='+')
1264
2d4d2fcf 1265 # commentaire = precision
1266 commentaire = models.CharField(max_length=255, null=True, blank=True)
fa1f7426 1267
2d4d2fcf 1268 # date_debut = anciennement date_effectif
8bb6f549
EMS
1269 date_debut = models.DateField(
1270 u"date de début", null=True, blank=True, db_index=True
1271 )
1272 date_fin = models.DateField(
1273 u"date de fin", null=True, blank=True, db_index=True
1274 )
ca1a7b76
EMS
1275
1276 class Meta:
2d4d2fcf 1277 abstract = True
6e4600ef 1278 ordering = ['type__nom', '-date_fin']
ca1a7b76 1279
6e4600ef 1280 def __unicode__(self):
1281 return u'%s %s (%s)' % (self.montant, self.devise.code, self.type.nom)
ca1a7b76 1282
fa1f7426 1283
e84c8ef1 1284class Remuneration_(RemunerationMixin, DevisableMixin):
fa1f7426
EMS
1285 """
1286 Structure de rémunération (données budgétaires) en situation normale
ca1a7b76
EMS
1287 pour un Dossier. Si un Evenement existe, utiliser la structure de
1288 rémunération EvenementRemuneration de cet événement.
2d4d2fcf 1289 """
c3550a05 1290 objects = RemunerationManager()
83b7692b 1291
1292 def montant_mois(self):
1293 return round(self.montant / 12, 2)
1294
626beb4d 1295 def montant_avec_regime(self):
fa1f7426 1296 return round(self.montant * (self.dossier.regime_travail / 100), 2)
626beb4d 1297
83b7692b 1298 def montant_euro_mois(self):
e84c8ef1 1299 return round(self.montant_euros() / 12, 2)
ca1a7b76 1300
9afaa55e 1301 def __unicode__(self):
1302 try:
1303 devise = self.devise.code
1304 except:
1305 devise = "???"
1306 return "%s %s" % (self.montant, devise)
83b7692b 1307
6e7c919b
NC
1308 class Meta:
1309 abstract = True
c1195471
OL
1310 verbose_name = u"Rémunération"
1311 verbose_name_plural = u"Rémunérations"
6e7c919b
NC
1312
1313
1314class Remuneration(Remuneration_):
4ba84959
EMS
1315 dossier = models.ForeignKey(
1316 Dossier, db_column='dossier', related_name='rh_remunerations'
1317 )
6e7c919b 1318
d104b0ae
EMS
1319reversion.register(Remuneration, format='xml')
1320
2d4d2fcf 1321
1322### CONTRATS
c41b7fcc 1323
45066657 1324class Contrat_(models.Model):
fa1f7426
EMS
1325 """
1326 Document juridique qui encadre la relation de travail d'un Employe
ca1a7b76 1327 pour un Poste particulier. Pour un Dossier (qui documente cette
2d4d2fcf 1328 relation de travail) plusieurs contrats peuvent être associés.
1329 """
c41b7fcc 1330 objects = ContratManager()
fa1f7426
EMS
1331 type_contrat = models.ForeignKey(
1332 'TypeContrat', db_column='type_contrat',
1333 verbose_name=u'type de contrat', related_name='+'
1334 )
8bb6f549
EMS
1335 date_debut = models.DateField(
1336 u"date de début", db_index=True
1337 )
1338 date_fin = models.DateField(
1339 u"date de fin", null=True, blank=True, db_index=True
1340 )
fa1f7426
EMS
1341 fichier = models.FileField(
1342 upload_to=contrat_dispatch, storage=storage_prive, null=True,
1343 blank=True
1344 )
6e4600ef 1345
1346 class Meta:
fc917340 1347 abstract = True
a2c3ad52 1348 ordering = ['dossier__employe__nom']
c1195471
OL
1349 verbose_name = u"Contrat"
1350 verbose_name_plural = u"Contrats"
ca1a7b76 1351
6e4600ef 1352 def __unicode__(self):
8c1ae2b3 1353 return u'%s - %s' % (self.dossier, self.id)
fc917340 1354
fa1f7426 1355
fc917340 1356class Contrat(Contrat_):
4ba84959
EMS
1357 dossier = models.ForeignKey(
1358 Dossier, db_column='dossier', related_name='rh_contrats'
1359 )
f31ddfa0 1360
d104b0ae
EMS
1361reversion.register(Contrat, format='xml')
1362
83b7692b 1363
ca1a7b76 1364### RÉFÉRENCES RH
83b7692b 1365
45066657 1366class CategorieEmploi(models.Model):
fa1f7426
EMS
1367 """
1368 Catégorie utilisée dans la gestion des Postes.
6e4600ef 1369 Catégorie supérieure à TypePoste.
1370 """
e9bbd6ba 1371 nom = models.CharField(max_length=255)
ca1a7b76 1372
8c1ae2b3 1373 class Meta:
321fe481 1374 ordering = ('nom',)
7bf28694
EMS
1375 verbose_name = u"catégorie d'emploi"
1376 verbose_name_plural = u"catégories d'emploi"
ca1a7b76 1377
6e4600ef 1378 def __unicode__(self):
321fe481
EMS
1379 return self.nom
1380
d104b0ae
EMS
1381reversion.register(CategorieEmploi, format='xml')
1382
321fe481
EMS
1383
1384class FamilleProfessionnelle(models.Model):
1385 """
1386 Famille professionnelle d'un poste.
1387 """
1388 nom = models.CharField(max_length=100)
1389
1390 class Meta:
1391 ordering = ('nom',)
1392 verbose_name = u'famille professionnelle'
1393 verbose_name_plural = u'familles professionnelles'
1394
1395 def __unicode__(self):
1396 return self.nom
e9bbd6ba 1397
d104b0ae
EMS
1398reversion.register(FamilleProfessionnelle, format='xml')
1399
fa1f7426 1400
15659516 1401class TypePoste(Archivable):
fa1f7426
EMS
1402 """
1403 Catégorie de Poste.
6e4600ef 1404 """
e9bbd6ba 1405 nom = models.CharField(max_length=255)
fa1f7426
EMS
1406 nom_feminin = models.CharField(u"nom féminin", max_length=255)
1407 is_responsable = models.BooleanField(
1408 u"poste de responsabilité", default=False
1409 )
7bf28694
EMS
1410 categorie_emploi = models.ForeignKey(
1411 CategorieEmploi, db_column='categorie_emploi', related_name='+',
1412 verbose_name=u"catégorie d'emploi"
fa1f7426 1413 )
321fe481
EMS
1414 famille_professionnelle = models.ForeignKey(
1415 FamilleProfessionnelle, related_name='types_de_poste',
1416 verbose_name=u"famille professionnelle", blank=True, null=True
1417 )
e9bbd6ba 1418
6e4600ef 1419 class Meta:
1420 ordering = ['nom']
c1195471
OL
1421 verbose_name = u"Type de poste"
1422 verbose_name_plural = u"Types de poste"
ca1a7b76 1423
e9bbd6ba 1424 def __unicode__(self):
6e4600ef 1425 return u'%s' % (self.nom)
e9bbd6ba 1426
d104b0ae
EMS
1427reversion.register(TypePoste, format='xml')
1428
1429
e9bbd6ba 1430TYPE_PAIEMENT_CHOICES = (
a3e3bde0
JPC
1431 (u'Régulier', u'Régulier'),
1432 (u'Ponctuel', u'Ponctuel'),
e9bbd6ba 1433)
1434
1435NATURE_REMUNERATION_CHOICES = (
46dab81b 1436 (u'Accessoire', u'Accessoire'),
6bec5651 1437 (u'Charges', u'Charges patronales'),
46dab81b 1438 (u'Indemnité', u'Indemnité autre'),
a3e3bde0
JPC
1439 (u'RAS', u'Rémunération autre source'),
1440 (u'Traitement', u'Traitement'),
e9bbd6ba 1441)
1442
fa1f7426 1443
7013d234 1444class TypeRemuneration(Archivable):
fa1f7426
EMS
1445 """
1446 Catégorie de Remuneration.
6e4600ef 1447 """
7ba822a6 1448
e9bbd6ba 1449 nom = models.CharField(max_length=255)
fa1f7426
EMS
1450 type_paiement = models.CharField(
1451 u"type de paiement", max_length=30, choices=TYPE_PAIEMENT_CHOICES
1452 )
6bec5651 1453
fa1f7426
EMS
1454 nature_remuneration = models.CharField(
1455 u"nature de la rémunération", max_length=30,
1456 choices=NATURE_REMUNERATION_CHOICES
1457 )
ca1a7b76 1458
8c1ae2b3 1459 class Meta:
1460 ordering = ['nom']
c1195471
OL
1461 verbose_name = u"Type de rémunération"
1462 verbose_name_plural = u"Types de rémunération"
9afaa55e 1463
1464 def __unicode__(self):
7013d234 1465 return self.nom
ca1a7b76 1466
d104b0ae
EMS
1467reversion.register(TypeRemuneration, format='xml')
1468
fa1f7426 1469
15659516 1470class TypeRevalorisation(Archivable):
fa1f7426
EMS
1471 """
1472 Justification du changement de la Remuneration.
6e4600ef 1473 (Actuellement utilisé dans aucun traitement informatique.)
7abc6d45 1474 """
e9bbd6ba 1475 nom = models.CharField(max_length=255)
ca1a7b76 1476
8c1ae2b3 1477 class Meta:
1478 ordering = ['nom']
c1195471
OL
1479 verbose_name = u"Type de revalorisation"
1480 verbose_name_plural = u"Types de revalorisation"
e9bbd6ba 1481
1482 def __unicode__(self):
6e4600ef 1483 return u'%s' % (self.nom)
ca1a7b76 1484
d104b0ae
EMS
1485reversion.register(TypeRevalorisation, format='xml')
1486
fa1f7426 1487
7013d234 1488class Service(Archivable):
fa1f7426
EMS
1489 """
1490 Unité administrative où les Postes sont rattachés.
6e4600ef 1491 """
1492 nom = models.CharField(max_length=255)
ca1a7b76 1493
9afaa55e 1494 class Meta:
1495 ordering = ['nom']
7013d234
EMS
1496 verbose_name = u"service"
1497 verbose_name_plural = u"services"
e9bbd6ba 1498
6e4600ef 1499 def __unicode__(self):
7013d234 1500 return self.nom
6e4600ef 1501
d104b0ae
EMS
1502reversion.register(Service, format='xml')
1503
e9bbd6ba 1504
1505TYPE_ORGANISME_CHOICES = (
1506 ('MAD', 'Mise à disposition'),
1507 ('DET', 'Détachement'),
1508)
1509
fa1f7426 1510
45066657 1511class OrganismeBstg(models.Model):
fa1f7426
EMS
1512 """
1513 Organisation d'où provient un Employe mis à disposition (MAD) de
6e4600ef 1514 ou détaché (DET) à l'AUF à titre gratuit.
ca1a7b76 1515
6e4600ef 1516 (BSTG = bien et service à titre gratuit.)
1517 """
e9bbd6ba 1518 nom = models.CharField(max_length=255)
1519 type = models.CharField(max_length=10, choices=TYPE_ORGANISME_CHOICES)
ca1a7b76 1520 pays = models.ForeignKey(ref.Pays, to_field='code',
6e4600ef 1521 db_column='pays',
1522 related_name='organismes_bstg',
1523 null=True, blank=True)
9afaa55e 1524
1525 class Meta:
1526 ordering = ['type', 'nom']
c1195471
OL
1527 verbose_name = u"Organisme BSTG"
1528 verbose_name_plural = u"Organismes BSTG"
9afaa55e 1529
6e4600ef 1530 def __unicode__(self):
8c1ae2b3 1531 return u'%s (%s)' % (self.nom, self.get_type_display())
83b7692b 1532
d104b0ae
EMS
1533reversion.register(OrganismeBstg, format='xml')
1534
aff1a4c6 1535
15659516 1536class Statut(Archivable):
fa1f7426
EMS
1537 """
1538 Statut de l'Employe dans le cadre d'un Dossier particulier.
6e4600ef 1539 """
9afaa55e 1540 # Identification
fa1f7426
EMS
1541 code = models.CharField(
1542 max_length=25, unique=True,
1543 help_text=(
1544 u"Saisir un code court mais lisible pour ce statut : "
1545 u"le code est utilisé pour associer les statuts aux autres "
1546 u"données tout en demeurant plus lisible qu'un identifiant "
1547 u"numérique."
1548 )
1549 )
e9bbd6ba 1550 nom = models.CharField(max_length=255)
e9bbd6ba 1551
6e4600ef 1552 class Meta:
1553 ordering = ['code']
c1195471
OL
1554 verbose_name = u"Statut d'employé"
1555 verbose_name_plural = u"Statuts d'employé"
ca1a7b76 1556
9afaa55e 1557 def __unicode__(self):
1558 return u'%s : %s' % (self.code, self.nom)
1559
d104b0ae
EMS
1560reversion.register(Statut, format='xml')
1561
83b7692b 1562
e9bbd6ba 1563TYPE_CLASSEMENT_CHOICES = (
6e4600ef 1564 ('S', 'S -Soutien'),
1565 ('T', 'T - Technicien'),
1566 ('P', 'P - Professionel'),
1567 ('C', 'C - Cadre'),
1568 ('D', 'D - Direction'),
1569 ('SO', 'SO - Sans objet [expatriés]'),
1570 ('HG', 'HG - Hors grille [direction]'),
e9bbd6ba 1571)
83b7692b 1572
fa1f7426 1573
15659516 1574class ClassementManager(ArchivableManager):
952ecb37
OL
1575 """
1576 Ordonner les spcéfiquement les classements.
1577 """
1578 def get_query_set(self):
1579 qs = super(self.__class__, self).get_query_set()
fa1f7426
EMS
1580 qs = qs.extra(select={
1581 'ponderation': 'FIND_IN_SET(type,"SO,HG,S,T,P,C,D")'
1582 })
6559f73b 1583 qs = qs.extra(order_by=('ponderation', 'echelon', 'degre', ))
952ecb37
OL
1584 return qs.all()
1585
6e7c919b 1586
15659516 1587class Classement_(Archivable):
fa1f7426
EMS
1588 """
1589 Éléments de classement de la
6e4600ef 1590 "Grille générique de classement hiérarchique".
ca1a7b76
EMS
1591
1592 Utile pour connaître, pour un Dossier, le salaire de base théorique lié au
6e4600ef 1593 classement dans la grille. Le classement donne le coefficient utilisé dans:
1594
1595 salaire de base = coefficient * valeur du point de l'Implantation du Poste
1596 """
952ecb37
OL
1597 objects = ClassementManager()
1598
9afaa55e 1599 # Identification
e9bbd6ba 1600 type = models.CharField(max_length=10, choices=TYPE_CLASSEMENT_CHOICES)
fa1f7426
EMS
1601 echelon = models.IntegerField(u"échelon", blank=True, default=0)
1602 degre = models.IntegerField(u"degré", blank=True, default=0)
d104b0ae 1603 coefficient = models.FloatField(u"coefficient", blank=True, null=True)
fa1f7426 1604
9afaa55e 1605 # Méta
6e4600ef 1606 # annee # au lieu de date_debut et date_fin
1607 commentaire = models.TextField(null=True, blank=True)
ca1a7b76 1608
6e4600ef 1609 class Meta:
6e7c919b 1610 abstract = True
fa1f7426 1611 ordering = ['type', 'echelon', 'degre', 'coefficient']
c1195471
OL
1612 verbose_name = u"Classement"
1613 verbose_name_plural = u"Classements"
e9bbd6ba 1614
1615 def __unicode__(self):
22343fe7 1616 return u'%s.%s.%s' % (self.type, self.echelon, self.degre, )
e9bbd6ba 1617
fa1f7426 1618
6e7c919b
NC
1619class Classement(Classement_):
1620 __doc__ = Classement_.__doc__
1621
d104b0ae
EMS
1622reversion.register(Classement, format='xml')
1623
6e7c919b 1624
45066657 1625class TauxChange_(models.Model):
fa1f7426
EMS
1626 """
1627 Taux de change de la devise vers l'euro (EUR)
6e4600ef 1628 pour chaque année budgétaire.
7abc6d45 1629 """
9afaa55e 1630 # Identification
8d3e2fff 1631 devise = models.ForeignKey('Devise', db_column='devise')
fa1f7426
EMS
1632 annee = models.IntegerField(u"année")
1633 taux = models.FloatField(u"taux vers l'euro")
6e7c919b 1634
6e4600ef 1635 class Meta:
6e7c919b 1636 abstract = True
8c1ae2b3 1637 ordering = ['-annee', 'devise__code']
c1195471
OL
1638 verbose_name = u"Taux de change"
1639 verbose_name_plural = u"Taux de change"
8bb6f549 1640 unique_together = ('devise', 'annee')
ca1a7b76 1641
6e4600ef 1642 def __unicode__(self):
8c1ae2b3 1643 return u'%s : %s € (%s)' % (self.devise, self.taux, self.annee)
e9bbd6ba 1644
6e7c919b
NC
1645
1646class TauxChange(TauxChange_):
1647 __doc__ = TauxChange_.__doc__
1648
d104b0ae
EMS
1649reversion.register(TauxChange, format='xml')
1650
fa1f7426 1651
45066657 1652class ValeurPointManager(models.Manager):
105dd778 1653
701f3bea 1654 def get_query_set(self):
fa1f7426 1655 return super(ValeurPointManager, self).get_query_set() \
15659516 1656 .select_related('devise', 'implantation')
701f3bea 1657
6e7c919b 1658
45066657 1659class ValeurPoint_(models.Model):
fa1f7426
EMS
1660 """
1661 Utile pour connaître, pour un Dossier, le salaire de base théorique lié
ca1a7b76 1662 au classement dans la grille. La ValeurPoint s'obtient par l'implantation
8c1ae2b3 1663 du Poste de ce Dossier : dossier.poste.implantation (pseudo code).
6e4600ef 1664
1665 salaire de base = coefficient * valeur du point de l'Implantation du Poste
1666 """
ca1a7b76 1667
45066657 1668 objects = models.Manager()
09aa8374 1669 actuelles = ValeurPointManager()
701f3bea 1670
8277a35b 1671 valeur = models.FloatField(null=True)
f614ca5c 1672 devise = models.ForeignKey('Devise', db_column='devise', related_name='+',)
ca1a7b76 1673 implantation = models.ForeignKey(ref.Implantation,
6e7c919b
NC
1674 db_column='implantation',
1675 related_name='%(app_label)s_valeur_point')
9afaa55e 1676 # Méta
e9bbd6ba 1677 annee = models.IntegerField()
9afaa55e 1678
6e4600ef 1679 class Meta:
701f3bea 1680 ordering = ['-annee', 'implantation__nom']
6e7c919b 1681 abstract = True
c1195471
OL
1682 verbose_name = u"Valeur du point"
1683 verbose_name_plural = u"Valeurs du point"
8bb6f549 1684 unique_together = ('implantation', 'annee')
6e0bbb73 1685
9afaa55e 1686 def __unicode__(self):
fa1f7426
EMS
1687 return u'%s %s %s [%s] %s' % (
1688 self.devise.code, self.annee, self.valeur,
1689 self.implantation.nom_court, self.devise.nom
1690 )
6e7c919b
NC
1691
1692
1693class ValeurPoint(ValeurPoint_):
1694 __doc__ = ValeurPoint_.__doc__
1695
d104b0ae
EMS
1696reversion.register(ValeurPoint, format='xml')
1697
e9bbd6ba 1698
7013d234 1699class Devise(Archivable):
6e4600ef 1700 """
fa1f7426
EMS
1701 Devise monétaire.
1702 """
fa1f7426 1703 code = models.CharField(max_length=10, unique=True)
e9bbd6ba 1704 nom = models.CharField(max_length=255)
1705
6e4600ef 1706 class Meta:
1707 ordering = ['code']
7013d234
EMS
1708 verbose_name = u"devise"
1709 verbose_name_plural = u"devises"
ca1a7b76 1710
e9bbd6ba 1711 def __unicode__(self):
1712 return u'%s - %s' % (self.code, self.nom)
1713
d104b0ae
EMS
1714reversion.register(Devise, format='xml')
1715
fa1f7426 1716
15659516 1717class TypeContrat(Archivable):
fa1f7426
EMS
1718 """
1719 Type de contrat.
6e4600ef 1720 """
e9bbd6ba 1721 nom = models.CharField(max_length=255)
6e4600ef 1722 nom_long = models.CharField(max_length=255)
49f9f116 1723
8c1ae2b3 1724 class Meta:
1725 ordering = ['nom']
c1195471
OL
1726 verbose_name = u"Type de contrat"
1727 verbose_name_plural = u"Types de contrat"
8c1ae2b3 1728
9afaa55e 1729 def __unicode__(self):
1730 return u'%s' % (self.nom)
ca1a7b76 1731
d104b0ae
EMS
1732reversion.register(TypeContrat, format='xml')
1733
ca1a7b76 1734
2d4d2fcf 1735### AUTRES
1736
8c8ffc4f 1737class ResponsableImplantationProxy(ref.Implantation):
7bf28694 1738
6fb68b2f
DB
1739 def save(self):
1740 pass
1741
8c8ffc4f 1742 class Meta:
d7bf0cd3 1743 managed = False
8c8ffc4f
OL
1744 proxy = True
1745 verbose_name = u"Responsable d'implantation"
1746 verbose_name_plural = u"Responsables d'implantation"
1747
1748
1749class ResponsableImplantation(models.Model):
fa1f7426
EMS
1750 """
1751 Le responsable d'une implantation.
30be56d5 1752 Anciennement géré sur le Dossier du responsable.
1753 """
fa1f7426
EMS
1754 employe = models.ForeignKey(
1755 'Employe', db_column='employe', related_name='+', null=True,
1756 blank=True
1757 )
1758 implantation = models.OneToOneField(
1759 "ResponsableImplantationProxy", db_column='implantation',
1760 related_name='responsable', unique=True
1761 )
30be56d5 1762
1763 def __unicode__(self):
1764 return u'%s : %s' % (self.implantation, self.employe)
ca1a7b76 1765
30be56d5 1766 class Meta:
1767 ordering = ['implantation__nom']
8c1ae2b3 1768 verbose_name = "Responsable d'implantation"
1769 verbose_name_plural = "Responsables d'implantation"
d104b0ae
EMS
1770
1771reversion.register(ResponsableImplantation, format='xml')
edbc9e37
OL
1772
1773
e8b6a20c
BS
1774class UserProfile(models.Model):
1775 user = models.OneToOneField(User, related_name='profile')
1776 zones_administratives = models.ManyToManyField(
1777 ref.ZoneAdministrative,
1778 related_name='profiles'
1779 )
1780 class Meta:
1781 verbose_name = "Permissions sur zones administratives"
1782 verbose_name_plural = "Permissions sur zones administratives"
1783
1784 def __unicode__(self):
1785 return self.user.__unicode__()
1786
1787reversion.register(UserProfile, format='xml')
343cfd9c
BS
1788
1789
1790
1791TYPES_CHANGEMENT = (
1792 ('NO', 'Nouveau personnel'),
1793 ('MO', 'Mouvement de personnel'),
1794 ('DE', 'Départ de personnel'),
1795 )
1796
1797
1798class ChangementPersonnelNotifications(models.Model):
1799 class Meta:
1800 verbose_name = u"Destinataire pour notices de changement de personnel"
1801 verbose_name_plural = u"Destinataires pour notices de changement de personnel"
1802
1803 type = models.CharField(
1804 max_length=2,
1805 choices = TYPES_CHANGEMENT,
1806 unique=True,
1807 )
1808
1809 destinataires = models.ManyToManyField(
1810 ref.Employe,
1811 related_name='changement_notifications',
1812 )
1813
1814 def __unicode__(self):
1815 return '%s: %s' % (
1816 self.get_type_display(), ','.join(
1817 self.destinataires.all().values_list(
1818 'courriel', flat=True))
1819 )
1820
1821
1822class ChangementPersonnel(models.Model):
1823 """
1824 Une notice qui enregistre un changement de personnel, incluant:
1825
1826 * Nouveaux employés
1827 * Mouvement de personnel
1828 * Départ d'employé
1829 """
1830
1831 class Meta:
1832 verbose_name = u"Notification de changement du personnel"
1833 verbose_name_plural = u"Notifications de changement du personnel"
1834
1835 def __unicode__(self):
1836 return '%s: %s' % (self.dossier.__unicode__(),
1837 self.get_type_display())
1838
1839 @classmethod
1840 def create_changement(cls, dossier, type):
1841 # If this employe has existing Changement, set them to invalid.
1842 cls.objects.filter(dossier__employe=dossier.employe).update(valide=False)
1843
1844 # Create a new one.
1845 cls.objects.create(
1846 dossier=dossier,
1847 type=type,
1848 valide=True,
1849 communique=False,
1850 )
1851
1852
1853 @classmethod
343cfd9c
BS
1854 def post_save_handler(cls,
1855 sender,
1856 instance,
1857 created,
1858 using,
1859 **kw):
1860
1861 # This defines the time limit used when checking in previous
1862 # files to see if an employee if new. Basically, if emloyee
1863 # left his position new_file.date_debut -
1864 # NEW_EMPLOYE_THRESHOLD +1 ago (compared to date_debut), then
1865 # if a new file is created for this employee, he will bec
1866 # onsidered "NEW" and a notice will be created to this effect.
1867 NEW_EMPLOYE_THRESHOLD = datetime.timedelta(7) # 7 days.
1868
1869 other_dossier_qs = instance.employe.rh_dossiers.exclude(
1870 id=instance.id)
1871 dd = instance.date_debut
1872 df = instance.date_fin
1873
1874 # Here, verify differences between the instance, before and
1875 # after the save.
e535b526
BS
1876 df_has_changed = False
1877
343cfd9c 1878 if created:
e535b526 1879 if df != None:
343cfd9c
BS
1880 df_has_changed = True
1881 else:
1882 df_has_changed = (df != instance.before_save.date_fin and
1883 df != None)
1884
1885
1886 # VERIFICATIONS:
1887
1888 # Date de fin est None et c'est une nouvelle instance de
1889 # Dossier
1890 if not df and created:
1891 # QS for finding other dossiers with a date_fin of None OR
1892 # with a date_fin >= to this dossier's date_debut
1893 exists_recent_file_qs = other_dossier_qs.filter(
1894 Q(date_fin__isnull=True) |
1895 Q(date_fin__gte=dd - NEW_EMPLOYE_THRESHOLD)
1896 )
1897
1898 # 1. If existe un Dossier récent, et c'est une nouvelle
1899 # instance de Dossier:
1900 if exists_recent_file_qs.count() > 0:
1901 cls.create_changement(
1902 instance,
1903 'MO',
1904 )
1905 # 2. Il n'existe un Dossier récent, et c'est une nouvelle
1906 # instance de Dossier:
1907 else:
1908 cls.create_changement(
1909 instance,
1910 'NO',
1911 )
1912
1913
1914 # Date de fin a été modifiée:
1915 if df_has_changed:
1916 # QS for other active files (date_fin == None), excludes
1917 # instance.
1918 exists_active_files_qs = other_dossier_qs.filter(
1919 Q(date_fin__isnull=True))
1920
1921 # 3. Date de fin a été modifiée et il n'existe aucun autre
1922 # dossier actifs: Depart
1923 if exists_active_files_qs.count() == 0:
1924 cls.create_changement(
1925 instance,
1926 'DE',
1927 )
1928 # 4. Dossier a une nouvelle date de fin par contre
1929 # d'autres dossiers actifs existent déjà: Mouvement
1930 else:
1931 cls.create_changement(
1932 instance,
1933 'MO',
1934 )
1935
1936
1937 dossier = models.ForeignKey(
1938 Dossier,
1939 related_name='mouvements',
1940 )
1941
1942 valide = models.BooleanField(default=True)
1943 communique = models.BooleanField(default=False)
1944 date_communication = models.DateTimeField(
1945 null=True,
1946 blank=True,
1947 )
1948
1949 type = models.CharField(
1950 max_length=2,
1951 choices = TYPES_CHANGEMENT,
1952 )
1953
1954reversion.register(ChangementPersonnel, format='xml')
1955
1956
9388fbac
BS
1957def dossier_pre_save_handler(sender,
1958 instance,
1959 using,
1960 **kw):
1961 # Store a copy of the model before save is called.
1962 if instance.pk is not None:
1963 instance.before_save = Dossier.objects.get(pk=instance.pk)
1964 else:
1965 instance.before_save = None
1966
1967
1968# Connect a pre_save handler that assigns a copy of the model as an
1969# attribute in order to compare it in post_save.
1970pre_save.connect(dossier_pre_save_handler, sender=Dossier)
1971
343cfd9c 1972post_save.connect(ChangementPersonnel.post_save_handler, sender=Dossier)
9388fbac
BS
1973post_save.connect(RHDossierClassementRecord.post_save_handler, sender=Dossier)
1974
1975