1 # -=- encoding: utf-8 -=-
5 from django
.core
.files
.storage
import FileSystemStorage
6 from django
.db
import models
9 import datamaster_modeles
.models
as ref
13 HELP_TEXT_DATE
= "format: aaaa-mm-jj"
14 REGIME_TRAVAIL_DEFAULT
=100.00
15 REGIME_TRAVAIL_NB_HEURE_SEMAINE_DEFAULT
=35.00
19 storage_prive
= FileSystemStorage(settings
.PRIVE_MEDIA_ROOT
,
20 base_url
=settings
.PRIVE_MEDIA_URL
)
22 def poste_piece_dispatch(instance
, filename
):
23 path
= "poste/%s/%s" % (instance
.poste_id
, filename
)
26 def dossier_piece_dispatch(instance
, filename
):
27 path
= "dossier/%s/%s" % (instance
.dossier_id
, filename
)
31 class Metadata(models
.Model
):
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")
45 class Commentaire(Metadata
):
46 texte
= models
.TextField()
47 owner
= models
.ForeignKey("auth.User")
55 POSTE_APPEL_CHOICES
= (
56 ('interne', 'Interne'),
57 ('externe', 'Externe'),
60 class Poste(Metadata
):
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")
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,
81 default
=REGIME_TRAVAIL_NB_HEURE_SEMAINE_DEFAULT
,
82 verbose_name
="Nb. heures par semaine")
85 local
= models
.BooleanField(verbose_name
="Local", default
=True, blank
=True)
86 expatrie
= models
.BooleanField(verbose_name
="Expatrié", default
=False,
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
)
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,
106 salaire_max
= models
.DecimalField(max_digits
=12, decimal_places
=2,
108 indemn_min
= models
.DecimalField(max_digits
=12, decimal_places
=2,
110 indemn_max
= models
.DecimalField(max_digits
=12, decimal_places
=2,
112 autre_min
= models
.DecimalField(max_digits
=12, decimal_places
=2,
114 autre_max
= models
.DecimalField(max_digits
=12, decimal_places
=2,
117 # Comparatifs de rémunération
118 devise_comparaison
= models
.ForeignKey('Devise', related_name
='+',
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)
142 justification
= models
.TextField()
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
)
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)
157 POSTE_FINANCEMENT_CHOICES
= (
158 ('A', 'A - Frais de personnel'),
159 ('B', 'B - Projet(s)-Titre(s)'),
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.")
174 class PostePiece(models
.Model
):
175 """Documents relatifs au Poste
176 Ex.: Description de poste
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
)
184 class PosteCommentaire(Commentaire
):
185 poste
= models
.ForeignKey("Poste")
194 SITUATION_CHOICES
= (
195 ('C', 'Célibataire'),
200 class Employe(Metadata
):
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
)
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)
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')
229 def __unicode__(self
):
230 return u
'%s %s' % (self
.prenom
, self
.nom
.upper())
232 class EmployePiece(models
.Model
):
233 """Documents relatifs à l'employé
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
)
242 class EmployeCommentaire(Commentaire
):
243 employe
= models
.ForeignKey("Employe")
246 LIEN_PARENTE_CHOICES
= (
247 ('Conjoint', 'Conjoint'),
248 ('Conjointe', 'Conjointe'),
253 class AyantDroit(Metadata
):
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
)
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
)
270 class AyantDroitCommentaire(Commentaire
):
271 ayant_droit
= models
.ForeignKey("AyantDroit")
276 STATUT_RESIDENCE_CHOICES
= (
278 ('expat', 'Expatrié'),
281 COMPTE_COMPTA_CHOICES
= (
287 class Dossier(Metadata
):
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.",
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
)
307 classement
= models
.ForeignKey('Classement', related_name
='+',
308 null
=True, blank
=True)
309 regime_travail
= models
.DecimalField(max_digits
=12,
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,
316 default
=REGIME_TRAVAIL_NB_HEURE_SEMAINE_DEFAULT
,
317 verbose_name
="Nb. heures par semaine")
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")
331 def __unicode__(self
):
332 return u
'%s - %s' % (self
.poste
.nom
, self
.employe
)
334 class DossierPiece(models
.Model
):
335 """Documents relatifs au Dossier (à l'occupation de ce poste par employé).
336 Ex.: Lettre de motivation.
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
)
344 class DossierCommentaire(Commentaire
):
345 dossier
= models
.ForeignKey("Dossier")
350 class RemunerationMixin(Metadata
):
352 id = models
.IntegerField(primary_key
=True)
353 dossier
= models
.ForeignKey('Dossier', db_column
='dossier')
354 type = models
.ForeignKey('TypeRemuneration', db_column
='type',
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)
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.
379 def montant_mois(self
):
380 return round(self
.montant
/ 12, 2)
382 def taux_devise(self
):
383 return self
.devise
.tauxchange_set
.order_by('-annee').all()[0].taux
385 def montant_euro(self
):
386 return round(float(self
.montant
) / float(self
.taux_devise()), 2)
388 def montant_euro_mois(self
):
389 return round(self
.montant_euro() / 12, 2)
391 def __unicode__(self
):
393 devise
= self
.devise
.code
396 return "%s %s" % (self
.montant
, devise
)
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.
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
)
415 class Evenement(models
.Models
):
421 class EvenementRemuneration(RemunerationMixin
):
428 class FamilleEmploi(models
.Model
):
430 id = models
.IntegerField(primary_key
=True)
431 nom
= models
.CharField(max_length
=255)
433 actif
= models
.BooleanField()
435 class TypePoste(models
.Model
):
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')
445 date_modification
= models
.DateField(auto_now
=True)
446 actif
= models
.BooleanField()
448 def __unicode__(self
):
449 return u
'%s' % self
.nom
452 TYPE_PAIEMENT_CHOICES
= (
453 ('Régulier', 'Régulier'),
454 ('Ponctuel', 'Ponctuel'),
457 NATURE_REMUNERATION_CHOICES
= (
458 ('Accessoire', 'Accessoire'),
459 ('Charges', 'Charges'),
460 ('Indemnité', 'Indemnité'),
461 ('RAS', 'Rémunération autre source'),
462 ('Traitement', 'Traitement'),
465 class TypeRemuneration(models
.Model
):
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
)
474 actif
= models
.BooleanField()
476 def __unicode__(self
):
477 return u
'%s' % self
.nom
479 class TypeRevalorisation(models
.Model
):
480 """Justification du changement de la Remuneration.
481 (Actuellement utilisé dans aucun traitement informatique)
484 id = models
.IntegerField(primary_key
=True)
485 nom
= models
.CharField(max_length
=255)
487 actif
= models
.BooleanField()
489 class Service(models
.Model
):
491 id = models
.IntegerField(primary_key
=True)
492 nom
= models
.CharField(max_length
=255)
494 actif
= models
.BooleanField()
496 def __unicode__(self
):
497 return u
'%s' % self
.nom
503 TYPE_ORGANISME_CHOICES
= (
504 ('MAD', 'Mise à disposition'),
505 ('DET', 'Détachement'),
508 class OrganismeBstg(models
.Model
):
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
)
516 actif
= models
.BooleanField()
518 def __unicode__(self
):
519 return u
'%s (%s)' % (self
.nom
, self
.type)
522 ordering
= ['type', 'nom']
525 CONTRAT_CATEGORIE_CHOICES
= (
530 class Statut(models
.Model
):
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 ???
540 actif
= models
.BooleanField()
542 def __unicode__(self
):
543 return u
'%s : %s' % (self
.code
, self
.nom
)
546 TYPE_CLASSEMENT_CHOICES
= (
551 class Classement(models
.Model
):
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
560 commentaire
= models
.TextField(null
=True, blank
=True)
561 date_modification
= models
.DateField(auto_now
=True)
562 actif
= models
.BooleanField()
564 def __unicode__(self
):
565 return u
'%s.%s.%s (%s)' % (self
.type, self
.echelon
, self
.degre
,
568 ordering
= ['type','echelon','degre','coefficient']
570 class TauxChange(models
.Model
):
571 """Taux de change de la devise vers l'euro (EUR)
572 pour cette année budgétaire.
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()
580 class ValeurPoint(models
.Model
):
582 id = models
.IntegerField(primary_key
=True)
583 valeur
= models
.FloatField()
585 implantation
= models
.ForeignKey(ref
.Implantation
,
586 db_column
='implantation',
587 related_name
='valeurs_point')
589 annee
= models
.IntegerField()
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
)
596 def __unicode__(self
):
597 tx
= self
.get_tauxchange_courant()
599 devise_code
= tx
.devise
.code
602 return u
'%s %s (%s-%s)' % (self
.valeur
, devise_code
,
603 self
.implantation_id
, self
.annee
)
606 ordering
= ['valeur']
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)
613 def __unicode__(self
):
614 return u
'%s - %s' % (self
.code
, self
.nom
)
616 class TypeContrat(models
.Model
):
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?
624 actif
= models
.BooleanField()
626 def __unicode__(self
):
627 return u
'%s' % (self
.nom
)
632 class ResponsableImplantation(models
.Model
):
633 """Le responsable d'une implantation.
634 Anciennement géré sur le Dossier du responsable.
636 employe
= models
.ForeignKey('Employe', null
=True, blank
=True)
637 implantation
= models
.ForeignKey(ref
.Implantation
, unique
=True)
639 def __unicode__(self
):
640 return u
'%s : %s' % (self
.implantation
, self
.employe
)
643 ordering
= ['implantation__nom']