models plutôt complets
[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 = u"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 Metadata.actif = flag remplaçant la suppression.
34 actif == False : objet réputé supprimé.
35 """
36 actif = models.BooleanField(default=True)
37 date_creation = models.DateField(auto_now_add=True)
38 user_creation = models.ForeignKey('auth.User',
39 db_column='user_creation', related_name='+',
40 null=True, blank=True, default=True)
41 date_modification = models.DateField(auto_now=True)
42 user_modification = models.ForeignKey('auth.User',
43 db_column='user_modification', related_name='+',
44 null=True, blank=True, default=True)
45 date_desactivation = models.DateField(null=True, blank=True, default=True)
46 user_desactivation = models.ForeignKey('auth.User',
47 db_column='user_desactivation', related_name='+',
48 null=True, blank=True, default=True)
49
50 class Meta:
51 abstract = True
52
53 class Commentaire(Metadata):
54 texte = models.TextField()
55 owner = models.ForeignKey('auth.User', db_column='owner', related_name='+')
56
57 class Meta:
58 abstract = True
59 ordering = ['-date_creation']
60
61 def __unicode__(self):
62 return u'%s' % (self.texte)
63
64
65 ### POSTE
66
67 POSTE_APPEL_CHOICES = (
68 ('interne', 'Interne'),
69 ('externe', 'Externe'),
70 )
71
72 class Poste(Metadata):
73 """Un Poste est un emploi (job) à combler dans une implantation.
74 Un Poste peut être comblé par un Employe, auquel cas un Dossier est créé.
75 Si on veut recruter 2 jardiniers, 2 Postes distincts existent.
76 """
77 # Identification
78 nom = models.CharField(max_length=255,
79 verbose_name=u"Titre du poste", )
80 nom_feminin = models.CharField(max_length=255,
81 verbose_name=u"Titre du poste (au féminin)"
82 null=True)
83 implantation = models.ForeignKey(ref.Implantation,
84 db_column='implantation', related_name='+')
85 type_poste = models.ForeignKey('TypePoste', db_column='type_poste',
86 related_name='+',
87 null=True)
88 service = models.ForeignKey('Service', db_column='service',
89 related_name='+',
90 verbose_name=u"Direction/Service/Pôle support",
91 default=1) # default = Rectorat
92 responsable = models.ForeignKey('Poste', db_column='responsable',
93 related_name='+',
94 verbose_name=u"Poste du responsable",
95 default=149) # default = Recteur
96
97 # Contrat
98 regime_travail = models.DecimalField(max_digits=12, decimal_places=2,
99 default=REGIME_TRAVAIL_DEFAULT,
100 verbose_name=u"Temps de travail",
101 help_text=u"% du temps complet")
102 regime_travail_nb_heure_semaine = models.DecimalField(max_digits=12,
103 decimal_places=2,
104 default=REGIME_TRAVAIL_NB_HEURE_SEMAINE_DEFAULT,
105 verbose_name=u"Nb. heures par semaine")
106
107 # Recrutement
108 local = models.BooleanField(verbose_name=u"Local", default=True,
109 blank=True)
110 expatrie = models.BooleanField(verbose_name=u"Expatrié", default=False,
111 blank=True)
112 mise_a_disposition = models.BooleanField(
113 verbose_name=u"Mise à disposition",
114 default=False)
115 appel = models.CharField(max_length=10,
116 verbose_name=u"Appel à candidature",
117 choices=POSTE_APPEL_CHOICES,
118 default='interne')
119
120 # Rémunération
121 classement_min = models.ForeignKey('Classement',
122 db_column='classement_min', related_name='+',
123 null=True, blank=True)
124 classement_max = models.ForeignKey('Classement',
125 db_column='classement_max', related_name='+',
126 null=True, blank=True)
127 valeur_point_min = models.ForeignKey('ValeurPoint',
128 db_column='valeur_point_min', related_name='+',
129 null=True, blank=True)
130 valeur_point_max = models.ForeignKey('ValeurPoint',
131 db_column='valeur_point_max', related_name='+',
132 null=True, blank=True)
133 devise_min = models.ForeignKey('Devise', db_column='devise_min',
134 related_name='+', default=5)
135 devise_max = models.ForeignKey('Devise', db_column='devise_max',
136 related_name='+', default=5)
137 salaire_min = models.DecimalField(max_digits=12, decimal_places=2,
138 default=0)
139 salaire_max = models.DecimalField(max_digits=12, decimal_places=2,
140 default=0)
141 indemn_min = models.DecimalField(max_digits=12, decimal_places=2,
142 default=0)
143 indemn_max = models.DecimalField(max_digits=12, decimal_places=2,
144 default=0)
145 autre_min = models.DecimalField(max_digits=12, decimal_places=2,
146 default=0)
147 autre_max = models.DecimalField(max_digits=12, decimal_places=2,
148 default=0)
149
150 # Comparatifs de rémunération
151 devise_comparaison = models.ForeignKey('Devise',
152 db_column='devise_comparaison',
153 related_name='+',
154 default=5)
155 comp_locale_min = models.DecimalField(max_digits=12, decimal_places=2,
156 null=True, blank=True)
157 comp_locale_max = models.DecimalField(max_digits=12, decimal_places=2,
158 null=True, blank=True)
159 comp_universite_min = models.DecimalField(max_digits=12, decimal_places=2,
160 null=True, blank=True)
161 comp_universite_max = models.DecimalField(max_digits=12, decimal_places=2,
162 null=True, blank=True)
163 comp_fonctionpub_min = models.DecimalField(max_digits=12, decimal_places=2,
164 null=True, blank=True)
165 comp_fonctionpub_max = models.DecimalField(max_digits=12, decimal_places=2,
166 null=True, blank=True)
167 comp_ong_min = models.DecimalField(max_digits=12, decimal_places=2,
168 null=True, blank=True)
169 comp_ong_max = models.DecimalField(max_digits=12, decimal_places=2,
170 null=True, blank=True)
171 comp_autre_min = models.DecimalField(max_digits=12, decimal_places=2,
172 null=True, blank=True)
173 comp_autre_max = models.DecimalField(max_digits=12, decimal_places=2,
174 null=True, blank=True)
175
176 # Justification
177 justification = models.TextField(null=True, blank=True)
178
179 # Autres Metadata
180 date_validation = models.DateTimeField(null=True, blank=True) # de dae
181 date_debut = models.DateField(verbose_name=u"Date de début",
182 help_text=HELP_TEXT_DATE)
183 date_fin = models.DateField(verbose_name=u"Date de fin",
184 help_text=HELP_TEXT_DATE,
185 null=True, blank=True)
186
187 class Meta:
188 ordering = ['implantation__nom', 'nom']
189
190 def __unicode__(self):
191 # TODO : gérer si poste est vacant ou non dans affichage
192 # TODO : gérer le nom_feminin (autre méthode appelée par __unicode__ ?)
193 return u'%s - %s [%s]' % (self.implantation, self.nom, self.id)
194
195
196 POSTE_FINANCEMENT_CHOICES = (
197 ('A', 'A - Frais de personnel'),
198 ('B', 'B - Projet(s)-Titre(s)'),
199 ('C', 'C - Autre')
200 )
201
202 class PosteFinancement(models.Model):
203 """Pour un Poste, structure d'informations décrivant comment on prévoit
204 financer ce Poste.
205 """
206 poste = models.ForeignKey('Poste', db_column='poste',
207 related_name='financements')
208 type = models.CharField(max_length=1, choices=POSTE_FINANCEMENT_CHOICES)
209 pourcentage = models.DecimalField(max_digits=12, decimal_places=2,
210 help_text=u"ex.: 33.33 % (décimale avec point)")
211 commentaire = models.TextField(
212 help_text=u"Spécifiez la source de financement.")
213
214 class Meta:
215 ordering = ['type']
216
217 def __unicode__(self):
218 return u'%s : %s %' % (self.type, self.pourcentage)
219
220 class PostePiece(models.Model):
221 """Documents relatifs au Poste.
222 Ex.: Description de poste
223 """
224 poste = models.ForeignKey("Poste", db_column='poste',
225 related_name='pieces')
226 nom = models.CharField(verbose_name=u"Nom", max_length=255)
227 fichier = models.FileField(verbose_name=u"Fichier",
228 upload_to=poste_piece_dispatch,
229 storage=storage_prive)
230
231 class Meta:
232 ordering = ['nom']
233
234 def __unicode__(self):
235 return u'%s' % (self.nom)
236
237 class PosteCommentaire(Commentaire):
238 poste = models.ForeignKey("Poste", db_column='poste', related_name='+')
239
240
241 ### EMPLOYÉ/PERSONNE
242
243 GENRE_CHOICES = (
244 ('M', 'Homme'),
245 ('F', 'Femme'),
246 )
247 SITUATION_CHOICES = (
248 ('C', 'Célibataire'),
249 ('F', 'Fiancé'),
250 ('M', 'Marié'),
251 )
252
253 class Employe(Metadata):
254 """Personne occupant ou ayant occupé un Poste. Un Employe aura autant de
255 Dossiers qu'il occupe ou a occupé de Postes.
256
257 Cette classe aurait pu avantageusement s'appeler Personne car la notion
258 d'employé n'a pas de sens si aucun Dossier n'existe pour une personne.
259 """
260 # Identification
261 nom = models.CharField(max_length=255)
262 prenom = models.CharField(max_length=255, verbose_name=u"Prénom")
263 # TODO : nom_affichage doit être obligatoire, pas nom et prenom
264 nom_affichage = models.CharField(max_length=255,
265 verbose_name=u"Nom d'affichage",
266 null=True, blank=True)
267 nationalite = models.ForeignKey(ref.Pays, to_field='code',
268 db_column='nationalite',
269 related_name='employes_nationalite')
270 date_naissance = models.DateField(help_text=HELP_TEXT_DATE,
271 null=True, blank=True)
272 genre = models.CharField(max_length=1, choices=GENRE_CHOICES)
273
274 # Infos personnelles
275 situation_famille = models.CharField(max_length=1,
276 choices=SITUATION_CHOICES,
277 null=True, blank=True)
278 date_entree = models.DateField(verbose_name=u"Date d'entrée à l'AUF",
279 help_text=HELP_TEXT_DATE,
280 null=True, blank=True)
281
282 # Coordonnées
283 tel_domicile = models.CharField(max_length=255, null=True, blank=True)
284 tel_cellulaire = models.CharField(max_length=255, null=True, blank=True)
285 adresse = models.CharField(max_length=255, null=True, blank=True)
286 ville = models.CharField(max_length=255, null=True, blank=True)
287 province = models.CharField(max_length=255, null=True, blank=True)
288 code_postal = models.CharField(max_length=255, null=True, blank=True)
289 pays = models.ForeignKey(ref.Pays, to_field='code', db_column='pays',
290 related_name='employes',
291 null=True, blank=True)
292
293 class Meta:
294 ordering = ['nom_affichage']
295
296 def __unicode__(self):
297 # TODO : gérer nom d'affichage
298 return u'%s' % (self.nom_affichage)
299
300 class EmployePiece(models.Model):
301 """Documents relatifs à un employé.
302 Ex.: CV...
303 """
304 employe = models.ForeignKey("Employe", db_column='employe',
305 related_name='+')
306 nom = models.CharField(verbose_name=u"Nom", max_length=255)
307 fichier = models.FileField(verbose_name=u"Fichier",
308 upload_to=dossier_piece_dispatch,
309 storage=storage_prive)
310
311 class Meta:
312 ordering = ['nom']
313
314 def __unicode__(self):
315 return u'%s' % (self.nom)
316
317 class EmployeCommentaire(Commentaire):
318 employe = models.ForeignKey("Employe", db_column='employe',
319 related_name='+')
320
321
322 LIEN_PARENTE_CHOICES = (
323 ('Conjoint', 'Conjoint'),
324 ('Conjointe', 'Conjointe'),
325 ('Fille', 'Fille'),
326 ('Fils', 'Fils'),
327 )
328
329 class AyantDroit(Metadata):
330 """Personne en relation avec un Employe.
331 """
332 # Identification
333 nom = models.CharField(max_length=255)
334 prenom = models.CharField(max_length=255)
335 # TODO : nom_affichage doit être obligatoire, pas nom et prenom
336 nom_affichage = models.CharField(max_length=255,
337 verbose_name=u"Nom d'affichage",
338 null=True, blank=True)
339 nationalite = models.ForeignKey(ref.Pays, to_field='code',
340 db_column='nationalite',
341 related_name='ayantdroits_nationalite')
342 date_naissance = models.DateField(help_text=HELP_TEXT_DATE,
343 null=True, blank=True)
344 genre = models.CharField(max_length=1, choices=GENRE_CHOICES)
345
346 # Relation
347 employe = models.ForeignKey('Employe', db_column='employe',
348 related_name='ayantdroits')
349 lien_parente = models.CharField(max_length=10,
350 choices=LIEN_PARENTE_CHOICES,
351 null=True, blank=True)
352
353 class Meta:
354 ordering = ['nom_affichage']
355 def __unicode__(self):
356 # TODO : gérer nom d'affichage
357 return u'%s %s' % (self.prenom, self.nom.upper())
358
359 class AyantDroitCommentaire(Commentaire):
360 ayant_droit = models.ForeignKey("AyantDroit", db_column='ayant_droit',
361 related_name='+')
362
363
364 ### DOSSIER
365
366 STATUT_RESIDENCE_CHOICES = (
367 ('local', 'Local'),
368 ('expat', 'Expatrié'),
369 )
370
371 COMPTE_COMPTA_CHOICES = (
372 ('coda', 'CODA'),
373 ('scs', 'SCS'),
374 ('aucun', 'Aucun'),
375 )
376
377 class Dossier(Metadata):
378 """Le Dossier regroupe les informations relatives à l'occupation
379 d'un Poste par un Employe. Un seul Dossier existe par Poste occupé
380 par un Employe.
381
382 Plusieurs Contrats peuvent être associés au Dossier.
383 Une structure de Remuneration est rattachée au Dossier. Un Poste pour
384 lequel aucun Dossier n'existe est un poste vacant.
385 """
386 # Identification
387 employe = models.ForeignKey('Employe', db_column='employe',
388 related_name='+')
389 poste = models.ForeignKey('Poste', db_column='poste',
390 related_name='+', editable=False)
391 statut = models.ForeignKey('Statut', related_name='+', default=3)
392 organisme_bstg = models.ForeignKey('OrganismeBstg',
393 db_column='organisme_bstg',
394 related_name='+',
395 verbose_name=u"Organisme",
396 help_text=u"Si détaché (DET) ou \
397 mis à disposition (MAD), \
398 préciser l'organisme.",
399 null=True, blank=True)
400
401 # Recrutement
402 remplacement = models.BooleanField(default=False)
403 statut_residence = models.CharField(max_length=10, default='local',
404 verbose_name=u"Statut",
405 choices=STATUT_RESIDENCE_CHOICES)
406
407 # Rémunération
408 classement = models.ForeignKey('Classement', db_column='classement',
409 related_name='+',
410 null=True, blank=True)
411 regime_travail = models.DecimalField(max_digits=12,
412 decimal_places=2,
413 default=REGIME_TRAVAIL_DEFAULT,
414 verbose_name=u"Régime de travail",
415 help_text=u"% du temps complet")
416 regime_travail_nb_heure_semaine = models.DecimalField(max_digits=12,
417 decimal_places=2,
418 default=REGIME_TRAVAIL_NB_HEURE_SEMAINE_DEFAULT,
419 verbose_name=u"Nb. heures par semaine")
420
421 # Occupation du Poste par cet Employe (anciennement "mandat")
422 date_debut = models.DateField(verbose_name=u"Date de début d'occupation \
423 de poste",
424 help_text=HELP_TEXT_DATE)
425 date_fin = models.DateField(verbose_name=u"Date de fin d'occupation \
426 de poste",
427 help_text=HELP_TEXT_DATE,
428 null=True, blank=True)
429
430 # Comptes
431 # TODO?
432
433 class Meta:
434 ordering = ['poste__nom', 'employe__nom_affichage']
435
436 def __unicode__(self):
437 return u'%s - %s' % (self.poste.nom, self.employe)
438
439 class DossierPiece(models.Model):
440 """Documents relatifs au Dossier (à l'occupation de ce poste par employé).
441 Ex.: Lettre de motivation.
442 """
443 dossier = models.ForeignKey("Dossier", db_column='dossier',
444 related_name='+')
445 nom = models.CharField(verbose_name=u"Nom", max_length=255)
446 fichier = models.FileField(verbose_name=u"Fichier",
447 upload_to=dossier_piece_dispatch,
448 storage=storage_prive)
449
450 class Meta:
451 ordering = ['nom']
452
453 def __unicode__(self):
454 return u'%s' % (self.nom)
455
456 class DossierCommentaire(Commentaire):
457 dossier = models.ForeignKey("Dossier", db_column='dossier',
458 related_name='+')
459
460
461 ### RÉMUNÉRATION
462
463 class RemunerationMixin(Metadata):
464 # Identification
465 dossier = models.ForeignKey('Dossier', db_column='dossier')
466 type = models.ForeignKey('TypeRemuneration', db_column='type',
467 related_name='+')
468 type_revalorisation = models.ForeignKey('TypeRevalorisation',
469 db_column='type_revalorisation',
470 related_name='+',
471 null=True, blank=True)
472 montant = models.FloatField(max_digits=12, decimal_places=2,
473 null=True, blank=True,
474 default=0)
475 # Annuel (12 mois, 52 semaines, 364 jours?)
476 devise = models.ForeignKey('Devise', to_field='code',
477 db_column='devise', related_name='+',
478 default=5)
479 # commentaire = precision
480 commentaire = models.CharField(max_length=255, null=True, blank=True)
481 # date_debut = anciennement date_effectif
482 date_debut = models.DateField(help_text=HELP_TEXT_DATE,
483 null=True, blank=True)
484 date_fin = models.DateField(help_text=HELP_TEXT_DATE,
485 null=True, blank=True)
486
487 class Meta:
488 abstract = True
489 ordering = ['type__nom', '-date_fin']
490
491 def __unicode__(self):
492 return u'%s %s (%s)' % (self.montant, self.devise.code, self.type.nom)
493
494 class Remuneration(RemunerationMixin):
495 """Structure de rémunération (données budgétaires) en situation normale
496 pour un Dossier. Si un Evenement existe, utiliser la structure de
497 rémunération EvenementRemuneration de cet événement.
498 """
499
500 def montant_mois(self):
501 return round(self.montant / 12, 2)
502
503 def taux_devise(self):
504 return self.devise.tauxchange_set.order_by('-annee').all()[0].taux
505
506 def montant_euro(self):
507 return round(float(self.montant) / float(self.taux_devise()), 2)
508
509 def montant_euro_mois(self):
510 return round(self.montant_euro() / 12, 2)
511
512 def __unicode__(self):
513 try:
514 devise = self.devise.code
515 except:
516 devise = "???"
517 return "%s %s" % (self.montant, devise)
518
519
520 ### CONTRATS
521
522 class Contrat(Metadata):
523 """Document juridique qui encadre la relation de travail d'un Employe
524 pour un Poste particulier. Pour un Dossier (qui documente cette
525 relation de travail) plusieurs contrats peuvent être associés.
526 """
527 dossier = models.ForeignKey('Dossier', db_column='dossier',
528 related_name='+')
529 type_contrat = models.ForeignKey('TypeContrat', db_column='type_contrat',
530 related_name='+')
531 date_debut = models.DateField(help_text=HELP_TEXT_DATE)
532 date_fin = models.DateField(help_text=HELP_TEXT_DATE,
533 null=True, blank=True)
534
535 class Meta:
536 ordering = ['dossier__employe__nom_affichage']
537
538 def __unicode__(self):
539 return u'%s - %s' % (self.dossier.employe.nom_affichage, self.id)
540
541 # TODO? class ContratPiece(models.Model):
542
543
544 ### ÉVÉNEMENTS
545
546 class Evenement(Metadata):
547 """Un Evenement sert à déclarer une situation temporaire (exceptionnelle)
548 d'un Dossier qui vient altérer des informations normales liées à un Dossier
549 (ex.: la Remuneration).
550
551 Ex.: congé de maternité, maladie...
552
553 Lors de ces situations exceptionnelles, l'Employe a un régime de travail
554 différent et une rémunération en conséquence. On souhaite toutefois
555 conserver le Dossier intact afin d'éviter une re-saisie des données lors
556 du retour à la normale.
557 """
558 dossier = models.ForeignKey("Dossier", db_column='dossier',
559 related_name='+')
560 nom = models.CharField(max_length=255)
561 date_debut = models.DateField(help_text=HELP_TEXT_DATE)
562 date_fin = models.DateField(help_text=HELP_TEXT_DATE,
563 null=True, blank=True)
564 class Meta:
565 ordering = ['nom']
566
567 def __unicode__(self):
568 return u'%s' % (self.nom)
569
570 class EvenementRemuneration(RemunerationMixin):
571 """Structure de rémunération liée à un Evenement qui remplace
572 temporairement la Remuneration normale d'un Dossier, pour toute la durée
573 de l'Evenement.
574 """
575 evenement = models.ForeignKey("Evenement", db_column='evenement',
576 related_name='+')
577
578
579 ### RÉFÉRENCES RH
580
581 class FamilleEmploi(Metadata):
582 """Catégorie utilisée dans la gestion des Postes.
583 Catégorie supérieure à TypePoste.
584 """
585 nom = models.CharField(max_length=255)
586
587 def __unicode__(self):
588 return u'%s' % (self.nom)
589
590 class TypePoste(Metadata):
591 """Catégorie de Poste.
592 """
593 nom = models.CharField(max_length=255)
594 nom_feminin = models.CharField(max_length=255)
595
596 is_responsable = models.BooleanField(default=False)
597 famille_emploi = models.ForeignKey('FamilleEmploi',
598 db_column='famille_emploi',
599 related_name='+')
600
601 class Meta:
602 ordering = ['nom']
603
604 def __unicode__(self):
605 # TODO : gérer nom féminin
606 return u'%s' % (self.nom)
607
608
609 TYPE_PAIEMENT_CHOICES = (
610 ('Régulier', 'Régulier'),
611 ('Ponctuel', 'Ponctuel'),
612 )
613
614 NATURE_REMUNERATION_CHOICES = (
615 ('Accessoire', 'Accessoire'),
616 ('Charges', 'Charges'),
617 ('Indemnité', 'Indemnité'),
618 ('RAS', 'Rémunération autre source'),
619 ('Traitement', 'Traitement'),
620 )
621
622 class TypeRemuneration(Metadata):
623 """Catégorie de Remuneration.
624 """
625 nom = models.CharField(max_length=255)
626 type_paiement = models.CharField(max_length=30,
627 choices=TYPE_PAIEMENT_CHOICES)
628 nature_remuneration = models.CharField(max_length=30,
629 choices=NATURE_REMUNERATION_CHOICES)
630
631 def __unicode__(self):
632 return u'%s' % (self.nom)
633
634 class TypeRevalorisation(Metadata):
635 """Justification du changement de la Remuneration.
636 (Actuellement utilisé dans aucun traitement informatique.)
637 """
638 nom = models.CharField(max_length=255)
639
640 def __unicode__(self):
641 return u'%s' % (self.nom)
642
643 class Service(Metadata):
644 """Unité administrative où les Postes sont rattachés.
645 """
646 nom = models.CharField(max_length=255)
647
648 class Meta:
649 ordering = ['nom']
650
651 def __unicode__(self):
652 return u'%s' % (self.nom)
653
654
655 TYPE_ORGANISME_CHOICES = (
656 ('MAD', 'Mise à disposition'),
657 ('DET', 'Détachement'),
658 )
659
660 class OrganismeBstg(Metadata):
661 """Organisation d'où provient un Employe mis à disposition (MAD) de
662 ou détaché (DET) à l'AUF à titre gratuit.
663
664 (BSTG = bien et service à titre gratuit.)
665 """
666 nom = models.CharField(max_length=255)
667 type = models.CharField(max_length=10, choices=TYPE_ORGANISME_CHOICES)
668 pays = models.ForeignKey(ref.Pays, to_field='code',
669 db_column='pays',
670 related_name='organismes_bstg',
671 null=True, blank=True)
672
673 class Meta:
674 ordering = ['type', 'nom']
675
676 def __unicode__(self):
677 return u'%s (%s)' % (self.nom, self.type)
678
679 class Statut(Metadata):
680 """Statut de l'Employe dans le cadre d'un Dossier particulier.
681 """
682 # Identification
683 code = models.CharField(max_length=25, unique=True)
684 nom = models.CharField(max_length=255)
685
686 class Meta:
687 ordering = ['code']
688
689 def __unicode__(self):
690 return u'%s : %s' % (self.code, self.nom)
691
692
693 TYPE_CLASSEMENT_CHOICES = (
694 ('S', 'S -Soutien'),
695 ('T', 'T - Technicien'),
696 ('P', 'P - Professionel'),
697 ('C', 'C - Cadre'),
698 ('D', 'D - Direction'),
699 ('SO', 'SO - Sans objet [expatriés]'),
700 ('HG', 'HG - Hors grille [direction]'),
701 )
702
703 class Classement(Metadata):
704 """Éléments de classement de la
705 "Grille générique de classement hiérarchique".
706
707 Utile pour connaître, pour un Dossier, le salaire de base théorique lié au
708 classement dans la grille. Le classement donne le coefficient utilisé dans:
709
710 salaire de base = coefficient * valeur du point de l'Implantation du Poste
711 """
712 # Identification
713 type = models.CharField(max_length=10, choices=TYPE_CLASSEMENT_CHOICES)
714 echelon = models.IntegerField()
715 degre = models.IntegerField()
716 coefficient = models.FloatField(default=0)
717 # Méta
718 # annee # au lieu de date_debut et date_fin
719 commentaire = models.TextField(null=True, blank=True)
720
721 class Meta:
722 ordering = ['type','echelon','degre','coefficient']
723
724 def __unicode__(self):
725 return u'%s.%s.%s (%s)' % (self.type, self.echelon, self.degre,
726 self.coefficient)
727
728 class TauxChange(Metadata):
729 """Taux de change de la devise vers l'euro (EUR)
730 pour chaque année budgétaire.
731 """
732 # Identification
733 devise = models.ForeignKey('Devise', to_field='code', db_column='devise',
734 related_name='+')
735 annee = models.IntegerField()
736 taux = models.FloatField()
737
738 class Meta:
739 ordering = ['annee', 'devise__code']
740
741 def __unicode__(self):
742 return u'%s : %s €' % (self.devise.code, self.taux)
743
744 class ValeurPoint(Metadata):
745 """Utile pour connaître, pour un Dossier, le salaire de base théorique lié
746 au classement dans la grille. La ValeurPoint s'obtient par l'implantation
747 du POste de ce Dossier : dossier.poste.implantation (pseudo code).
748
749 salaire de base = coefficient * valeur du point de l'Implantation du Poste
750 """
751 valeur = models.FloatField()
752 devise = models.ForeignKey('Devise', db_column='devise',
753 related_name='+', default=5)
754 implantation = models.ForeignKey(ref.Implantation,
755 db_column='implantation',
756 related_name='valeur_point')
757 # Méta
758 annee = models.IntegerField()
759
760 # Stockage de tous les taux de change
761 # pour optimiser la recherche de la devise associée
762 annee_courante = datetime.datetime.now().year
763 tauxchange = TauxChange.objects.select_related('devise') \
764 .filter(annee=annee_courante)
765
766 class Meta:
767 ordering = ['annee', 'implantation__nom']
768
769 def __unicode__(self):
770 tx = self.get_tauxchange_courant()
771 if tx:
772 devise_code = tx.devise.code
773 else:
774 devise_code = "??"
775 return u'%s %s (%s-%s)' % (self.valeur, devise_code,
776 self.implantation_id, self.annee)
777
778 class Meta:
779 ordering = ['valeur']
780
781 class Devise(Metadata):
782 """Devise monétaire.
783 """
784 code = models.CharField(max_length=10, unique=True)
785 nom = models.CharField(max_length=255)
786
787 class Meta:
788 ordering = ['code']
789
790 def __unicode__(self):
791 return u'%s - %s' % (self.code, self.nom)
792
793 class TypeContrat(Metadata):
794 """Type de contrat.
795 """
796 nom = models.CharField(max_length=255)
797 nom_long = models.CharField(max_length=255)
798
799 def __unicode__(self):
800 return u'%s' % (self.nom)
801
802
803 ### AUTRES
804
805 class ResponsableImplantation(Metadata):
806 """Le responsable d'une implantation.
807 Anciennement géré sur le Dossier du responsable.
808 """
809 employe = models.ForeignKey('Employe', db_column='employe',
810 related_name='+',
811 null=True, blank=True)
812 implantation = models.ForeignKey(ref.Implantation,
813 db_column='implantation', related_name='+',
814 unique=True)
815
816 def __unicode__(self):
817 return u'%s : %s' % (self.implantation, self.employe)
818
819 class Meta:
820 ordering = ['implantation__nom']