refact models (incomplet)
[auf_rh_dae.git] / project / rh / models.py
1 # -=- encoding: utf-8 -=-
2
3 import datetime
4
5 from django.core.files.storage import FileSystemStorage
6 from django.db import models
7 import settings
8
9 import datamaster_modeles.models as ref
10
11
12 # Constantes
13 HELP_TEXT_DATE = "format: aaaa-mm-jj"
14 REGIME_TRAVAIL_DEFAULT=100.00
15 REGIME_TRAVAIL_NB_HEURE_SEMAINE_DEFAULT=35.00
16
17
18 # Upload de fichiers
19 storage_prive = FileSystemStorage(settings.PRIVE_MEDIA_ROOT,
20 base_url=settings.PRIVE_MEDIA_URL)
21
22 def poste_piece_dispatch(instance, filename):
23 path = "poste/%s/%s" % (instance.poste_id, filename)
24 return path
25
26 def dossier_piece_dispatch(instance, filename):
27 path = "dossier/%s/%s" % (instance.dossier_id, filename)
28 return path
29
30 # Abstracts
31 class Metadata(models.Model):
32 """Méta-données AUF.
33 """
34 actif = models.BooleanField(default=True)
35 date_creation = models.DateField(auto_now_add=True)
36 user_creation = models.ForeignKey("auth.User")
37 date_modification = models.DateField(auto_now=True)
38 user_modification = models.ForeignKey("auth.User")
39 date_desactivation = models.DateField()
40 user_desactivation = models.ForeignKey("auth.User")
41
42 class Meta:
43 abstract = True
44
45 class Commentaire(Metadata):
46 texte = models.TextField()
47 owner = models.ForeignKey("auth.User")
48
49 class Meta:
50 abstract = True
51
52
53 ### POSTE
54
55 POSTE_APPEL_CHOICES = (
56 ('interne', 'Interne'),
57 ('externe', 'Externe'),
58 )
59
60 class Poste(Metadata):
61 # Identification
62 id = models.IntegerField(primary_key=True)
63 nom = models.CharField(max_length=255,
64 verbose_name="Titre du poste", )
65 nom_feminin = models.CharField(max_length=255,
66 verbose_name="Titre du poste (au féminin)")
67 implantation = models.ForeignKey(ref.Implantation)
68 type_poste = models.ForeignKey('TypePoste', null=True, related_name='+')
69 service = models.ForeignKey('Service', related_name='+',
70 verbose_name=u"Direction/Service/Pôle support")
71 responsable = models.ForeignKey('Poste', related_name='+',
72 verbose_name="Poste du responsable")
73
74 # Contrat
75 regime_travail = models.DecimalField(max_digits=12, decimal_places=2,
76 default=REGIME_TRAVAIL_DEFAULT,
77 verbose_name="Temps de travail",
78 help_text="% du temps complet")
79 regime_travail_nb_heure_semaine = models.DecimalField(max_digits=12,
80 decimal_places=2,
81 default=REGIME_TRAVAIL_NB_HEURE_SEMAINE_DEFAULT,
82 verbose_name="Nb. heures par semaine")
83
84 # Recrutement
85 local = models.BooleanField(verbose_name="Local", default=True, blank=True)
86 expatrie = models.BooleanField(verbose_name="Expatrié", default=False,
87 blank=True)
88 mise_a_disposition = models.BooleanField(verbose_name="Mise à disposition")
89 appel = models.CharField(max_length=10, default='interne',
90 verbose_name="Appel à candidature",
91 choices=POSTE_APPEL_CHOICES)
92
93 # Rémunération
94 classement_min = models.ForeignKey('Classement', related_name='+',
95 blank=True, null=True)
96 classement_max = models.ForeignKey('Classement', related_name='+',
97 blank=True, null=True)
98 valeur_point_min = models.ForeignKey('ValeurPoint', related_name='+',
99 blank=True, null=True)
100 valeur_point_max = models.ForeignKey('ValeurPoint', related_name='+',
101 blank=True, null=True)
102 devise_min = models.ForeignKey('Devise', default=5, related_name='+')
103 devise_max = models.ForeignKey('Devise', default=5, related_name='+')
104 salaire_min = models.DecimalField(max_digits=12, decimal_places=2,
105 default=0)
106 salaire_max = models.DecimalField(max_digits=12, decimal_places=2,
107 default=0)
108 indemn_min = models.DecimalField(max_digits=12, decimal_places=2,
109 default=0)
110 indemn_max = models.DecimalField(max_digits=12, decimal_places=2,
111 default=0)
112 autre_min = models.DecimalField(max_digits=12, decimal_places=2,
113 default=0)
114 autre_max = models.DecimalField(max_digits=12, decimal_places=2,
115 default=0)
116
117 # Comparatifs de rémunération
118 devise_comparaison = models.ForeignKey('Devise', related_name='+',
119 default=5)
120 comp_locale_min = models.DecimalField(max_digits=12, decimal_places=2,
121 null=True, blank=True)
122 comp_locale_max = models.DecimalField(max_digits=12, decimal_places=2,
123 null=True, blank=True)
124 comp_universite_min = models.DecimalField(max_digits=12, decimal_places=2,
125 null=True, blank=True)
126 comp_universite_max = models.DecimalField(max_digits=12, decimal_places=2,
127 null=True, blank=True)
128 comp_fonctionpub_min = models.DecimalField(max_digits=12, decimal_places=2,
129 null=True, blank=True)
130 comp_fonctionpub_max = models.DecimalField(max_digits=12, decimal_places=2,
131 null=True, blank=True)
132 comp_ong_min = models.DecimalField(max_digits=12, decimal_places=2,
133 null=True, blank=True)
134 comp_ong_max = models.DecimalField(max_digits=12, decimal_places=2,
135 null=True, blank=True)
136 comp_autre_min = models.DecimalField(max_digits=12, decimal_places=2,
137 null=True, blank=True)
138 comp_autre_max = models.DecimalField(max_digits=12, decimal_places=2,
139 null=True, blank=True)
140
141 # Justification
142 justification = models.TextField()
143
144 # Autres Metadata
145 date_validation = models.DateTimeField(null=True, blank=True) # de dae
146 date_debut = models.DateField(verbose_name="Date de début",
147 help_text=HELP_TEXT_DATE)
148 date_fin = models.DateField(null=True, blank=True,
149 verbose_name="Date de fin",
150 help_text=HELP_TEXT_DATE)
151
152 def __unicode__(self):
153 # TODO : gérer si poste est vacant ou non dans affichage
154 return u'%s - %s [%s]' % (self.implantation, self.nom, self.id)
155
156
157 POSTE_FINANCEMENT_CHOICES = (
158 ('A', 'A - Frais de personnel'),
159 ('B', 'B - Projet(s)-Titre(s)'),
160 ('C', 'C - Autre')
161 )
162
163 class PosteFinancement(models.Model):
164 poste = models.ForeignKey('Poste', related_name='financements')
165 type = models.CharField(max_length=1, choices=POSTE_FINANCEMENT_CHOICES)
166 pourcentage = models.DecimalField(max_digits=12, decimal_places=2,
167 help_text="ex.: 33.33 % (décimale avec point)")
168 commentaire = models.TextField(
169 help_text="Spécifiez la source de financement.")
170
171 class Meta:
172 ordering = ['type']
173
174 class PostePiece(models.Model):
175 """Documents relatifs au Poste
176 Ex.: Description de poste
177 """
178 poste = models.ForeignKey("Poste")
179 nom = models.CharField(verbose_name="Nom", max_length=255)
180 fichier = models.FileField(verbose_name="Fichier",
181 upload_to=poste_piece_dispatch,
182 storage=storage_prive)
183
184 class PosteCommentaire(Commentaire):
185 poste = models.ForeignKey("Poste")
186
187
188 ### EMPLOYÉ/PERSONNE
189
190 GENRE_CHOICES = (
191 ('M', 'Homme'),
192 ('F', 'Femme'),
193 )
194 SITUATION_CHOICES = (
195 ('C', 'Célibataire'),
196 ('F', 'Fiancé'),
197 ('M', 'Marié'),
198 )
199
200 class Employe(Metadata):
201 # Identification
202 id = models.IntegerField(primary_key=True)
203 nom = models.CharField(max_length=255)
204 prenom = models.CharField(max_length=255, verbose_name='Prénom')
205 nationalite = models.ForeignKey(ref.Pays, to_field='code',
206 related_name='employes_nationalite',
207 db_column='nationalite')
208 date_naissance = models.DateField(null=True, blank=True)
209 genre = models.CharField(max_length=1, choices=GENRE_CHOICES)
210
211 # Infos personnelles
212 situation_famille = models.CharField(max_length=1, null=True, blank=True,
213 choices=SITUATION_CHOICES)
214 date_entree = models.DateField(verbose_name="Date d'entrée à l'AUF",
215 null=True, blank=True)
216
217 # Coordonnées
218 tel_domicile = models.CharField(max_length=255, null=True, blank=True)
219 tel_cellulaire = models.CharField(max_length=255, null=True, blank=True)
220 adresse = models.CharField(max_length=255, null=True, blank=True)
221 no_rue = models.CharField(max_length=255, null=True, blank=True)
222 ville = models.CharField(max_length=255, null=True, blank=True)
223 province = models.CharField(max_length=255, null=True, blank=True)
224 code_postal = models.CharField(max_length=255, null=True, blank=True)
225 pays = models.ForeignKey(ref.Pays, to_field='code',
226 null=True, blank=True,
227 related_name='employes', db_column='pays')
228
229 def __unicode__(self):
230 return u'%s %s' % (self.prenom, self.nom.upper())
231
232 class EmployePiece(models.Model):
233 """Documents relatifs à l'employé
234 Ex.: CV...
235 """
236 employe = models.ForeignKey("Employe")
237 nom = models.CharField(verbose_name="Nom", max_length=255)
238 fichier = models.FileField(verbose_name="Fichier",
239 upload_to=dossier_piece_dispatch,
240 storage=storage_prive)
241
242 class EmployeCommentaire(Commentaire):
243 employe = models.ForeignKey("Employe")
244
245
246 LIEN_PARENTE_CHOICES = (
247 ('Conjoint', 'Conjoint'),
248 ('Conjointe', 'Conjointe'),
249 ('Fille', 'Fille'),
250 ('Fils', 'Fils'),
251 )
252
253 class AyantDroit(Metadata):
254 # Identification
255 id = models.IntegerField(primary_key=True)
256 nom = models.CharField(max_length=255)
257 prenom = models.CharField(max_length=255)
258 nationalite = models.ForeignKey(ref.Pays, to_field='code',
259 related_name='ayantdroits_nationalite',
260 db_column='nationalite')
261 date_naissance = models.DateField(null=True, blank=True)
262 genre = models.CharField(max_length=1, choices=GENRE_CHOICES)
263
264 # Relation
265 employe = models.ForeignKey('Employe', db_column='employe',
266 related_name='ayants_droit')
267 lien_parente = models.CharField(max_length=10, null=True, blank=True,
268 choices=LIEN_PARENTE_CHOICES)
269
270 class AyantDroitCommentaire(Commentaire):
271 ayant_droit = models.ForeignKey("AyantDroit")
272
273
274 ### DOSSIER
275
276 STATUT_RESIDENCE_CHOICES = (
277 ('local', 'Local'),
278 ('expat', 'Expatrié'),
279 )
280
281 COMPTE_COMPTA_CHOICES = (
282 ('coda', 'CODA'),
283 ('scs', 'SCS'),
284 ('aucun', 'Aucun'),
285 )
286
287 class Dossier(Metadata):
288 # Identification
289 id = models.IntegerField(primary_key=True)
290 employe = models.ForeignKey('Employe', db_column='employe')
291 poste = models.ForeignKey('Poste', related_name='+', editable=False)
292 statut = models.ForeignKey('Statut', related_name='+')
293 organisme_bstg = models.ForeignKey('OrganismeBstg',
294 null=True, blank=True,
295 verbose_name="Organisme",
296 help_text="Si détaché (DET) ou mis à disposition (MAD), \
297 préciser l'organisme.",
298 related_name='+')
299
300 # Recrutement
301 remplacement = models.BooleanField(default=False)
302 statut_residence = models.CharField(max_length=10, default='local',
303 verbose_name="Statut",
304 choices=STATUT_RESIDENCE_CHOICES)
305
306 # Rémunération
307 classement = models.ForeignKey('Classement', related_name='+',
308 null=True, blank=True)
309 regime_travail = models.DecimalField(max_digits=12,
310 decimal_places=2,
311 default=REGIME_TRAVAIL_DEFAULT,
312 verbose_name="Régime de travail",
313 help_text="% du temps complet")
314 regime_travail_nb_heure_semaine = models.DecimalField(max_digits=12,
315 decimal_places=2,
316 default=REGIME_TRAVAIL_NB_HEURE_SEMAINE_DEFAULT,
317 verbose_name="Nb. heures par semaine")
318
319 # Occupation du Poste par cet Employe (anciennement "mandat")
320 date_debut = models.DateField(help_text=HELP_TEXT_DATE,
321 verbose_name="Date de début d'occupation de poste")
322 date_fin = models.DateField(null=True, blank=True,
323 help_text=HELP_TEXT_DATE,
324 verbose_name="Date de fin d'occupation de poste")
325 # Contrat
326 # TODO : m2m Contrat
327
328 # Comptes
329 # TODO?
330
331 def __unicode__(self):
332 return u'%s - %s' % (self.poste.nom, self.employe)
333
334 class DossierPiece(models.Model):
335 """Documents relatifs au Dossier (à l'occupation de ce poste par employé).
336 Ex.: Lettre de motivation.
337 """
338 dossier = models.ForeignKey("Dossier")
339 nom = models.CharField(verbose_name="Nom", max_length=255)
340 fichier = models.FileField(verbose_name="Fichier",
341 upload_to=dossier_piece_dispatch,
342 storage=storage_prive)
343
344 class DossierCommentaire(Commentaire):
345 dossier = models.ForeignKey("Dossier")
346
347
348 ### RÉMUNÉRATION
349
350 class RemunerationMixin(Metadata):
351 # Identification
352 id = models.IntegerField(primary_key=True)
353 dossier = models.ForeignKey('Dossier', db_column='dossier')
354 type = models.ForeignKey('TypeRemuneration', db_column='type',
355 related_name='+')
356 type_revalorisation = models.ForeignKey('TypeRevalorisation',
357 db_column='type_revalorisation',
358 null=True, blank=True)
359 montant = models.FloatField(max_digits=12, decimal_places=2,
360 null=True, blank=True)
361 # Annuel (12 mois, 52 semaines, 364 jours?)
362 devise = models.ForeignKey('Devise', to_field='code',
363 db_column='devise', related_name='+')
364 # commentaire = precision
365 commentaire = models.CharField(max_length=255, null=True, blank=True)
366 # date_debut = anciennement date_effectif
367 date_debut = models.DateField(null=True, blank=True)
368 date_fin = models.DateField(null=True, blank=True)
369
370 class Meta:
371 abstract = True
372
373 class Remuneration(RemunerationMixin):
374 """Structure de rémunération (données budgétaires) en situation normale
375 pour un Dossier. Si un Evenement existe, utiliser la structure de
376 rémunération EvenementRemuneration de cet événement.
377 """
378
379 def montant_mois(self):
380 return round(self.montant / 12, 2)
381
382 def taux_devise(self):
383 return self.devise.tauxchange_set.order_by('-annee').all()[0].taux
384
385 def montant_euro(self):
386 return round(float(self.montant) / float(self.taux_devise()), 2)
387
388 def montant_euro_mois(self):
389 return round(self.montant_euro() / 12, 2)
390
391 def __unicode__(self):
392 try:
393 devise = self.devise.code
394 except:
395 devise = "???"
396 return "%s %s" % (self.montant, devise)
397
398
399 ### CONTRATS
400
401 class Contrat(Metadata):
402 """Document juridique qui encadre la relation de travail d'un Employe
403 pour un Poste particulier. Pour un Dossier (qui documente cette
404 relation de travail) plusieurs contrats peuvent être associés.
405 """
406 dossier = models.ForeignKey("Dossier")
407 type_contrat = models.ForeignKey('TypeContrat', related_name='+')
408 date_debut = models.DateField(help_text=HELP_TEXT_DATE)
409 date_fin = models.DateField(null=True, blank=True,
410 help_text=HELP_TEXT_DATE)
411
412
413 ### ÉVÉNEMENTS
414
415 class Evenement(models.Models):
416 pass
417 # nom
418 # date_debut
419 # date_fin
420
421 class EvenementRemuneration(RemunerationMixin):
422 pass
423 # evenement
424
425
426 ### RÉFÉRENCES RH
427
428 class FamilleEmploi(models.Model):
429 # Identification
430 id = models.IntegerField(primary_key=True)
431 nom = models.CharField(max_length=255)
432 # Méta
433 actif = models.BooleanField()
434
435 class TypePoste(models.Model):
436 # Identification
437 id = models.IntegerField(primary_key=True)
438 nom = models.CharField(max_length=255)
439 nom_feminin = models.CharField(max_length=255)
440 #description = models.CharField(max_length=255)
441 is_responsable = models.BooleanField()
442 famille_emploi = models.ForeignKey('FamilleEmploi',
443 db_column='famille_emploi')
444 # Méta
445 date_modification = models.DateField(auto_now=True)
446 actif = models.BooleanField()
447
448 def __unicode__(self):
449 return u'%s' % self.nom
450
451
452 TYPE_PAIEMENT_CHOICES = (
453 ('Régulier', 'Régulier'),
454 ('Ponctuel', 'Ponctuel'),
455 )
456
457 NATURE_REMUNERATION_CHOICES = (
458 ('Accessoire', 'Accessoire'),
459 ('Charges', 'Charges'),
460 ('Indemnité', 'Indemnité'),
461 ('RAS', 'Rémunération autre source'),
462 ('Traitement', 'Traitement'),
463 )
464
465 class TypeRemuneration(models.Model):
466 # Identification
467 id = models.IntegerField(primary_key=True)
468 nom = models.CharField(max_length=255)
469 type_paiement = models.CharField(max_length=30,
470 choices=TYPE_PAIEMENT_CHOICES)
471 nature_remuneration = models.CharField(max_length=30,
472 choices=NATURE_REMUNERATION_CHOICES)
473 # Méta
474 actif = models.BooleanField()
475
476 def __unicode__(self):
477 return u'%s' % self.nom
478
479 class TypeRevalorisation(models.Model):
480 """Justification du changement de la Remuneration.
481 (Actuellement utilisé dans aucun traitement informatique)
482 """
483 # Identification
484 id = models.IntegerField(primary_key=True)
485 nom = models.CharField(max_length=255)
486 # Méta
487 actif = models.BooleanField()
488
489 class Service(models.Model):
490 # Identification
491 id = models.IntegerField(primary_key=True)
492 nom = models.CharField(max_length=255)
493 # Méta
494 actif = models.BooleanField()
495
496 def __unicode__(self):
497 return u'%s' % self.nom
498
499 class Meta:
500 ordering = ['nom']
501
502
503 TYPE_ORGANISME_CHOICES = (
504 ('MAD', 'Mise à disposition'),
505 ('DET', 'Détachement'),
506 )
507
508 class OrganismeBstg(models.Model):
509 # Identification
510 id = models.IntegerField(primary_key=True)
511 nom = models.CharField(max_length=255)
512 type = models.CharField(max_length=10, choices=TYPE_ORGANISME_CHOICES)
513 # pays
514
515 # Méta
516 actif = models.BooleanField()
517
518 def __unicode__(self):
519 return u'%s (%s)' % (self.nom, self.type)
520
521 class Meta:
522 ordering = ['type', 'nom']
523
524
525 CONTRAT_CATEGORIE_CHOICES= (
526 ('A', 'A'),
527 ('C', 'C'),
528 )
529
530 class Statut(models.Model):
531 # Identification
532 id = models.IntegerField(primary_key=True)
533 code = models.CharField(max_length=25, unique=True)
534 nom = models.CharField(max_length=255)
535 type_contrat_categorie = models.CharField(max_length=10,
536 choices=CONTRAT_CATEGORIE_CHOICES)
537 #CHOICES A, C (veut dire quoi?) voir TypeContrat.categorie
538 # A = AUF, C = Contractuel ???
539 # Méta
540 actif = models.BooleanField()
541
542 def __unicode__(self):
543 return u'%s : %s' % (self.code, self.nom)
544
545
546 TYPE_CLASSEMENT_CHOICES = (
547 ('S', 'S'),
548 ('T', 'T'),
549 )
550
551 class Classement(models.Model):
552 # Identification
553 id = models.IntegerField(primary_key=True)
554 type = models.CharField(max_length=10, choices=TYPE_CLASSEMENT_CHOICES)
555 echelon = models.IntegerField()
556 degre = models.IntegerField()
557 coefficient = models.FloatField(null=True)
558 # annee # au lieu de date_debut et date_fin
559 # Méta
560 commentaire = models.TextField(null=True, blank=True)
561 date_modification = models.DateField(auto_now=True)
562 actif = models.BooleanField()
563
564 def __unicode__(self):
565 return u'%s.%s.%s (%s)' % (self.type, self.echelon, self.degre,
566 self.coefficient)
567 class Meta:
568 ordering = ['type','echelon','degre','coefficient']
569
570 class TauxChange(models.Model):
571 """Taux de change de la devise vers l'euro (EUR)
572 pour cette année budgétaire.
573 """
574 # Identification
575 id = models.IntegerField(primary_key=True)
576 devise = models.ForeignKey('Devise', to_field='code', db_column='devise')
577 annee = models.IntegerField()
578 taux = models.FloatField()
579
580 class ValeurPoint(models.Model):
581 # Identification
582 id = models.IntegerField(primary_key=True)
583 valeur = models.FloatField()
584 # devise
585 implantation = models.ForeignKey(ref.Implantation,
586 db_column='implantation',
587 related_name='valeurs_point')
588 # Méta
589 annee = models.IntegerField()
590
591 # Stockage de tous les taux de change
592 # pour optimiser la recherche de la devise associée
593 annee_courante = datetime.datetime.now().year
594 tauxchange = TauxChange.objects.select_related('devise').filter(annee=annee_courante)
595
596 def __unicode__(self):
597 tx = self.get_tauxchange_courant()
598 if tx:
599 devise_code = tx.devise.code
600 else:
601 devise_code = "??"
602 return u'%s %s (%s-%s)' % (self.valeur, devise_code,
603 self.implantation_id, self.annee)
604
605 class Meta:
606 ordering = ['valeur']
607
608 class Devise(models.Model):
609 id = models.IntegerField(primary_key=True)
610 code = models.CharField(max_length=10, unique=True)
611 nom = models.CharField(max_length=255)
612
613 def __unicode__(self):
614 return u'%s - %s' % (self.code, self.nom)
615
616 class TypeContrat(models.Model):
617 # Identification
618 id = models.IntegerField(primary_key=True)
619 nom = models.CharField(max_length=255)
620 nom_long = models.CharField(max_length=255) # description
621 categorie = models.CharField(max_length=10,
622 choices=CONTRAT_CATEGORIE_CHOICES) # A, C?
623 # Méta
624 actif = models.BooleanField()
625
626 def __unicode__(self):
627 return u'%s' % (self.nom)
628
629
630 ### AUTRES
631
632 class ResponsableImplantation(models.Model):
633 """Le responsable d'une implantation.
634 Anciennement géré sur le Dossier du responsable.
635 """
636 employe = models.ForeignKey('Employe', null=True, blank=True)
637 implantation = models.ForeignKey(ref.Implantation, unique=True)
638
639 def __unicode__(self):
640 return u'%s : %s' % (self.implantation, self.employe)
641
642 class Meta:
643 ordering = ['implantation__nom']