Fixed natures choices
[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(
815 u"Est un câdre?",
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
1117 # Attempt to pull previous classement from history.
1118 previous_classement = None
1119 has_changed = False
1120
1121 if not created:
1122 has_changed = (
1123 instance.classement.id !=
1124 instance.before_save.classement.id
1125 )
1126 try:
1127 previous_record = cls.objects.get(
1128 dossier=instance,
1129 classement=instance.before_save.classement,
1130 date_fin=None,
1131 )
1132 except cls.DoesNotExist:
1133 previous_record = None
1134 else:
1135 previous_classement = previous_record.classement
1136
1137
1138 if created or (not created and not previous_classement):
1139 cls.objects.create(
1140 date_debut=instance.date_debut,
1141 classement=instance.classement,
1142 dossier=instance,
1143 )
1144 elif has_changed and previous_classement:
1145 change_date = datetime.date.today()
1146 previous_record.date_fin = change_date
1147 previous_record.save()
1148 cls.objects.create(
1149 date_debut=change_date,
1150 classement=instance.classement,
1151 dossier=instance,
1152 )
1153
1154 # elif not created:
1155 # cmp_inst = self.before_save
1156
1157 # # Classement has changed!
1158 # if (cmp_inst.classement.id !=
1159 # instance.classement.id):
1160
1161
1162
1163
1164
1165
1166
1167
1168
fc917340 1169class DossierPiece_(models.Model):
fa1f7426
EMS
1170 """
1171 Documents relatifs au Dossier (à l'occupation de ce poste par employé).
7abc6d45 1172 Ex.: Lettre de motivation.
1173 """
fa1f7426
EMS
1174 nom = models.CharField(max_length=255)
1175 fichier = models.FileField(
1176 upload_to=dossier_piece_dispatch, storage=storage_prive
1177 )
83b7692b 1178
6e4600ef 1179 class Meta:
fc917340 1180 abstract = True
6e4600ef 1181 ordering = ['nom']
ca1a7b76 1182
6e4600ef 1183 def __unicode__(self):
1184 return u'%s' % (self.nom)
1185
fa1f7426 1186
fc917340 1187class DossierPiece(DossierPiece_):
fa1f7426 1188 dossier = models.ForeignKey(
4ba84959 1189 Dossier, db_column='dossier', related_name='rh_dossierpieces'
fa1f7426
EMS
1190 )
1191
d104b0ae 1192reversion.register(DossierPiece, format='xml')
fc917340 1193
4ba84959
EMS
1194class DossierCommentaire(Commentaire):
1195 dossier = models.ForeignKey(
1196 Dossier, db_column='dossier', related_name='commentaires'
1197 )
fc917340 1198
d104b0ae
EMS
1199reversion.register(DossierCommentaire, format='xml')
1200
fa1f7426 1201
e84c8ef1 1202class DossierComparaison_(models.Model, DevisableMixin):
1d0f4eef
OL
1203 """
1204 Photo d'une comparaison salariale au moment de l'embauche.
1205 """
16b1454e
OL
1206 objects = DossierComparaisonManager()
1207
fa1f7426
EMS
1208 implantation = models.ForeignKey(
1209 ref.Implantation, related_name="+", null=True, blank=True
1210 )
1d0f4eef
OL
1211 poste = models.CharField(max_length=255, null=True, blank=True)
1212 personne = models.CharField(max_length=255, null=True, blank=True)
1213 montant = models.IntegerField(null=True)
fa1f7426
EMS
1214 devise = models.ForeignKey(
1215 'Devise', related_name='+', null=True, blank=True
1216 )
1d0f4eef 1217
fc917340
OL
1218 class Meta:
1219 abstract = True
1220
3b14230d
OL
1221 def __unicode__(self):
1222 return "%s (%s)" % (self.poste, self.personne)
1223
1d0f4eef 1224
fc917340 1225class DossierComparaison(DossierComparaison_):
4ba84959
EMS
1226 dossier = models.ForeignKey(
1227 Dossier, related_name='rh_comparaisons'
1228 )
2d4d2fcf 1229
d104b0ae
EMS
1230reversion.register(DossierComparaison, format='xml')
1231
fa1f7426 1232
07b40eda 1233### RÉMUNÉRATION
ca1a7b76 1234
45066657 1235class RemunerationMixin(models.Model):
fa1f7426 1236
9afaa55e 1237 # Identification
fa1f7426
EMS
1238 type = models.ForeignKey(
1239 'TypeRemuneration', db_column='type', related_name='+',
1240 verbose_name=u"type de rémunération"
1241 )
1242 type_revalorisation = models.ForeignKey(
1243 'TypeRevalorisation', db_column='type_revalorisation',
1244 related_name='+', verbose_name=u"type de revalorisation",
1245 null=True, blank=True
1246 )
1247 montant = models.DecimalField(
d104b0ae 1248 null=True, blank=True, max_digits=12, decimal_places=2
fa1f7426
EMS
1249 ) # Annuel (12 mois, 52 semaines, 364 jours?)
1250 devise = models.ForeignKey('Devise', db_column='devise', related_name='+')
1251
2d4d2fcf 1252 # commentaire = precision
1253 commentaire = models.CharField(max_length=255, null=True, blank=True)
fa1f7426 1254
2d4d2fcf 1255 # date_debut = anciennement date_effectif
8bb6f549
EMS
1256 date_debut = models.DateField(
1257 u"date de début", null=True, blank=True, db_index=True
1258 )
1259 date_fin = models.DateField(
1260 u"date de fin", null=True, blank=True, db_index=True
1261 )
ca1a7b76
EMS
1262
1263 class Meta:
2d4d2fcf 1264 abstract = True
6e4600ef 1265 ordering = ['type__nom', '-date_fin']
ca1a7b76 1266
6e4600ef 1267 def __unicode__(self):
1268 return u'%s %s (%s)' % (self.montant, self.devise.code, self.type.nom)
ca1a7b76 1269
fa1f7426 1270
e84c8ef1 1271class Remuneration_(RemunerationMixin, DevisableMixin):
fa1f7426
EMS
1272 """
1273 Structure de rémunération (données budgétaires) en situation normale
ca1a7b76
EMS
1274 pour un Dossier. Si un Evenement existe, utiliser la structure de
1275 rémunération EvenementRemuneration de cet événement.
2d4d2fcf 1276 """
c3550a05 1277 objects = RemunerationManager()
83b7692b 1278
1279 def montant_mois(self):
1280 return round(self.montant / 12, 2)
1281
626beb4d 1282 def montant_avec_regime(self):
fa1f7426 1283 return round(self.montant * (self.dossier.regime_travail / 100), 2)
626beb4d 1284
83b7692b 1285 def montant_euro_mois(self):
e84c8ef1 1286 return round(self.montant_euros() / 12, 2)
ca1a7b76 1287
9afaa55e 1288 def __unicode__(self):
1289 try:
1290 devise = self.devise.code
1291 except:
1292 devise = "???"
1293 return "%s %s" % (self.montant, devise)
83b7692b 1294
6e7c919b
NC
1295 class Meta:
1296 abstract = True
c1195471
OL
1297 verbose_name = u"Rémunération"
1298 verbose_name_plural = u"Rémunérations"
6e7c919b
NC
1299
1300
1301class Remuneration(Remuneration_):
4ba84959
EMS
1302 dossier = models.ForeignKey(
1303 Dossier, db_column='dossier', related_name='rh_remunerations'
1304 )
6e7c919b 1305
d104b0ae
EMS
1306reversion.register(Remuneration, format='xml')
1307
2d4d2fcf 1308
1309### CONTRATS
c41b7fcc 1310
45066657 1311class Contrat_(models.Model):
fa1f7426
EMS
1312 """
1313 Document juridique qui encadre la relation de travail d'un Employe
ca1a7b76 1314 pour un Poste particulier. Pour un Dossier (qui documente cette
2d4d2fcf 1315 relation de travail) plusieurs contrats peuvent être associés.
1316 """
c41b7fcc 1317 objects = ContratManager()
fa1f7426
EMS
1318 type_contrat = models.ForeignKey(
1319 'TypeContrat', db_column='type_contrat',
1320 verbose_name=u'type de contrat', related_name='+'
1321 )
8bb6f549
EMS
1322 date_debut = models.DateField(
1323 u"date de début", db_index=True
1324 )
1325 date_fin = models.DateField(
1326 u"date de fin", null=True, blank=True, db_index=True
1327 )
fa1f7426
EMS
1328 fichier = models.FileField(
1329 upload_to=contrat_dispatch, storage=storage_prive, null=True,
1330 blank=True
1331 )
6e4600ef 1332
1333 class Meta:
fc917340 1334 abstract = True
a2c3ad52 1335 ordering = ['dossier__employe__nom']
c1195471
OL
1336 verbose_name = u"Contrat"
1337 verbose_name_plural = u"Contrats"
ca1a7b76 1338
6e4600ef 1339 def __unicode__(self):
8c1ae2b3 1340 return u'%s - %s' % (self.dossier, self.id)
fc917340 1341
fa1f7426 1342
fc917340 1343class Contrat(Contrat_):
4ba84959
EMS
1344 dossier = models.ForeignKey(
1345 Dossier, db_column='dossier', related_name='rh_contrats'
1346 )
f31ddfa0 1347
d104b0ae
EMS
1348reversion.register(Contrat, format='xml')
1349
83b7692b 1350
ca1a7b76 1351### RÉFÉRENCES RH
83b7692b 1352
45066657 1353class CategorieEmploi(models.Model):
fa1f7426
EMS
1354 """
1355 Catégorie utilisée dans la gestion des Postes.
6e4600ef 1356 Catégorie supérieure à TypePoste.
1357 """
e9bbd6ba 1358 nom = models.CharField(max_length=255)
ca1a7b76 1359
8c1ae2b3 1360 class Meta:
321fe481 1361 ordering = ('nom',)
7bf28694
EMS
1362 verbose_name = u"catégorie d'emploi"
1363 verbose_name_plural = u"catégories d'emploi"
ca1a7b76 1364
6e4600ef 1365 def __unicode__(self):
321fe481
EMS
1366 return self.nom
1367
d104b0ae
EMS
1368reversion.register(CategorieEmploi, format='xml')
1369
321fe481
EMS
1370
1371class FamilleProfessionnelle(models.Model):
1372 """
1373 Famille professionnelle d'un poste.
1374 """
1375 nom = models.CharField(max_length=100)
1376
1377 class Meta:
1378 ordering = ('nom',)
1379 verbose_name = u'famille professionnelle'
1380 verbose_name_plural = u'familles professionnelles'
1381
1382 def __unicode__(self):
1383 return self.nom
e9bbd6ba 1384
d104b0ae
EMS
1385reversion.register(FamilleProfessionnelle, format='xml')
1386
fa1f7426 1387
15659516 1388class TypePoste(Archivable):
fa1f7426
EMS
1389 """
1390 Catégorie de Poste.
6e4600ef 1391 """
e9bbd6ba 1392 nom = models.CharField(max_length=255)
fa1f7426
EMS
1393 nom_feminin = models.CharField(u"nom féminin", max_length=255)
1394 is_responsable = models.BooleanField(
1395 u"poste de responsabilité", default=False
1396 )
7bf28694
EMS
1397 categorie_emploi = models.ForeignKey(
1398 CategorieEmploi, db_column='categorie_emploi', related_name='+',
1399 verbose_name=u"catégorie d'emploi"
fa1f7426 1400 )
321fe481
EMS
1401 famille_professionnelle = models.ForeignKey(
1402 FamilleProfessionnelle, related_name='types_de_poste',
1403 verbose_name=u"famille professionnelle", blank=True, null=True
1404 )
e9bbd6ba 1405
6e4600ef 1406 class Meta:
1407 ordering = ['nom']
c1195471
OL
1408 verbose_name = u"Type de poste"
1409 verbose_name_plural = u"Types de poste"
ca1a7b76 1410
e9bbd6ba 1411 def __unicode__(self):
6e4600ef 1412 return u'%s' % (self.nom)
e9bbd6ba 1413
d104b0ae
EMS
1414reversion.register(TypePoste, format='xml')
1415
1416
e9bbd6ba 1417TYPE_PAIEMENT_CHOICES = (
a3e3bde0
JPC
1418 (u'Régulier', u'Régulier'),
1419 (u'Ponctuel', u'Ponctuel'),
e9bbd6ba 1420)
1421
1422NATURE_REMUNERATION_CHOICES = (
46dab81b 1423 (u'Accessoire', u'Accessoire'),
6bec5651 1424 (u'Charges', u'Charges patronales'),
46dab81b 1425 (u'Indemnité', u'Indemnité autre'),
a3e3bde0
JPC
1426 (u'RAS', u'Rémunération autre source'),
1427 (u'Traitement', u'Traitement'),
e9bbd6ba 1428)
1429
fa1f7426 1430
7013d234 1431class TypeRemuneration(Archivable):
fa1f7426
EMS
1432 """
1433 Catégorie de Remuneration.
6e4600ef 1434 """
7ba822a6 1435
e9bbd6ba 1436 nom = models.CharField(max_length=255)
fa1f7426
EMS
1437 type_paiement = models.CharField(
1438 u"type de paiement", max_length=30, choices=TYPE_PAIEMENT_CHOICES
1439 )
6bec5651 1440
fa1f7426
EMS
1441 nature_remuneration = models.CharField(
1442 u"nature de la rémunération", max_length=30,
1443 choices=NATURE_REMUNERATION_CHOICES
1444 )
ca1a7b76 1445
8c1ae2b3 1446 class Meta:
1447 ordering = ['nom']
c1195471
OL
1448 verbose_name = u"Type de rémunération"
1449 verbose_name_plural = u"Types de rémunération"
9afaa55e 1450
1451 def __unicode__(self):
7013d234 1452 return self.nom
ca1a7b76 1453
d104b0ae
EMS
1454reversion.register(TypeRemuneration, format='xml')
1455
fa1f7426 1456
15659516 1457class TypeRevalorisation(Archivable):
fa1f7426
EMS
1458 """
1459 Justification du changement de la Remuneration.
6e4600ef 1460 (Actuellement utilisé dans aucun traitement informatique.)
7abc6d45 1461 """
e9bbd6ba 1462 nom = models.CharField(max_length=255)
ca1a7b76 1463
8c1ae2b3 1464 class Meta:
1465 ordering = ['nom']
c1195471
OL
1466 verbose_name = u"Type de revalorisation"
1467 verbose_name_plural = u"Types de revalorisation"
e9bbd6ba 1468
1469 def __unicode__(self):
6e4600ef 1470 return u'%s' % (self.nom)
ca1a7b76 1471
d104b0ae
EMS
1472reversion.register(TypeRevalorisation, format='xml')
1473
fa1f7426 1474
7013d234 1475class Service(Archivable):
fa1f7426
EMS
1476 """
1477 Unité administrative où les Postes sont rattachés.
6e4600ef 1478 """
1479 nom = models.CharField(max_length=255)
ca1a7b76 1480
9afaa55e 1481 class Meta:
1482 ordering = ['nom']
7013d234
EMS
1483 verbose_name = u"service"
1484 verbose_name_plural = u"services"
e9bbd6ba 1485
6e4600ef 1486 def __unicode__(self):
7013d234 1487 return self.nom
6e4600ef 1488
d104b0ae
EMS
1489reversion.register(Service, format='xml')
1490
e9bbd6ba 1491
1492TYPE_ORGANISME_CHOICES = (
1493 ('MAD', 'Mise à disposition'),
1494 ('DET', 'Détachement'),
1495)
1496
fa1f7426 1497
45066657 1498class OrganismeBstg(models.Model):
fa1f7426
EMS
1499 """
1500 Organisation d'où provient un Employe mis à disposition (MAD) de
6e4600ef 1501 ou détaché (DET) à l'AUF à titre gratuit.
ca1a7b76 1502
6e4600ef 1503 (BSTG = bien et service à titre gratuit.)
1504 """
e9bbd6ba 1505 nom = models.CharField(max_length=255)
1506 type = models.CharField(max_length=10, choices=TYPE_ORGANISME_CHOICES)
ca1a7b76 1507 pays = models.ForeignKey(ref.Pays, to_field='code',
6e4600ef 1508 db_column='pays',
1509 related_name='organismes_bstg',
1510 null=True, blank=True)
9afaa55e 1511
1512 class Meta:
1513 ordering = ['type', 'nom']
c1195471
OL
1514 verbose_name = u"Organisme BSTG"
1515 verbose_name_plural = u"Organismes BSTG"
9afaa55e 1516
6e4600ef 1517 def __unicode__(self):
8c1ae2b3 1518 return u'%s (%s)' % (self.nom, self.get_type_display())
83b7692b 1519
d104b0ae
EMS
1520reversion.register(OrganismeBstg, format='xml')
1521
aff1a4c6 1522
15659516 1523class Statut(Archivable):
fa1f7426
EMS
1524 """
1525 Statut de l'Employe dans le cadre d'un Dossier particulier.
6e4600ef 1526 """
9afaa55e 1527 # Identification
fa1f7426
EMS
1528 code = models.CharField(
1529 max_length=25, unique=True,
1530 help_text=(
1531 u"Saisir un code court mais lisible pour ce statut : "
1532 u"le code est utilisé pour associer les statuts aux autres "
1533 u"données tout en demeurant plus lisible qu'un identifiant "
1534 u"numérique."
1535 )
1536 )
e9bbd6ba 1537 nom = models.CharField(max_length=255)
e9bbd6ba 1538
6e4600ef 1539 class Meta:
1540 ordering = ['code']
c1195471
OL
1541 verbose_name = u"Statut d'employé"
1542 verbose_name_plural = u"Statuts d'employé"
ca1a7b76 1543
9afaa55e 1544 def __unicode__(self):
1545 return u'%s : %s' % (self.code, self.nom)
1546
d104b0ae
EMS
1547reversion.register(Statut, format='xml')
1548
83b7692b 1549
e9bbd6ba 1550TYPE_CLASSEMENT_CHOICES = (
6e4600ef 1551 ('S', 'S -Soutien'),
1552 ('T', 'T - Technicien'),
1553 ('P', 'P - Professionel'),
1554 ('C', 'C - Cadre'),
1555 ('D', 'D - Direction'),
1556 ('SO', 'SO - Sans objet [expatriés]'),
1557 ('HG', 'HG - Hors grille [direction]'),
e9bbd6ba 1558)
83b7692b 1559
fa1f7426 1560
15659516 1561class ClassementManager(ArchivableManager):
952ecb37
OL
1562 """
1563 Ordonner les spcéfiquement les classements.
1564 """
1565 def get_query_set(self):
1566 qs = super(self.__class__, self).get_query_set()
fa1f7426
EMS
1567 qs = qs.extra(select={
1568 'ponderation': 'FIND_IN_SET(type,"SO,HG,S,T,P,C,D")'
1569 })
6559f73b 1570 qs = qs.extra(order_by=('ponderation', 'echelon', 'degre', ))
952ecb37
OL
1571 return qs.all()
1572
6e7c919b 1573
15659516 1574class Classement_(Archivable):
fa1f7426
EMS
1575 """
1576 Éléments de classement de la
6e4600ef 1577 "Grille générique de classement hiérarchique".
ca1a7b76
EMS
1578
1579 Utile pour connaître, pour un Dossier, le salaire de base théorique lié au
6e4600ef 1580 classement dans la grille. Le classement donne le coefficient utilisé dans:
1581
1582 salaire de base = coefficient * valeur du point de l'Implantation du Poste
1583 """
952ecb37
OL
1584 objects = ClassementManager()
1585
9afaa55e 1586 # Identification
e9bbd6ba 1587 type = models.CharField(max_length=10, choices=TYPE_CLASSEMENT_CHOICES)
fa1f7426
EMS
1588 echelon = models.IntegerField(u"échelon", blank=True, default=0)
1589 degre = models.IntegerField(u"degré", blank=True, default=0)
d104b0ae 1590 coefficient = models.FloatField(u"coefficient", blank=True, null=True)
fa1f7426 1591
9afaa55e 1592 # Méta
6e4600ef 1593 # annee # au lieu de date_debut et date_fin
1594 commentaire = models.TextField(null=True, blank=True)
ca1a7b76 1595
6e4600ef 1596 class Meta:
6e7c919b 1597 abstract = True
fa1f7426 1598 ordering = ['type', 'echelon', 'degre', 'coefficient']
c1195471
OL
1599 verbose_name = u"Classement"
1600 verbose_name_plural = u"Classements"
e9bbd6ba 1601
1602 def __unicode__(self):
22343fe7 1603 return u'%s.%s.%s' % (self.type, self.echelon, self.degre, )
e9bbd6ba 1604
fa1f7426 1605
6e7c919b
NC
1606class Classement(Classement_):
1607 __doc__ = Classement_.__doc__
1608
d104b0ae
EMS
1609reversion.register(Classement, format='xml')
1610
6e7c919b 1611
45066657 1612class TauxChange_(models.Model):
fa1f7426
EMS
1613 """
1614 Taux de change de la devise vers l'euro (EUR)
6e4600ef 1615 pour chaque année budgétaire.
7abc6d45 1616 """
9afaa55e 1617 # Identification
8d3e2fff 1618 devise = models.ForeignKey('Devise', db_column='devise')
fa1f7426
EMS
1619 annee = models.IntegerField(u"année")
1620 taux = models.FloatField(u"taux vers l'euro")
6e7c919b 1621
6e4600ef 1622 class Meta:
6e7c919b 1623 abstract = True
8c1ae2b3 1624 ordering = ['-annee', 'devise__code']
c1195471
OL
1625 verbose_name = u"Taux de change"
1626 verbose_name_plural = u"Taux de change"
8bb6f549 1627 unique_together = ('devise', 'annee')
ca1a7b76 1628
6e4600ef 1629 def __unicode__(self):
8c1ae2b3 1630 return u'%s : %s € (%s)' % (self.devise, self.taux, self.annee)
e9bbd6ba 1631
6e7c919b
NC
1632
1633class TauxChange(TauxChange_):
1634 __doc__ = TauxChange_.__doc__
1635
d104b0ae
EMS
1636reversion.register(TauxChange, format='xml')
1637
fa1f7426 1638
45066657 1639class ValeurPointManager(models.Manager):
105dd778 1640
701f3bea 1641 def get_query_set(self):
fa1f7426 1642 return super(ValeurPointManager, self).get_query_set() \
15659516 1643 .select_related('devise', 'implantation')
701f3bea 1644
6e7c919b 1645
45066657 1646class ValeurPoint_(models.Model):
fa1f7426
EMS
1647 """
1648 Utile pour connaître, pour un Dossier, le salaire de base théorique lié
ca1a7b76 1649 au classement dans la grille. La ValeurPoint s'obtient par l'implantation
8c1ae2b3 1650 du Poste de ce Dossier : dossier.poste.implantation (pseudo code).
6e4600ef 1651
1652 salaire de base = coefficient * valeur du point de l'Implantation du Poste
1653 """
ca1a7b76 1654
45066657 1655 objects = models.Manager()
09aa8374 1656 actuelles = ValeurPointManager()
701f3bea 1657
8277a35b 1658 valeur = models.FloatField(null=True)
f614ca5c 1659 devise = models.ForeignKey('Devise', db_column='devise', related_name='+',)
ca1a7b76 1660 implantation = models.ForeignKey(ref.Implantation,
6e7c919b
NC
1661 db_column='implantation',
1662 related_name='%(app_label)s_valeur_point')
9afaa55e 1663 # Méta
e9bbd6ba 1664 annee = models.IntegerField()
9afaa55e 1665
6e4600ef 1666 class Meta:
701f3bea 1667 ordering = ['-annee', 'implantation__nom']
6e7c919b 1668 abstract = True
c1195471
OL
1669 verbose_name = u"Valeur du point"
1670 verbose_name_plural = u"Valeurs du point"
8bb6f549 1671 unique_together = ('implantation', 'annee')
6e0bbb73 1672
9afaa55e 1673 def __unicode__(self):
fa1f7426
EMS
1674 return u'%s %s %s [%s] %s' % (
1675 self.devise.code, self.annee, self.valeur,
1676 self.implantation.nom_court, self.devise.nom
1677 )
6e7c919b
NC
1678
1679
1680class ValeurPoint(ValeurPoint_):
1681 __doc__ = ValeurPoint_.__doc__
1682
d104b0ae
EMS
1683reversion.register(ValeurPoint, format='xml')
1684
e9bbd6ba 1685
7013d234 1686class Devise(Archivable):
6e4600ef 1687 """
fa1f7426
EMS
1688 Devise monétaire.
1689 """
fa1f7426 1690 code = models.CharField(max_length=10, unique=True)
e9bbd6ba 1691 nom = models.CharField(max_length=255)
1692
6e4600ef 1693 class Meta:
1694 ordering = ['code']
7013d234
EMS
1695 verbose_name = u"devise"
1696 verbose_name_plural = u"devises"
ca1a7b76 1697
e9bbd6ba 1698 def __unicode__(self):
1699 return u'%s - %s' % (self.code, self.nom)
1700
d104b0ae
EMS
1701reversion.register(Devise, format='xml')
1702
fa1f7426 1703
15659516 1704class TypeContrat(Archivable):
fa1f7426
EMS
1705 """
1706 Type de contrat.
6e4600ef 1707 """
e9bbd6ba 1708 nom = models.CharField(max_length=255)
6e4600ef 1709 nom_long = models.CharField(max_length=255)
49f9f116 1710
8c1ae2b3 1711 class Meta:
1712 ordering = ['nom']
c1195471
OL
1713 verbose_name = u"Type de contrat"
1714 verbose_name_plural = u"Types de contrat"
8c1ae2b3 1715
9afaa55e 1716 def __unicode__(self):
1717 return u'%s' % (self.nom)
ca1a7b76 1718
d104b0ae
EMS
1719reversion.register(TypeContrat, format='xml')
1720
ca1a7b76 1721
2d4d2fcf 1722### AUTRES
1723
8c8ffc4f 1724class ResponsableImplantationProxy(ref.Implantation):
7bf28694 1725
6fb68b2f
DB
1726 def save(self):
1727 pass
1728
8c8ffc4f 1729 class Meta:
d7bf0cd3 1730 managed = False
8c8ffc4f
OL
1731 proxy = True
1732 verbose_name = u"Responsable d'implantation"
1733 verbose_name_plural = u"Responsables d'implantation"
1734
1735
1736class ResponsableImplantation(models.Model):
fa1f7426
EMS
1737 """
1738 Le responsable d'une implantation.
30be56d5 1739 Anciennement géré sur le Dossier du responsable.
1740 """
fa1f7426
EMS
1741 employe = models.ForeignKey(
1742 'Employe', db_column='employe', related_name='+', null=True,
1743 blank=True
1744 )
1745 implantation = models.OneToOneField(
1746 "ResponsableImplantationProxy", db_column='implantation',
1747 related_name='responsable', unique=True
1748 )
30be56d5 1749
1750 def __unicode__(self):
1751 return u'%s : %s' % (self.implantation, self.employe)
ca1a7b76 1752
30be56d5 1753 class Meta:
1754 ordering = ['implantation__nom']
8c1ae2b3 1755 verbose_name = "Responsable d'implantation"
1756 verbose_name_plural = "Responsables d'implantation"
d104b0ae
EMS
1757
1758reversion.register(ResponsableImplantation, format='xml')
edbc9e37
OL
1759
1760
e8b6a20c
BS
1761class UserProfile(models.Model):
1762 user = models.OneToOneField(User, related_name='profile')
1763 zones_administratives = models.ManyToManyField(
1764 ref.ZoneAdministrative,
1765 related_name='profiles'
1766 )
1767 class Meta:
1768 verbose_name = "Permissions sur zones administratives"
1769 verbose_name_plural = "Permissions sur zones administratives"
1770
1771 def __unicode__(self):
1772 return self.user.__unicode__()
1773
1774reversion.register(UserProfile, format='xml')
343cfd9c
BS
1775
1776
1777
1778TYPES_CHANGEMENT = (
1779 ('NO', 'Nouveau personnel'),
1780 ('MO', 'Mouvement de personnel'),
1781 ('DE', 'Départ de personnel'),
1782 )
1783
1784
1785class ChangementPersonnelNotifications(models.Model):
1786 class Meta:
1787 verbose_name = u"Destinataire pour notices de changement de personnel"
1788 verbose_name_plural = u"Destinataires pour notices de changement de personnel"
1789
1790 type = models.CharField(
1791 max_length=2,
1792 choices = TYPES_CHANGEMENT,
1793 unique=True,
1794 )
1795
1796 destinataires = models.ManyToManyField(
1797 ref.Employe,
1798 related_name='changement_notifications',
1799 )
1800
1801 def __unicode__(self):
1802 return '%s: %s' % (
1803 self.get_type_display(), ','.join(
1804 self.destinataires.all().values_list(
1805 'courriel', flat=True))
1806 )
1807
1808
1809class ChangementPersonnel(models.Model):
1810 """
1811 Une notice qui enregistre un changement de personnel, incluant:
1812
1813 * Nouveaux employés
1814 * Mouvement de personnel
1815 * Départ d'employé
1816 """
1817
1818 class Meta:
1819 verbose_name = u"Notification de changement du personnel"
1820 verbose_name_plural = u"Notifications de changement du personnel"
1821
1822 def __unicode__(self):
1823 return '%s: %s' % (self.dossier.__unicode__(),
1824 self.get_type_display())
1825
1826 @classmethod
1827 def create_changement(cls, dossier, type):
1828 # If this employe has existing Changement, set them to invalid.
1829 cls.objects.filter(dossier__employe=dossier.employe).update(valide=False)
1830
1831 # Create a new one.
1832 cls.objects.create(
1833 dossier=dossier,
1834 type=type,
1835 valide=True,
1836 communique=False,
1837 )
1838
1839
1840 @classmethod
343cfd9c
BS
1841 def post_save_handler(cls,
1842 sender,
1843 instance,
1844 created,
1845 using,
1846 **kw):
1847
1848 # This defines the time limit used when checking in previous
1849 # files to see if an employee if new. Basically, if emloyee
1850 # left his position new_file.date_debut -
1851 # NEW_EMPLOYE_THRESHOLD +1 ago (compared to date_debut), then
1852 # if a new file is created for this employee, he will bec
1853 # onsidered "NEW" and a notice will be created to this effect.
1854 NEW_EMPLOYE_THRESHOLD = datetime.timedelta(7) # 7 days.
1855
1856 other_dossier_qs = instance.employe.rh_dossiers.exclude(
1857 id=instance.id)
1858 dd = instance.date_debut
1859 df = instance.date_fin
1860
1861 # Here, verify differences between the instance, before and
1862 # after the save.
e535b526
BS
1863 df_has_changed = False
1864
343cfd9c 1865 if created:
e535b526 1866 if df != None:
343cfd9c
BS
1867 df_has_changed = True
1868 else:
1869 df_has_changed = (df != instance.before_save.date_fin and
1870 df != None)
1871
1872
1873 # VERIFICATIONS:
1874
1875 # Date de fin est None et c'est une nouvelle instance de
1876 # Dossier
1877 if not df and created:
1878 # QS for finding other dossiers with a date_fin of None OR
1879 # with a date_fin >= to this dossier's date_debut
1880 exists_recent_file_qs = other_dossier_qs.filter(
1881 Q(date_fin__isnull=True) |
1882 Q(date_fin__gte=dd - NEW_EMPLOYE_THRESHOLD)
1883 )
1884
1885 # 1. If existe un Dossier récent, et c'est une nouvelle
1886 # instance de Dossier:
1887 if exists_recent_file_qs.count() > 0:
1888 cls.create_changement(
1889 instance,
1890 'MO',
1891 )
1892 # 2. Il n'existe un Dossier récent, et c'est une nouvelle
1893 # instance de Dossier:
1894 else:
1895 cls.create_changement(
1896 instance,
1897 'NO',
1898 )
1899
1900
1901 # Date de fin a été modifiée:
1902 if df_has_changed:
1903 # QS for other active files (date_fin == None), excludes
1904 # instance.
1905 exists_active_files_qs = other_dossier_qs.filter(
1906 Q(date_fin__isnull=True))
1907
1908 # 3. Date de fin a été modifiée et il n'existe aucun autre
1909 # dossier actifs: Depart
1910 if exists_active_files_qs.count() == 0:
1911 cls.create_changement(
1912 instance,
1913 'DE',
1914 )
1915 # 4. Dossier a une nouvelle date de fin par contre
1916 # d'autres dossiers actifs existent déjà: Mouvement
1917 else:
1918 cls.create_changement(
1919 instance,
1920 'MO',
1921 )
1922
1923
1924 dossier = models.ForeignKey(
1925 Dossier,
1926 related_name='mouvements',
1927 )
1928
1929 valide = models.BooleanField(default=True)
1930 communique = models.BooleanField(default=False)
1931 date_communication = models.DateTimeField(
1932 null=True,
1933 blank=True,
1934 )
1935
1936 type = models.CharField(
1937 max_length=2,
1938 choices = TYPES_CHANGEMENT,
1939 )
1940
1941reversion.register(ChangementPersonnel, format='xml')
1942
1943
9388fbac
BS
1944def dossier_pre_save_handler(sender,
1945 instance,
1946 using,
1947 **kw):
1948 # Store a copy of the model before save is called.
1949 if instance.pk is not None:
1950 instance.before_save = Dossier.objects.get(pk=instance.pk)
1951 else:
1952 instance.before_save = None
1953
1954
1955# Connect a pre_save handler that assigns a copy of the model as an
1956# attribute in order to compare it in post_save.
1957pre_save.connect(dossier_pre_save_handler, sender=Dossier)
1958
343cfd9c 1959post_save.connect(ChangementPersonnel.post_save_handler, sender=Dossier)
9388fbac
BS
1960post_save.connect(RHDossierClassementRecord.post_save_handler, sender=Dossier)
1961
1962