fix #1432
[auf_rh_dae.git] / project / dae / models.py
CommitLineData
bd28238f 1# -=- encoding: utf-8 -=-
3f3cf5f3 2
36341125 3import os
5633fa41 4from django.conf import settings
36341125 5from django.core.files.storage import FileSystemStorage
a9c281dd
OL
6from django.db import models
7import reversion
afc204bf 8from workflow import PosteWorkflow, DossierWorkflow
f258e4e7 9from managers import DossierManager, PosteManager
bd28238f 10import datamaster_modeles.models as ref
a9c281dd 11from rh_v1 import models as rh
bd28238f 12
bd28238f 13
2d4d2fcf 14# Constantes
15HELP_TEXT_DATE = "format: aaaa-mm-jj"
16REGIME_TRAVAIL_DEFAULT=100.00
17REGIME_TRAVAIL_NB_HEURE_SEMAINE_DEFAULT=35.00
bd28238f 18
bd28238f 19
d766bf2c 20# Upload de fichiers
2d4d2fcf 21storage_prive = FileSystemStorage(settings.PRIVE_MEDIA_ROOT,
22 base_url=settings.PRIVE_MEDIA_URL)
36341125
OL
23
24def poste_piece_dispatch(instance, filename):
fe13cd48 25 path = "poste/%s/%s" % (instance.poste_id, filename)
36341125
OL
26 return path
27
d766bf2c 28def dossier_piece_dispatch(instance, filename):
fe13cd48 29 path = "dossier/%s/%s" % (instance.dossier_id, filename)
d766bf2c
OL
30 return path
31
36341125 32
2d4d2fcf 33### POSTE
34
35POSTE_APPEL_CHOICES = (
36 ('interne', 'Interne'),
37 ('externe', 'Externe'),
38)
36341125 39
1c7d67ce 40
8fa94e8b 41class Poste(PosteWorkflow, models.Model):
bd28238f 42 # Modèle existant
5d680e84 43 id_rh = models.ForeignKey(rh.Poste, null=True, related_name='+',
2d4d2fcf 44 editable=False,
45 verbose_name="Mise à jour du poste")
ce110fb9 46 nom = models.CharField(verbose_name="Titre du poste", max_length=255)
5d680e84
NC
47 implantation = models.ForeignKey(ref.Implantation)
48 type_poste = models.ForeignKey(rh.TypePoste, null=True, related_name='+')
98d51b59 49 service = models.ForeignKey(rh.Service, related_name='+',
2d4d2fcf 50 verbose_name=u"Direction/Service/Pôle support")
98d51b59 51 responsable = models.ForeignKey(rh.Poste, related_name='+',
2d4d2fcf 52 verbose_name="Poste du responsable")
9a85768a 53
2d4d2fcf 54 # Contrat
5d680e84 55 regime_travail = models.DecimalField(max_digits=12, decimal_places=2,
2d4d2fcf 56 default=REGIME_TRAVAIL_DEFAULT,
57 verbose_name="Temps de travail",
58 help_text="% du temps complet")
5d680e84 59 regime_travail_nb_heure_semaine = models.DecimalField(max_digits=12,
2d4d2fcf 60 decimal_places=2,
61 default=REGIME_TRAVAIL_NB_HEURE_SEMAINE_DEFAULT,
62 verbose_name="Nb. heures par semaine")
bd28238f
NC
63
64 # Recrutement
154677c3 65 local = models.BooleanField(verbose_name="Local", default=True, blank=True)
2d4d2fcf 66 expatrie = models.BooleanField(verbose_name="Expatrié", default=False,
67 blank=True)
a3508c67 68 mise_a_disposition = models.BooleanField(verbose_name="Mise à disposition")
98d51b59 69 appel = models.CharField(max_length=10, default='interne',
2d4d2fcf 70 verbose_name="Appel à candidature",
71 choices=POSTE_APPEL_CHOICES)
bd28238f
NC
72
73 # Rémunération
2d4d2fcf 74 classement_min = models.ForeignKey(rh.Classement, related_name='+',
75 blank=True, null=True)
76 classement_max = models.ForeignKey(rh.Classement, related_name='+',
77 blank=True, null=True)
78 valeur_point_min = models.ForeignKey(rh.ValeurPoint, related_name='+',
79 blank=True, null=True)
80 valeur_point_max = models.ForeignKey(rh.ValeurPoint, related_name='+',
81 blank=True, null=True)
3d627bfd 82 devise_min = models.ForeignKey(rh.Devise, default=5, related_name='+')
83 devise_max = models.ForeignKey(rh.Devise, default=5, related_name='+')
5d680e84
NC
84 salaire_min = models.DecimalField(max_digits=12, decimal_places=2,
85 default=0)
86 salaire_max = models.DecimalField(max_digits=12, decimal_places=2,
87 default=0)
88 indemn_min = models.DecimalField(max_digits=12, decimal_places=2,
89 default=0)
90 indemn_max = models.DecimalField(max_digits=12, decimal_places=2,
91 default=0)
92 autre_min = models.DecimalField(max_digits=12, decimal_places=2,
93 default=0)
94 autre_max = models.DecimalField(max_digits=12, decimal_places=2,
95 default=0)
96
97 # Comparatifs de rémunération
98 devise_comparaison = models.ForeignKey(rh.Devise, related_name='+',
a3508c67 99 default=5)
5d680e84
NC
100 comp_locale_min = models.DecimalField(max_digits=12, decimal_places=2,
101 null=True, blank=True)
102 comp_locale_max = models.DecimalField(max_digits=12, decimal_places=2,
103 null=True, blank=True)
104 comp_universite_min = models.DecimalField(max_digits=12, decimal_places=2,
105 null=True, blank=True)
106 comp_universite_max = models.DecimalField(max_digits=12, decimal_places=2,
107 null=True, blank=True)
108 comp_fonctionpub_min = models.DecimalField(max_digits=12, decimal_places=2,
109 null=True, blank=True)
110 comp_fonctionpub_max = models.DecimalField(max_digits=12, decimal_places=2,
111 null=True, blank=True)
112 comp_ong_min = models.DecimalField(max_digits=12, decimal_places=2,
113 null=True, blank=True)
114 comp_ong_max = models.DecimalField(max_digits=12, decimal_places=2,
115 null=True, blank=True)
116 comp_autre_min = models.DecimalField(max_digits=12, decimal_places=2,
117 null=True, blank=True)
118 comp_autre_max = models.DecimalField(max_digits=12, decimal_places=2,
119 null=True, blank=True)
bd28238f 120
2e092e0c
OL
121 # Justification
122 justification = models.TextField()
123
bd28238f 124 # Méta
5d680e84
NC
125 date_creation = models.DateTimeField(auto_now_add=True)
126 date_modification = models.DateTimeField(auto_now=True)
98d51b59 127 date_debut = models.DateField(verbose_name="Date de début",
2d4d2fcf 128 help_text=HELP_TEXT_DATE)
9fb2ccd9 129 date_fin = models.DateField(null=True, blank=True,
130 verbose_name="Date de fin",
2d4d2fcf 131 help_text=HELP_TEXT_DATE)
bd28238f
NC
132 actif = models.BooleanField(default=True)
133
1c7d67ce
OL
134 # Managers
135 objects = PosteManager()
136
03858ba5
OL
137 def _get_key(self):
138 """
2d4d2fcf 139 Les vues sont montées selon une clef spéciale
140 pour identifier la provenance du poste.
141 Cette méthode fournit un moyen de reconstruire cette clef
142 afin de générer les URLs.
03858ba5
OL
143 """
144 return "dae-%s" % self.id
145 key = property(_get_key)
146
f3333b0e
OL
147 def get_dossiers(self):
148 """
149 Liste tous les anciens dossiers liés à ce poste.
2d4d2fcf 150 (Le nom de la relation sur le rh.Poste est mal choisi
151 poste1 au lieu de dossier1)
f3333b0e 152 Note1 : seulement le dosssier principal fait l'objet de la recherche.
2d4d2fcf 153 Note2 : les dossiers sont retournés du plus récent au plus vieux.
154 (Ce test est fait en fonction du id,
155 car les dates de création sont absentes de rh v1).
f3333b0e
OL
156 """
157 if self.id_rh is None:
158 return []
159 postes = [p for p in self.id_rh.poste1.all()]
160 return sorted(postes, key=lambda poste: poste.id, reverse=True)
161
162 def get_complement_nom(self):
163 """
2d4d2fcf 164 Inspecte les modèles rh v1 pour trouver dans le dernier dossier
165 un complément de titre de poste.
f3333b0e
OL
166 """
167 dossiers = self.get_dossiers()
168 if len(dossiers) > 0:
169 nom = dossiers[0].complement1
170 else:
171 nom = ""
a9c281dd 172 return nom
f3333b0e
OL
173
174 def get_employe(self):
175 """
176 Inspecte les modèles rh v1 pour trouver l'employé du dernier dossier.
177 """
178 dossiers = self.get_dossiers()
179 if len(dossiers) > 0:
180 return dossiers[0].employe
181 else:
182 return None
183
179f6b49 184 def get_default_devise(self):
2d4d2fcf 185 """Récupère la devise par défaut en fonction de l'implantation
186 (EUR autrement)
187 """
179f6b49 188 try:
6e4600ef 189 implantation_devise = rh.TauxChange.objects \
190 .filter(implantation=self.implantation)[0].devise
179f6b49
OL
191 except:
192 implantation_devise = 5 # EUR
193 return implantation_devise
194
c0413a6f
OL
195 #####################
196 # Classement de poste
197 #####################
198
199 def get_couts_minimum(self):
200 return (float)(self.salaire_min + self.indemn_min + self.autre_min)
201
202 def get_taux_minimum(self):
203 try:
204 return rh.TauxChange.objects.filter(implantation=self.implantation, devise=self.devise_min)[0].taux
205 except:
206 return 1
207
208 def get_couts_minimum_euros(self):
209 return self.get_couts_minimum() * self.get_taux_minimum()
210
211 def get_couts_maximum(self):
212 return (float)(self.salaire_max + self.indemn_max + self.autre_max)
213
214 def get_taux_maximum(self):
215 try:
216 return rh.TauxChange.objects.filter(implantation=self.implantation, devise=self.devise_max)[0].taux
217 except:
218 return 1
219
220 def get_couts_maximum_euros(self):
221 return self.get_couts_maximum() * self.get_taux_maximum()
222
223 ######################
224 # Comparaison de poste
225 ######################
a3fee9c5
OL
226
227 def est_comparable(self):
228 """
229 Si on a au moins une valeur de saisie dans les comparaisons, alors le poste
230 est comparable.
231 """
232 if self.comp_universite_min is None and \
233 self.comp_fonctionpub_min is None and \
234 self.comp_locale_min is None and \
235 self.comp_ong_min is None and \
236 self.comp_autre_min is None and \
237 self.comp_universite_max is None and \
238 self.comp_fonctionpub_max is None and \
239 self.comp_locale_max is None and \
240 self.comp_ong_max is None and \
241 self.comp_autre_max is None:
242 return False
243 else:
244 return True
c0413a6f 245
a3fee9c5 246
c0413a6f
OL
247 def get_taux_comparaison(self):
248 try:
249 return rh.TauxChange.objects.filter(implantation=self.implantation, devise=self.devise_comparaison)[0].taux
250 except:
251 return 1
252
253 def get_comp_universite_min_euros(self):
254 return (float)(self.comp_universite_min) * self.get_taux_comparaison()
255
256 def get_comp_fonctionpub_min_euros(self):
257 return (float)(self.comp_fonctionpub_min) * self.get_taux_comparaison()
258
259 def get_comp_locale_min_euros(self):
260 return (float)(self.comp_locale_min) * self.get_taux_comparaison()
261
262 def get_comp_ong_min_euros(self):
263 return (float)(self.comp_ong_min) * self.get_taux_comparaison()
264
265 def get_comp_autre_min_euros(self):
266 return (float)(self.comp_autre_min) * self.get_taux_comparaison()
267
268 def get_comp_universite_max_euros(self):
269 return (float)(self.comp_universite_max) * self.get_taux_comparaison()
270
271 def get_comp_fonctionpub_max_euros(self):
272 return (float)(self.comp_fonctionpub_max) * self.get_taux_comparaison()
273
274 def get_comp_locale_max_euros(self):
275 return (float)(self.comp_locale_max) * self.get_taux_comparaison()
276
277 def get_comp_ong_max_euros(self):
278 return (float)(self.comp_ong_max) * self.get_taux_comparaison()
279
280 def get_comp_autre_max_euros(self):
281 return (float)(self.comp_autre_max) * self.get_taux_comparaison()
282
a9e52624 283
5d680e84 284 def __unicode__(self):
f3333b0e 285 """
2d4d2fcf 286 Cette fonction est consommatrice SQL car elle cherche les dossiers
287 qui ont été liés à celui-ci.
f3333b0e
OL
288 """
289 complement_nom_poste = self.get_complement_nom()
c7a4aa2b
OL
290 if complement_nom_poste is None:
291 complement_nom_poste = ""
f3333b0e
OL
292 data = (
293 self.implantation,
294 self.type_poste.nom,
295 self.nom,
f3333b0e 296 )
585d5317 297 return u'%s - %s (%s)' % data
5d680e84 298
bd28238f 299
a9c281dd
OL
300# Tester l'enregistrement car les models.py sont importés au complet
301if not reversion.is_registered(Poste):
302 reversion.register(Poste)
303
bd28238f 304
5d680e84
NC
305POSTE_FINANCEMENT_CHOICES = (
306 ('A', 'A - Frais de personnel'),
307 ('B', 'B - Projet(s)-Titre(s)'),
308 ('C', 'C - Autre')
309)
bd28238f 310
5d680e84 311class PosteFinancement(models.Model):
5d680e84
NC
312 poste = models.ForeignKey('Poste', related_name='financements')
313 type = models.CharField(max_length=1, choices=POSTE_FINANCEMENT_CHOICES)
43d04712 314 pourcentage = models.DecimalField(max_digits=12, decimal_places=2,
315 help_text="ex.: 33.33 % (décimale avec point)")
316 commentaire = models.TextField(
317 help_text="Spécifiez la source de financement.")
bd28238f 318
43d04712 319 class Meta:
320 ordering = ['type']
bd28238f 321
c0413a6f
OL
322 def __unicode__(self):
323 return u"%s %s %s" % (self.get_type_display(), self.pourcentage, self.commentaire)
324
c589d980 325
2d4d2fcf 326class PostePiece(models.Model):
327 """Documents relatifs au Poste
328 Ex.: Description de poste
329 """
330 poste = models.ForeignKey("Poste")
331 nom = models.CharField(verbose_name="Nom", max_length=255)
332 fichier = models.FileField(verbose_name="Fichier",
333 upload_to=poste_piece_dispatch,
334 storage=storage_prive)
335
336### EMPLOYÉ/PERSONNE
337
338# TODO : migration pour m -> M, f -> F
c589d980 339
bd28238f 340GENRE_CHOICES = (
139686f2
NC
341 ('m', 'Homme'),
342 ('f', 'Femme'),
bd28238f
NC
343)
344
bd28238f 345class Employe(models.Model):
bd28238f
NC
346
347 # Modèle existant
da3ca955 348 id_rh = models.ForeignKey(rh.Employe, null=True, related_name='+',
349 verbose_name='Employé')
bd28238f 350 nom = models.CharField(max_length=255)
da3ca955 351 prenom = models.CharField(max_length=255, verbose_name='Prénom')
07b40eda 352 genre = models.CharField(max_length=1, choices=GENRE_CHOICES)
bd28238f 353
139686f2 354 def __unicode__(self):
2d4d2fcf 355 return u'%s %s' % (self.prenom, self.nom.upper())
139686f2 356
bd28238f 357
2d4d2fcf 358### DOSSIER
359
360STATUT_RESIDENCE_CHOICES = (
361 ('local', 'Local'),
362 ('expat', 'Expatrié'),
363)
364
bd28238f 365COMPTE_COMPTA_CHOICES = (
494ff2be
NC
366 ('coda', 'CODA'),
367 ('scs', 'SCS'),
368 ('aucun', 'Aucun'),
bd28238f
NC
369)
370
afc204bf 371class Dossier(DossierWorkflow, models.Model):
bd28238f
NC
372
373 # Modèle existant
139686f2
NC
374 employe = models.ForeignKey('Employe', related_name='+', editable=False)
375 poste = models.ForeignKey('Poste', related_name='+', editable=False)
5d680e84 376 statut = models.ForeignKey(rh.Statut, related_name='+')
dfc2c344 377 organisme_bstg = models.ForeignKey(rh.OrganismeBstg,
1f109689 378 null=True, blank=True,
dfc2c344 379 verbose_name="Organisme",
380 help_text="Si détaché (DET) ou mis à disposition (MAD), \
381 préciser l'organisme.",
382 related_name='+')
0288adb5
OL
383 organisme_bstg_autre = models.CharField(max_length=255,
384 verbose_name="Autre organisme",
385 help_text="indiquer l'organisme ici s'il n'est pas dans la liste",
386 null=True,
387 blank=True,)
bd28238f 388
139686f2
NC
389 # Données antérieures de l'employé
390 statut_anterieur = models.ForeignKey(
391 rh.Statut, related_name='+', null=True, blank=True,
da3ca955 392 verbose_name='Statut antérieur')
139686f2
NC
393 classement_anterieur = models.ForeignKey(
394 rh.Classement, related_name='+', null=True, blank=True,
395 verbose_name='Classement précédent')
396 salaire_anterieur = models.DecimalField(
397 max_digits=12, decimal_places=2, null=True, default=None,
398 blank=True, verbose_name='Salaire précédent')
399
400 # Données du titulaire précédent
401 employe_anterieur = models.ForeignKey(
402 rh.Employe, related_name='+', null=True, blank=True,
403 verbose_name='Employé précédent')
404 statut_titulaire_anterieur = models.ForeignKey(
405 rh.Statut, related_name='+', null=True, blank=True,
406 verbose_name='Statut titulaire précédent')
407 classement_titulaire_anterieur = models.ForeignKey(
408 rh.Classement, related_name='+', null=True, blank=True,
409 verbose_name='Classement titulaire précédent')
410 salaire_titulaire_anterieur = models.DecimalField(
411 max_digits=12, decimal_places=2, default=None, null=True,
412 blank=True, verbose_name='Salaire titulaire précédent')
494ff2be 413
bd28238f
NC
414 # Recrutement
415 remplacement = models.BooleanField()
4d25e2ba 416 statut_residence = models.CharField(max_length=10, default='local',
2d4d2fcf 417 verbose_name="Statut",
418 choices=STATUT_RESIDENCE_CHOICES)
bd28238f
NC
419
420 # Rémunération
494ff2be 421 classement = models.ForeignKey(rh.Classement, related_name='+',
2d4d2fcf 422 null=True, blank=True,
423 verbose_name='Classement proposé')
494ff2be 424 salaire = models.DecimalField(max_digits=12, decimal_places=2,
2d4d2fcf 425 verbose_name='Salaire de base',
426 null=True, default=None)
e8e75458 427 devise = models.ForeignKey(rh.Devise, default=5, related_name='+')
2d4d2fcf 428 regime_travail = models.DecimalField(max_digits=12,
429 decimal_places=2,
430 default=REGIME_TRAVAIL_DEFAULT,
431 verbose_name="Régime de travail",
432 help_text="% du temps complet")
139686f2 433 regime_travail_nb_heure_semaine = models.DecimalField(max_digits=12,
2d4d2fcf 434 decimal_places=2,
435 default=REGIME_TRAVAIL_NB_HEURE_SEMAINE_DEFAULT,
436 verbose_name="Nb. heures par semaine")
bd28238f
NC
437
438 # Contrat
5d680e84 439 type_contrat = models.ForeignKey(rh.TypeContrat, related_name='+')
4d25e2ba 440 contrat_date_debut = models.DateField(help_text="format: aaaa-mm-jj")
1f109689 441 contrat_date_fin = models.DateField(null=True, blank=True,
442 help_text="format: aaaa-mm-jj")
bd28238f
NC
443
444 # Comptes
dfc2c344 445 compte_compta = models.CharField(max_length=10, default='aucun',
446 verbose_name=u'Compte comptabilité',
447 choices=COMPTE_COMPTA_CHOICES)
bd28238f 448 compte_courriel = models.BooleanField()
0140cbd2 449
450 # Méta
451 date_creation = models.DateTimeField(auto_now_add=True)
e4f56614
OL
452
453 # Managers
454 objects = DossierManager()
455
aec2c91e 456 def __unicode__(self):
e4f56614 457 return u'[%s] %s - %s' % (self.poste.implantation, self.poste.nom, self.employe)
bd28238f 458
b1baa306
OL
459 def get_salaire_euros(self):
460 try:
461 tx = rh.TauxChange.objects.filter(implantation=self.poste.implantation, devise=self.devise)[0].taux
462 except:
463 tx = 1
464 return (float)(tx) * (float)(self.salaire)
465
57bd966c
OL
466 def get_couts_auf(self):
467 """
468 On retire les MAD BSTG
469 """
470 return [r for r in self.remuneration_set.all() if r.type_id not in (2, )]
a9e52624
OL
471
472 def get_total_couts_auf(self):
473 total = 0.0
474 for r in self.get_couts_auf():
475 total += r.montant_euro()
476 return total
57bd966c
OL
477
478 def get_aides_auf(self):
479 """
480 On récupère les MAD BSTG
481 """
482 return [r for r in self.remuneration_set.all() if r.type_id in (2, )]
483
a9e52624
OL
484 def get_total_aides_auf(self):
485 total = 0.0
486 for r in self.get_aides_auf():
487 total += r.montant_euro()
488 return total
489
490 def get_total_remun(self):
491 return self.get_total_couts_auf() + self.get_total_aides_auf()
bd28238f 492
0140cbd2 493# Tester l'enregistrement car les models.py sont importés au complet
494if not reversion.is_registered(Dossier):
495 reversion.register(Dossier)
bd28238f 496
2d4d2fcf 497class DossierPiece(models.Model):
498 """Documents relatifs au Dossier (à l'occupation de ce poste par employé).
499 Ex.: Lettre de motivation.
500 """
501 dossier = models.ForeignKey("Dossier")
502 nom = models.CharField(verbose_name="Nom", max_length=255)
503 fichier = models.FileField(verbose_name="Fichier",
504 upload_to=dossier_piece_dispatch,
505 storage=storage_prive)
506
507
03b395db
OL
508class DossierComparaison(models.Model):
509 """
510 Photo d'une comparaison salariale au moment de l'embauche.
511 """
512 dossier = models.ForeignKey('Dossier', related_name='comparaisons')
513 implantation = models.ForeignKey(ref.Implantation, null=True, blank=True)
514 poste = models.CharField(max_length=255, null=True, blank=True)
515 personne = models.CharField(max_length=255, null=True, blank=True)
516 montant = models.IntegerField(null=True)
517 devise = models.ForeignKey(rh.Devise, default=5, related_name='+', null=True, blank=True)
518 montant_euros = models.IntegerField(null=True)
519
c589d980 520
2d4d2fcf 521### RÉMUNÉRATION
522
bd28238f 523class Remuneration(models.Model):
5d680e84 524 # Identification
bd28238f 525 dossier = models.ForeignKey('Dossier', db_column='dossier')
cb1d62b5
NC
526 type = models.ForeignKey(rh.TypeRemuneration, db_column='type',
527 related_name='+')
cb1d62b5
NC
528 montant = models.DecimalField(max_digits=12, decimal_places=2,
529 null=True) # Annuel
494ff2be 530 devise = models.ForeignKey(rh.Devise, to_field='code',
5d680e84 531 db_column='devise', related_name='+')
cb1d62b5 532 precision = models.CharField(max_length=255, null=True, blank=True)
bd28238f 533
5d680e84 534 # Méta
bd28238f 535 date_creation = models.DateField(auto_now_add=True)
2d4d2fcf 536 user_creation = models.IntegerField(null=True, blank=True) # TODO : user
bd28238f 537
ee91bc95
NC
538 def montant_mois(self):
539 return round(self.montant / 12, 2)
540
541 def taux_devise(self):
542 return self.devise.tauxchange_set.order_by('-annee').all()[0].taux
543
544 def montant_euro(self):
a9e52624 545 return round(float(self.montant) * float(self.taux_devise()), 2)
ee91bc95
NC
546
547 def montant_euro_mois(self):
548 return round(self.montant_euro() / 12, 2)
bd28238f
NC
549
550
2d4d2fcf 551### JUSTIFICATIONS
552
72db8238
OL
553TYPE_JUSTIFICATIONS = (
554 ('N', 'Nouvel employé'),
555 ('R', 'Renouvellement employé'),
556)
bd28238f 557
72db8238
OL
558class JustificationQuestion(models.Model):
559 question = models.CharField(max_length=255)
560 type = models.CharField(max_length=255, choices=TYPE_JUSTIFICATIONS)
561
562 def __unicode__(self,):
563 return self.question
bd28238f 564
72db8238
OL
565class JustificationNouvelEmploye(models.Model):
566 dossier = models.ForeignKey("Dossier")
567 question = models.ForeignKey("JustificationQuestion")
568 reponse = models.TextField()
bd28238f 569
72db8238
OL
570class JustificationAutreEmploye(models.Model):
571 dossier = models.ForeignKey("Dossier")
572 question = models.ForeignKey("JustificationQuestion")
573 reponse = models.TextField()