employe apercu
[auf_rh_dae.git] / project / rh / models.py
1 # -=- encoding: utf-8 -=-
2
3 import datetime
4 from datetime import date
5 from decimal import Decimal
6
7 from django.core.files.storage import FileSystemStorage
8 from django.db import models
9 from django.db.models import Q
10 from django.conf import settings
11
12 from auf.django.emploi.models import GENRE_CHOICES, SITUATION_CHOICES # devrait plutot être dans references
13 from auf.django.metadata.models import AUFMetadata
14 from auf.django.metadata.managers import NoDeleteManager
15 import auf.django.references.models as ref
16 from validators import validate_date_passee
17 from managers import PosteManager, DossierManager, DossierComparaisonManager, \
18 PosteComparaisonManager, DeviseManager, ServiceManager, TypeRemunerationManager
19 from change_list import RechercheTemporelle, KEY_STATUT, STATUT_ACTIF, \
20 STATUT_INACTIF, STATUT_FUTUR
21
22
23 # Gruick hack pour déterminer d'ou provient l'instanciation d'une classe pour l'héritage.
24 # Cela permet de faire du dynamic loading par app sans avoir à redéfinir dans DAE la FK
25 def app_context():
26 import inspect;
27 models_stack = [s[1].split('/')[-2] for s in inspect.stack() if s[1].endswith('models.py')]
28 return models_stack[-1]
29
30
31 # Constantes
32 HELP_TEXT_DATE = "format: jj-mm-aaaa"
33 REGIME_TRAVAIL_DEFAULT = Decimal('100.00')
34 REGIME_TRAVAIL_NB_HEURE_SEMAINE_DEFAULT = Decimal('35.00')
35 REGIME_TRAVAIL_NB_HEURE_SEMAINE_HELP_TEXT = "Saisir le nombre d'heure de travail à temps complet (100%), sans tenir compte du régime de travail"
36
37 # Upload de fichiers
38 storage_prive = FileSystemStorage(settings.PRIVE_MEDIA_ROOT,
39 base_url=settings.PRIVE_MEDIA_URL)
40
41 def poste_piece_dispatch(instance, filename):
42 path = "%s/poste/%s/%s" % (instance._meta.app_label, instance.poste_id, filename)
43 return path
44
45 def dossier_piece_dispatch(instance, filename):
46 path = "%s/dossier/%s/%s" % (instance._meta.app_label, instance.dossier_id, filename)
47 return path
48
49 def employe_piece_dispatch(instance, filename):
50 path = "%s/employe/%s/%s" % (instance._meta.app_label, instance.employe_id, filename)
51 return path
52
53 def contrat_dispatch(instance, filename):
54 path = "%s/contrat/%s/%s" % (instance._meta.app_label, instance.dossier_id, filename)
55 return path
56
57
58 class DevisableMixin(object):
59
60 def get_annee_pour_taux_devise(self):
61 return datetime.datetime.now().year
62
63
64 def taux_devise(self, devise=None):
65 if devise is None:
66 devise = self.devise
67
68 if devise is None:
69 return None
70 if devise.code == "EUR":
71 return 1
72
73 annee = self.get_annee_pour_taux_devise()
74 taux = [tc.taux for tc in TauxChange.objects.filter(devise=devise, annee=annee)]
75 taux = set(taux)
76
77 if len(taux) == 0:
78 raise Exception(u"Pas de taux pour %s en %s" % (devise.code, annee))
79
80 if len(taux) > 1:
81 raise Exception(u"Il existe plusieurs taux de %s en %s" %
82 (devise.code, annee))
83 else:
84 return list(taux)[0]
85
86 def montant_euros(self):
87 try:
88 taux = self.taux_devise()
89 except Exception, e:
90 return e
91 if not taux:
92 return None
93 return int(round(float(self.montant) * float(taux), 2))
94
95
96 class Commentaire(AUFMetadata):
97 texte = models.TextField()
98 owner = models.ForeignKey('auth.User', db_column='owner', related_name='+', verbose_name=u"Commentaire de")
99
100 class Meta:
101 abstract = True
102 ordering = ['-date_creation']
103
104 def __unicode__(self):
105 return u'%s' % (self.texte)
106
107
108 ### POSTE
109
110 POSTE_APPEL_CHOICES = (
111 ('interne', 'Interne'),
112 ('externe', 'Externe'),
113 )
114
115 class Poste_(AUFMetadata):
116 """Un Poste est un emploi (job) à combler dans une implantation.
117 Un Poste peut être comblé par un Employe, auquel cas un Dossier est créé.
118 Si on veut recruter 2 jardiniers, 2 Postes distincts existent.
119 """
120
121 objects = PosteManager()
122
123 # Identification
124 nom = models.CharField(max_length=255,
125 verbose_name = u"Titre du poste", )
126 nom_feminin = models.CharField(max_length=255,
127 verbose_name = u"Titre du poste (au féminin)",
128 null=True)
129 implantation = models.ForeignKey(ref.Implantation, help_text=u"Taper le nom de l'implantation ou sa région",
130 db_column='implantation', related_name='+')
131 type_poste = models.ForeignKey('TypePoste', db_column='type_poste', help_text=u"Taper le nom du type de poste",
132 related_name='+',
133 null=True,
134 verbose_name=u"type de poste")
135 service = models.ForeignKey('Service', db_column='service',
136 related_name='+',
137 verbose_name = u"direction/service/pôle support",
138 null=True,)
139 responsable = models.ForeignKey('Poste', db_column='responsable',
140 related_name='+',
141 null=True,
142 help_text=u"Taper le nom du poste ou du type de poste",
143 verbose_name = u"Poste du responsable", )
144
145 # Contrat
146 regime_travail = models.DecimalField(max_digits=12, decimal_places=2,
147 default=REGIME_TRAVAIL_DEFAULT, null=True,
148 verbose_name = u"Temps de travail",
149 help_text="% du temps complet")
150 regime_travail_nb_heure_semaine = models.DecimalField(max_digits=12,
151 decimal_places=2, null=True,
152 default=REGIME_TRAVAIL_NB_HEURE_SEMAINE_DEFAULT,
153 verbose_name= u"Nb. heures par semaine",
154 help_text=REGIME_TRAVAIL_NB_HEURE_SEMAINE_HELP_TEXT)
155
156 # Recrutement
157 local = models.NullBooleanField(verbose_name = u"Local", default=True,
158 null=True, blank=True)
159 expatrie = models.NullBooleanField(verbose_name = u"Expatrié", default=False,
160 null=True, blank=True)
161 mise_a_disposition = models.NullBooleanField(
162 verbose_name = u"Mise à disposition",
163 null=True, default=False)
164 appel = models.CharField(max_length=10, null=True,
165 verbose_name = u"Appel à candidature",
166 choices=POSTE_APPEL_CHOICES,
167 default='interne')
168
169 # Rémunération
170 classement_min = models.ForeignKey('Classement',
171 db_column='classement_min', related_name='+',
172 null=True, blank=True)
173 classement_max = models.ForeignKey('Classement',
174 db_column='classement_max', related_name='+',
175 null=True, blank=True)
176 valeur_point_min = models.ForeignKey('ValeurPoint', help_text=u"Taper le code ou le nom de l'implantation",
177 db_column='valeur_point_min', related_name='+',
178 null=True, blank=True)
179 valeur_point_max = models.ForeignKey('ValeurPoint', help_text=u"Taper le code ou le nom de l'implantation",
180 db_column='valeur_point_max', related_name='+',
181 null=True, blank=True)
182 devise_min = models.ForeignKey('Devise', db_column='devise_min', null=True,
183 related_name='+',)
184 devise_max = models.ForeignKey('Devise', db_column='devise_max', null=True,
185 related_name='+',)
186 salaire_min = models.DecimalField(max_digits=12, decimal_places=2,
187 null=True, default=0)
188 salaire_max = models.DecimalField(max_digits=12, decimal_places=2,
189 null=True, default=0)
190 indemn_min = models.DecimalField(max_digits=12, decimal_places=2,
191 null=True, default=0)
192 indemn_max = models.DecimalField(max_digits=12, decimal_places=2,
193 null=True, default=0)
194 autre_min = models.DecimalField(max_digits=12, decimal_places=2,
195 null=True, default=0)
196 autre_max = models.DecimalField(max_digits=12, decimal_places=2,
197 null=True, default=0)
198
199 # Comparatifs de rémunération
200 devise_comparaison = models.ForeignKey('Devise', null=True, blank=True,
201 db_column='devise_comparaison',
202 related_name='+', )
203 comp_locale_min = models.DecimalField(max_digits=12, decimal_places=2,
204 null=True, blank=True)
205 comp_locale_max = models.DecimalField(max_digits=12, decimal_places=2,
206 null=True, blank=True)
207 comp_universite_min = models.DecimalField(max_digits=12, decimal_places=2,
208 null=True, blank=True)
209 comp_universite_max = models.DecimalField(max_digits=12, decimal_places=2,
210 null=True, blank=True)
211 comp_fonctionpub_min = models.DecimalField(max_digits=12, decimal_places=2,
212 null=True, blank=True)
213 comp_fonctionpub_max = models.DecimalField(max_digits=12, decimal_places=2,
214 null=True, blank=True)
215 comp_ong_min = models.DecimalField(max_digits=12, decimal_places=2,
216 null=True, blank=True)
217 comp_ong_max = models.DecimalField(max_digits=12, decimal_places=2,
218 null=True, blank=True)
219 comp_autre_min = models.DecimalField(max_digits=12, decimal_places=2,
220 null=True, blank=True)
221 comp_autre_max = models.DecimalField(max_digits=12, decimal_places=2,
222 null=True, blank=True)
223
224 # Justification
225 justification = models.TextField(null=True, blank=True)
226
227 # Autres Metadata
228 date_debut = models.DateField(verbose_name=u"Date de début", help_text=HELP_TEXT_DATE,
229 null=True, blank=True)
230 date_fin = models.DateField(verbose_name=u"Date de fin", help_text=HELP_TEXT_DATE,
231 null=True, blank=True)
232
233 class Meta:
234 abstract = True
235 ordering = ['implantation__nom', 'nom']
236 verbose_name = u"Poste"
237 verbose_name_plural = u"Postes"
238 ordering = ["nom"]
239
240 def __unicode__(self):
241 representation = u'%s - %s [%s]' % (self.implantation, self.nom,
242 self.id)
243 return representation
244
245
246 prefix_implantation = "implantation__region"
247 def get_regions(self):
248 return [self.implantation.region]
249
250 def get_devise(self):
251 return ValeurPoint.objects.filter(implantation=self.implantation, devise__archive=False).order_by('annee')[0].devise
252
253 class Poste(Poste_):
254 __doc__ = Poste_.__doc__
255
256 # meta dématérialisation : pour permettre le filtrage
257 vacant = models.NullBooleanField(verbose_name = u"vacant", null=True, blank=True)
258
259 def is_vacant(self):
260 vacant = True
261 if self.occupe_par():
262 vacant = False
263 return vacant
264
265 def occupe_par(self):
266 """Retourne la liste d'employé occupant ce poste.
267 Généralement, retourne une liste d'un élément.
268 Si poste inoccupé, retourne liste vide.
269 UTILISE pour mettre a jour le flag vacant
270 """
271 return [d.employe for d in self.rh_dossiers.filter(supprime=False).exclude(date_fin__lt=date.today())]
272
273
274 POSTE_FINANCEMENT_CHOICES = (
275 ('A', 'A - Frais de personnel'),
276 ('B', 'B - Projet(s)-Titre(s)'),
277 ('C', 'C - Autre')
278 )
279
280
281 class PosteFinancement_(models.Model):
282 """Pour un Poste, structure d'informations décrivant comment on prévoit
283 financer ce Poste.
284 """
285 poste = models.ForeignKey('%s.Poste' % app_context(), db_column='poste', related_name='%(app_label)s_financements')
286 type = models.CharField(max_length=1, choices=POSTE_FINANCEMENT_CHOICES)
287 pourcentage = models.DecimalField(max_digits=12, decimal_places=2,
288 help_text="ex.: 33.33 % (décimale avec point)")
289 commentaire = models.TextField(
290 help_text="Spécifiez la source de financement.")
291
292 class Meta:
293 abstract = True
294 ordering = ['type']
295
296 def __unicode__(self):
297 return u'%s : %s %%' % (self.type, self.pourcentage)
298
299 def choix(self):
300 return u"%s" % dict(POSTE_FINANCEMENT_CHOICES)[self.type]
301
302
303 class PosteFinancement(PosteFinancement_):
304 pass
305
306
307 class PostePiece_(models.Model):
308 """Documents relatifs au Poste.
309 Ex.: Description de poste
310 """
311 poste = models.ForeignKey('%s.Poste' % app_context(), db_column='poste', related_name='%(app_label)s_pieces')
312 nom = models.CharField(verbose_name = u"Nom", max_length=255)
313 fichier = models.FileField(verbose_name = u"Fichier",
314 upload_to=poste_piece_dispatch,
315 storage=storage_prive)
316
317 class Meta:
318 abstract = True
319 ordering = ['nom']
320
321 def __unicode__(self):
322 return u'%s' % (self.nom)
323
324 class PostePiece(PostePiece_):
325 pass
326
327 class PosteComparaison_(AUFMetadata, DevisableMixin):
328 """
329 De la même manière qu'un dossier, un poste peut-être comparé à un autre poste.
330 """
331 poste = models.ForeignKey('%s.Poste' % app_context(), related_name='%(app_label)s_comparaisons_internes')
332 objects = PosteComparaisonManager()
333
334 implantation = models.ForeignKey(ref.Implantation, null=True, blank=True, related_name="+")
335 nom = models.CharField(verbose_name = u"Poste", max_length=255, null=True, blank=True)
336 montant = models.IntegerField(null=True)
337 devise = models.ForeignKey("Devise", related_name='+', null=True, blank=True)
338
339 class Meta:
340 abstract = True
341
342
343 def __unicode__(self):
344 return self.nom
345
346 class PosteComparaison(PosteComparaison_):
347 objects = NoDeleteManager()
348
349 class PosteCommentaire_(Commentaire):
350 poste = models.ForeignKey('%s.Poste' % app_context(), db_column='poste', related_name='+')
351
352 class Meta:
353 abstract = True
354
355 class PosteCommentaire(PosteCommentaire_):
356 pass
357
358 ### EMPLOYÉ/PERSONNE
359
360 class Employe(AUFMetadata):
361 """Personne occupant ou ayant occupé un Poste. Un Employe aura autant de
362 Dossiers qu'il occupe ou a occupé de Postes.
363
364 Cette classe aurait pu avantageusement s'appeler Personne car la notion
365 d'employé n'a pas de sens si aucun Dossier n'existe pour une personne.
366 """
367 # Identification
368 nom = models.CharField(max_length=255)
369 prenom = models.CharField(max_length=255, verbose_name = u"Prénom")
370 nom_affichage = models.CharField(max_length=255,
371 verbose_name = u"Nom d'affichage",
372 null=True, blank=True)
373 nationalite = models.ForeignKey(ref.Pays, to_field='code',
374 db_column='nationalite',
375 related_name='employes_nationalite',
376 verbose_name = u"Nationalité",
377 blank=True, null=True)
378 date_naissance = models.DateField(verbose_name = u"Date de naissance",
379 help_text=HELP_TEXT_DATE,
380 validators=[validate_date_passee],
381 null=True, blank=True)
382 genre = models.CharField(max_length=1, choices=GENRE_CHOICES)
383
384 # Infos personnelles
385 situation_famille = models.CharField(max_length=1,
386 choices=SITUATION_CHOICES,
387 verbose_name = u"Situation familiale",
388 null=True, blank=True)
389 date_entree = models.DateField(verbose_name = u"Date d'entrée à l'AUF",
390 help_text=HELP_TEXT_DATE,
391 null=True, blank=True)
392
393 # Coordonnées
394 tel_domicile = models.CharField(max_length=255,
395 verbose_name = u"Tél. domicile",
396 null=True, blank=True)
397 tel_cellulaire = models.CharField(max_length=255,
398 verbose_name = u"Tél. cellulaire",
399 null=True, blank=True)
400 adresse = models.CharField(max_length=255, null=True, blank=True)
401 ville = models.CharField(max_length=255, null=True, blank=True)
402 province = models.CharField(max_length=255, null=True, blank=True)
403 code_postal = models.CharField(max_length=255, null=True, blank=True)
404 pays = models.ForeignKey(ref.Pays, to_field='code', db_column='pays',
405 related_name='employes',
406 null=True, blank=True)
407
408 # meta dématérialisation : pour permettre le filtrage
409 nb_postes = models.IntegerField(verbose_name = u"nombre de postes", null=True, blank=True)
410
411 class Meta:
412 ordering = ['nom','prenom']
413 verbose_name = u"Employé"
414 verbose_name_plural = u"Employés"
415
416 def __unicode__(self):
417 return u'%s %s [%s]' % (self.nom.upper(), self.prenom, self.id)
418
419 def civilite(self):
420 civilite = u''
421 if self.genre.upper() == u'M':
422 civilite = u'M.'
423 elif self.genre.upper() == u'F':
424 civilite = u'Mme'
425 return civilite
426
427 def url_photo(self):
428 """Retourne l'URL du service retournant la photo de l'Employe.
429 Équivalent reverse url 'rh_photo' avec id en param.
430 """
431 from django.core.urlresolvers import reverse
432 return reverse('rh_photo', kwargs={'id':self.id})
433
434 def dossiers_passes(self):
435 params = {KEY_STATUT: STATUT_INACTIF, }
436 search = RechercheTemporelle(params, self.__class__)
437 search.purge_params(params)
438 q = search.get_q_temporel(self.rh_dossiers)
439 return self.rh_dossiers.filter(q)
440
441 def dossiers_futurs(self):
442 params = {KEY_STATUT: STATUT_FUTUR, }
443 search = RechercheTemporelle(params, self.__class__)
444 search.purge_params(params)
445 q = search.get_q_temporel(self.rh_dossiers)
446 return self.rh_dossiers.filter(q)
447
448 def dossiers_encours(self):
449 params = {KEY_STATUT: STATUT_ACTIF, }
450 search = RechercheTemporelle(params, self.__class__)
451 search.purge_params(params)
452 q = search.get_q_temporel(self.rh_dossiers)
453 return self.rh_dossiers.filter(q)
454
455 def postes_encours(self):
456 postes_encours = set()
457 for d in self.dossiers_encours():
458 postes_encours.add(d.poste)
459 return postes_encours
460
461 def poste_principal(self):
462 """
463 Retourne le Poste du premier Dossier créé parmi les Dossiers en cours.
464 Idée derrière :
465 si on ajout d'autre Dossiers, c'est pour des Postes secondaires.
466 """
467 poste = Poste.objects.none()
468 try:
469 poste = self.dossiers_encours().order_by('date_debut')[0].poste
470 except:
471 pass
472 return poste
473
474 prefix_implantation = "rh_dossiers__poste__implantation__region"
475 def get_regions(self):
476 regions = []
477 for d in self.dossiers.all():
478 regions.append(d.poste.implantation.region)
479 return regions
480
481
482 class EmployePiece(models.Model):
483 """Documents relatifs à un employé.
484 Ex.: CV...
485 """
486 employe = models.ForeignKey('Employe', db_column='employe')
487 nom = models.CharField(verbose_name="Nom", max_length=255)
488 fichier = models.FileField(verbose_name="Fichier",
489 upload_to=employe_piece_dispatch,
490 storage=storage_prive)
491
492 class Meta:
493 ordering = ['nom']
494 verbose_name = u"Employé pièce"
495 verbose_name_plural = u"Employé pièces"
496
497 def __unicode__(self):
498 return u'%s' % (self.nom)
499
500 class EmployeCommentaire(Commentaire):
501 employe = models.ForeignKey('Employe', db_column='employe',
502 related_name='+')
503
504 class Meta:
505 verbose_name = u"Employé commentaire"
506 verbose_name_plural = u"Employé commentaires"
507
508
509 LIEN_PARENTE_CHOICES = (
510 ('Conjoint', 'Conjoint'),
511 ('Conjointe', 'Conjointe'),
512 ('Fille', 'Fille'),
513 ('Fils', 'Fils'),
514 )
515
516 class AyantDroit(AUFMetadata):
517 """Personne en relation avec un Employe.
518 """
519 # Identification
520 nom = models.CharField(max_length=255)
521 prenom = models.CharField(max_length=255,
522 verbose_name = u"Prénom",)
523 nom_affichage = models.CharField(max_length=255,
524 verbose_name = u"Nom d'affichage",
525 null=True, blank=True)
526 nationalite = models.ForeignKey(ref.Pays, to_field='code',
527 db_column='nationalite',
528 related_name='ayantdroits_nationalite',
529 verbose_name = u"Nationalité",
530 null=True, blank=True)
531 date_naissance = models.DateField(verbose_name = u"Date de naissance",
532 help_text=HELP_TEXT_DATE,
533 validators=[validate_date_passee],
534 null=True, blank=True)
535 genre = models.CharField(max_length=1, choices=GENRE_CHOICES)
536
537 # Relation
538 employe = models.ForeignKey('Employe', db_column='employe',
539 related_name='ayantdroits',
540 verbose_name = u"Employé")
541 lien_parente = models.CharField(max_length=10,
542 choices=LIEN_PARENTE_CHOICES,
543 verbose_name = u"Lien de parenté",
544 null=True, blank=True)
545
546 class Meta:
547 ordering = ['nom', ]
548 verbose_name = u"Ayant droit"
549 verbose_name_plural = u"Ayants droit"
550
551 def __unicode__(self):
552 return u'%s %s [%s]' % (self.nom.upper(), self.prenom, self.id)
553
554 prefix_implantation = "employe__dossiers__poste__implantation__region"
555 def get_regions(self):
556 regions = []
557 for d in self.employe.dossiers.all():
558 regions.append(d.poste.implantation.region)
559 return regions
560
561
562 class AyantDroitCommentaire(Commentaire):
563 ayant_droit = models.ForeignKey('AyantDroit', db_column='ayant_droit',
564 related_name='+')
565
566
567 ### DOSSIER
568
569 STATUT_RESIDENCE_CHOICES = (
570 ('local', 'Local'),
571 ('expat', 'Expatrié'),
572 )
573
574 COMPTE_COMPTA_CHOICES = (
575 ('coda', 'CODA'),
576 ('scs', 'SCS'),
577 ('aucun', 'Aucun'),
578 )
579
580 class Dossier_(AUFMetadata, DevisableMixin):
581 """Le Dossier regroupe les informations relatives à l'occupation
582 d'un Poste par un Employe. Un seul Dossier existe par Poste occupé
583 par un Employe.
584
585 Plusieurs Contrats peuvent être associés au Dossier.
586 Une structure de Remuneration est rattachée au Dossier. Un Poste pour
587 lequel aucun Dossier n'existe est un poste vacant.
588 """
589
590 objects = DossierManager()
591
592 # TODO: OneToOne ??
593 statut = models.ForeignKey('Statut', related_name='+', null=True)
594 organisme_bstg = models.ForeignKey('OrganismeBstg',
595 db_column='organisme_bstg',
596 related_name='+',
597 verbose_name = u"Organisme",
598 help_text="Si détaché (DET) ou \
599 mis à disposition (MAD), \
600 préciser l'organisme.",
601 null=True, blank=True)
602
603 # Recrutement
604 remplacement = models.BooleanField(default=False)
605 remplacement_de = models.ForeignKey('self', related_name='+',
606 help_text=u"Taper le nom de l'employé",
607 null=True, blank=True)
608 statut_residence = models.CharField(max_length=10, default='local',
609 verbose_name = u"Statut", null=True,
610 choices=STATUT_RESIDENCE_CHOICES)
611
612 # Rémunération
613 classement = models.ForeignKey('Classement', db_column='classement',
614 related_name='+',
615 null=True, blank=True)
616 regime_travail = models.DecimalField(max_digits=12, null=True,
617 decimal_places=2,
618 default=REGIME_TRAVAIL_DEFAULT,
619 verbose_name = u"Régime de travail",
620 help_text="% du temps complet")
621 regime_travail_nb_heure_semaine = models.DecimalField(max_digits=12,
622 decimal_places=2, null=True,
623 default=REGIME_TRAVAIL_NB_HEURE_SEMAINE_DEFAULT,
624 verbose_name=u"Nb. heures par semaine",
625 help_text=REGIME_TRAVAIL_NB_HEURE_SEMAINE_HELP_TEXT)
626
627 # Occupation du Poste par cet Employe (anciennement "mandat")
628 date_debut = models.DateField(verbose_name = u"Date de début d'occupation \
629 de poste",)
630 date_fin = models.DateField(verbose_name = u"Date de fin d'occupation \
631 de poste",
632 null=True, blank=True)
633
634 # Comptes
635 # TODO?
636
637 class Meta:
638 abstract = True
639 ordering = ['employe__nom', ]
640 verbose_name = u"Dossier"
641 verbose_name_plural = "Dossiers"
642
643 def salaire_theorique(self):
644 annee = date.today().year
645 coeff = self.classement.coefficient
646 implantation = self.poste.implantation
647 point = ValeurPoint.objects.get(implantation=implantation, annee=annee)
648
649 montant = coeff * point.valeur
650 devise = point.devise
651 return {'montant':montant, 'devise':devise}
652
653 def __unicode__(self):
654 poste = self.poste.nom
655 if self.employe.genre == 'F':
656 poste = self.poste.nom_feminin
657 return u'%s - %s' % (self.employe, poste)
658
659 prefix_implantation = "poste__implantation__region"
660 def get_regions(self):
661 return [self.poste.implantation.region]
662
663
664 def remunerations(self):
665 key = "%s_remunerations" % self._meta.app_label
666 remunerations = getattr(self, key)
667 return remunerations.all().order_by('-date_debut')
668
669 def remunerations_en_cours(self):
670 q = Q(date_fin__exact=None) | Q(date_fin__gt=datetime.date.today())
671 return self.remunerations().all().filter(q).order_by('date_debut')
672
673 def get_salaire(self):
674 try:
675 return [r for r in self.remunerations().order_by('-date_debut') if r.type_id == 1][0]
676 except:
677 return None
678
679 def get_salaire_euros(self):
680 tx = self.taux_devise()
681 return (float)(tx) * (float)(self.salaire)
682
683 def get_remunerations_brutes(self):
684 """
685 1 Salaire de base
686 3 Indemnité de base
687 4 Indemnité d'expatriation
688 5 Indemnité pour frais
689 6 Indemnité de logement
690 7 Indemnité de fonction
691 8 Indemnité de responsabilité
692 9 Indemnité de transport
693 10 Indemnité compensatrice
694 11 Indemnité de subsistance
695 12 Indemnité différentielle
696 13 Prime d'installation
697 14 Billet d'avion
698 15 Déménagement
699 16 Indemnité de départ
700 18 Prime de 13ième mois
701 19 Prime d'intérim
702 """
703 ids = [1,3,4,5,6,7,8,9,10,11,12,13,14,15,16,18,19]
704 return [r for r in self.remunerations_en_cours().all() if r.type_id in ids]
705
706 def get_charges_salariales(self):
707 """
708 20 Charges salariales ?
709 """
710 ids = [20, ]
711 return [r for r in self.remunerations_en_cours().all() if r.type_id in ids]
712
713 def get_charges_patronales(self):
714 """
715 17 Charges patronales
716 """
717 ids = [17, ]
718 return [r for r in self.remunerations_en_cours().all() if r.type_id in ids]
719
720 def get_remunerations_tierces(self):
721 """
722 2 Salaire MAD
723 """
724 return [r for r in self.remunerations_en_cours().all() if r.type_id in (2, )]
725
726 # DEVISE LOCALE
727
728 def get_total_local_charges_salariales(self):
729 total = 0.0
730 for r in self.get_charges_salariales():
731 total += r.montant
732 return total
733
734 def get_total_local_charges_patronales(self):
735 total = 0.0
736 for r in self.get_charges_patronales():
737 total += float(r.montant)
738 return total
739
740 def get_local_salaire_brut(self):
741 """
742 somme des rémuérations brutes
743 """
744 devise = self.poste.get_devise()
745 total = 0.0
746 for r in self.get_remunerations_brutes():
747 if r.devise != devise:
748 return None
749 total += float(r.montant)
750 return total
751
752 def get_local_salaire_net(self):
753 """
754 salaire brut - charges salariales
755 """
756 devise = self.poste.get_devise()
757 total_charges = 0.0
758 for r in self.get_charges_salariales():
759 if r.devise != devise:
760 return None
761 total_charges += float(r.montant)
762 return self.get_local_salaire_brut() - total_charges
763
764 def get_local_couts_auf(self):
765 """
766 salaire net + charges patronales
767 """
768 devise = self.poste.get_devise()
769 total_charges = 0.0
770 for r in self.get_charges_patronales():
771 if r.devise != devise:
772 return None
773 total_charges += float(r.montant)
774 return self.get_local_salaire_net() + total_charges
775
776 def get_total_local_remunerations_tierces(self):
777 devise = self.poste.get_devise()
778 total = 0.0
779 for r in self.get_remunerations_tierces():
780 if r.devise != devise:
781 return None
782 total += float(r.montant)
783 return total
784
785 # DEVISE EURO
786
787 def get_total_charges_salariales(self):
788 total = 0.0
789 for r in self.get_charges_salariales():
790 total += r.montant_euros()
791 return total
792
793 def get_total_charges_patronales(self):
794 total = 0.0
795 for r in self.get_charges_patronales():
796 total += r.montant_euros()
797 return total
798
799 def get_salaire_brut(self):
800 """
801 somme des rémuérations brutes
802 """
803 total = 0.0
804 for r in self.get_remunerations_brutes():
805 total += r.montant_euros()
806 return total
807
808 def get_salaire_net(self):
809 """
810 salaire brut - charges salariales
811 """
812 total_charges = 0.0
813 for r in self.get_charges_salariales():
814 total_charges += r.montant_euros()
815 return self.get_salaire_brut() - total_charges
816
817 def get_couts_auf(self):
818 """
819 salaire net + charges patronales
820 """
821 total_charges = 0.0
822 for r in self.get_charges_patronales():
823 total_charges += r.montant_euros()
824 return self.get_salaire_net() + total_charges
825
826 def get_total_remunerations_tierces(self):
827 total = 0.0
828 for r in self.get_remunerations_tierces():
829 total += r.montant_euros()
830 return total
831
832
833 class Dossier(Dossier_):
834 __doc__ = Dossier_.__doc__
835 poste = models.ForeignKey('%s.Poste' % app_context(),
836 db_column='poste',
837 related_name='%(app_label)s_dossiers',
838 help_text=u"Taper le nom du poste ou du type de poste",
839 )
840 employe = models.ForeignKey('Employe', db_column='employe',
841 help_text=u"Taper le nom de l'employé",
842 related_name='%(app_label)s_dossiers',
843 verbose_name=u"Employé")
844 principal = models.BooleanField(verbose_name=u"Principal?", default=True,
845 help_text=u"Ce Dossier est pour le principal Poste occupé par l'Employé")
846
847
848 class DossierPiece_(models.Model):
849 """Documents relatifs au Dossier (à l'occupation de ce poste par employé).
850 Ex.: Lettre de motivation.
851 """
852 dossier = models.ForeignKey('%s.Dossier' % app_context(), db_column='dossier', related_name='%(app_label)s_dossierpieces')
853 nom = models.CharField(verbose_name = u"Nom", max_length=255)
854 fichier = models.FileField(verbose_name = u"Fichier",
855 upload_to=dossier_piece_dispatch,
856 storage=storage_prive)
857
858 class Meta:
859 abstract = True
860 ordering = ['nom']
861
862 def __unicode__(self):
863 return u'%s' % (self.nom)
864
865 class DossierPiece(DossierPiece_):
866 pass
867
868 class DossierCommentaire_(Commentaire):
869 dossier = models.ForeignKey('%s.Dossier' % app_context(), db_column='dossier', related_name='+')
870 class Meta:
871 abstract = True
872
873 class DossierCommentaire(DossierCommentaire_):
874 pass
875
876 class DossierComparaison_(models.Model, DevisableMixin):
877 """
878 Photo d'une comparaison salariale au moment de l'embauche.
879 """
880 dossier = models.ForeignKey('%s.Dossier' % app_context(), related_name='%(app_label)s_comparaisons')
881 objects = DossierComparaisonManager()
882
883 implantation = models.ForeignKey(ref.Implantation, related_name="+", null=True, blank=True)
884 poste = models.CharField(max_length=255, null=True, blank=True)
885 personne = models.CharField(max_length=255, null=True, blank=True)
886 montant = models.IntegerField(null=True)
887 devise = models.ForeignKey('Devise', related_name='+', null=True, blank=True)
888
889 class Meta:
890 abstract = True
891
892 def __unicode__(self):
893 return "%s (%s)" % (self.poste, self.personne)
894
895
896 class DossierComparaison(DossierComparaison_):
897 pass
898
899 ### RÉMUNÉRATION
900
901 class RemunerationMixin(AUFMetadata):
902 dossier = models.ForeignKey('%s.Dossier' % app_context(), db_column='dossier', related_name='%(app_label)s_remunerations')
903 # Identification
904 type = models.ForeignKey('TypeRemuneration', db_column='type',
905 related_name='+',
906 verbose_name = u"Type de rémunération")
907 type_revalorisation = models.ForeignKey('TypeRevalorisation',
908 db_column='type_revalorisation',
909 related_name='+',
910 verbose_name = u"Type de revalorisation",
911 null=True, blank=True)
912 montant = models.DecimalField(null=True, blank=True,
913 default=0, max_digits=12, decimal_places=2)
914 # Annuel (12 mois, 52 semaines, 364 jours?)
915 devise = models.ForeignKey('Devise', db_column='devise', related_name='+',)
916 # commentaire = precision
917 commentaire = models.CharField(max_length=255, null=True, blank=True)
918 # date_debut = anciennement date_effectif
919 date_debut = models.DateField(verbose_name = u"Date de début",
920 null=True, blank=True)
921 date_fin = models.DateField(verbose_name = u"Date de fin",
922 null=True, blank=True)
923
924 class Meta:
925 abstract = True
926 ordering = ['type__nom', '-date_fin']
927
928 def __unicode__(self):
929 return u'%s %s (%s)' % (self.montant, self.devise.code, self.type.nom)
930
931 class Remuneration_(RemunerationMixin, DevisableMixin):
932 """Structure de rémunération (données budgétaires) en situation normale
933 pour un Dossier. Si un Evenement existe, utiliser la structure de
934 rémunération EvenementRemuneration de cet événement.
935 """
936
937 def montant_mois(self):
938 return round(self.montant / 12, 2)
939
940 def montant_avec_regime(self):
941 return round(self.montant * (self.dossier.regime_travail/100), 2)
942
943 def montant_euro_mois(self):
944 return round(self.montant_euros() / 12, 2)
945
946 def __unicode__(self):
947 try:
948 devise = self.devise.code
949 except:
950 devise = "???"
951 return "%s %s" % (self.montant, devise)
952
953 class Meta:
954 abstract = True
955 verbose_name = u"Rémunération"
956 verbose_name_plural = u"Rémunérations"
957
958
959 class Remuneration(Remuneration_):
960 pass
961
962
963 ### CONTRATS
964
965 class ContratManager(NoDeleteManager):
966 def get_query_set(self):
967 return super(ContratManager, self).get_query_set().select_related('dossier', 'dossier__poste')
968
969
970 class Contrat_(AUFMetadata):
971 """Document juridique qui encadre la relation de travail d'un Employe
972 pour un Poste particulier. Pour un Dossier (qui documente cette
973 relation de travail) plusieurs contrats peuvent être associés.
974 """
975 objects = ContratManager()
976 dossier = models.ForeignKey('%s.Dossier' % app_context(), db_column='dossier', related_name='%(app_label)s_contrats')
977 type_contrat = models.ForeignKey('TypeContrat', db_column='type_contrat',
978 related_name='+',
979 verbose_name = u"type de contrat")
980 date_debut = models.DateField(verbose_name = u"Date de début")
981 date_fin = models.DateField(verbose_name = u"Date de fin",
982 null=True, blank=True)
983 fichier = models.FileField(verbose_name = u"Fichier",
984 upload_to=contrat_dispatch,
985 storage=storage_prive,
986 null=True, blank=True)
987
988 class Meta:
989 abstract = True
990 ordering = ['dossier__employe__nom']
991 verbose_name = u"Contrat"
992 verbose_name_plural = u"Contrats"
993
994 def __unicode__(self):
995 return u'%s - %s' % (self.dossier, self.id)
996
997 class Contrat(Contrat_):
998 pass
999
1000
1001 ### ÉVÉNEMENTS
1002
1003 #class Evenement_(AUFMetadata):
1004 # """Un Evenement sert à déclarer une situation temporaire (exceptionnelle)
1005 # d'un Dossier qui vient altérer des informations normales liées à un Dossier
1006 # (ex.: la Remuneration).
1007 #
1008 # Ex.: congé de maternité, maladie...
1009 #
1010 # Lors de ces situations exceptionnelles, l'Employe a un régime de travail
1011 # différent et une rémunération en conséquence. On souhaite toutefois
1012 # conserver le Dossier intact afin d'éviter une re-saisie des données lors
1013 # du retour à la normale.
1014 # """
1015 # dossier = models.ForeignKey('%s.Dossier' % app_context(), db_column='dossier',
1016 # related_name='+')
1017 # nom = models.CharField(max_length=255)
1018 # date_debut = models.DateField(verbose_name = u"Date de début")
1019 # date_fin = models.DateField(verbose_name = u"Date de fin",
1020 # null=True, blank=True)
1021 #
1022 # class Meta:
1023 # abstract = True
1024 # ordering = ['nom']
1025 # verbose_name = u"Évènement"
1026 # verbose_name_plural = u"Évènements"
1027 #
1028 # def __unicode__(self):
1029 # return u'%s' % (self.nom)
1030 #
1031 #
1032 #class Evenement(Evenement_):
1033 # __doc__ = Evenement_.__doc__
1034 #
1035 #
1036 #class EvenementRemuneration_(RemunerationMixin):
1037 # """Structure de rémunération liée à un Evenement qui remplace
1038 # temporairement la Remuneration normale d'un Dossier, pour toute la durée
1039 # de l'Evenement.
1040 # """
1041 # evenement = models.ForeignKey("Evenement", db_column='evenement',
1042 # related_name='+',
1043 # verbose_name = u"Évènement")
1044 # # TODO : le champ dossier hérité de Remuneration doit être dérivé
1045 # # de l'Evenement associé
1046 #
1047 # class Meta:
1048 # abstract = True
1049 # ordering = ['evenement', 'type__nom', '-date_fin']
1050 # verbose_name = u"Évènement - rémunération"
1051 # verbose_name_plural = u"Évènements - rémunérations"
1052 #
1053 #
1054 #class EvenementRemuneration(EvenementRemuneration_):
1055 # __doc__ = EvenementRemuneration_.__doc__
1056 #
1057 # class Meta:
1058 # abstract = True
1059 #
1060 #
1061 #class EvenementRemuneration(EvenementRemuneration_):
1062 # __doc__ = EvenementRemuneration_.__doc__
1063 # TODO? class ContratPiece(models.Model):
1064
1065
1066 ### RÉFÉRENCES RH
1067
1068 class FamilleEmploi(AUFMetadata):
1069 """Catégorie utilisée dans la gestion des Postes.
1070 Catégorie supérieure à TypePoste.
1071 """
1072 nom = models.CharField(max_length=255)
1073
1074 class Meta:
1075 ordering = ['nom']
1076 verbose_name = u"Famille d'emploi"
1077 verbose_name_plural = u"Familles d'emploi"
1078
1079 def __unicode__(self):
1080 return u'%s' % (self.nom)
1081
1082 class TypePoste(AUFMetadata):
1083 """Catégorie de Poste.
1084 """
1085 nom = models.CharField(max_length=255)
1086 nom_feminin = models.CharField(max_length=255,
1087 verbose_name = u"Nom féminin")
1088
1089 is_responsable = models.BooleanField(default=False,
1090 verbose_name = u"Poste de responsabilité")
1091 famille_emploi = models.ForeignKey('FamilleEmploi',
1092 db_column='famille_emploi',
1093 related_name='+',
1094 verbose_name = u"famille d'emploi")
1095
1096 class Meta:
1097 ordering = ['nom']
1098 verbose_name = u"Type de poste"
1099 verbose_name_plural = u"Types de poste"
1100
1101 def __unicode__(self):
1102 return u'%s' % (self.nom)
1103
1104
1105 TYPE_PAIEMENT_CHOICES = (
1106 (u'Régulier', u'Régulier'),
1107 (u'Ponctuel', u'Ponctuel'),
1108 )
1109
1110 NATURE_REMUNERATION_CHOICES = (
1111 (u'Accessoire', u'Accessoire'),
1112 (u'Charges', u'Charges'),
1113 (u'Indemnité', u'Indemnité'),
1114 (u'RAS', u'Rémunération autre source'),
1115 (u'Traitement', u'Traitement'),
1116 )
1117
1118 class TypeRemuneration(AUFMetadata):
1119 """Catégorie de Remuneration.
1120 """
1121 objects = TypeRemunerationManager()
1122
1123 nom = models.CharField(max_length=255)
1124 type_paiement = models.CharField(max_length=30,
1125 choices=TYPE_PAIEMENT_CHOICES,
1126 verbose_name = u"Type de paiement")
1127 nature_remuneration = models.CharField(max_length=30,
1128 choices=NATURE_REMUNERATION_CHOICES,
1129 verbose_name = u"Nature de la rémunération")
1130 archive = models.BooleanField(verbose_name=u"Archivé", default=False)
1131
1132 class Meta:
1133 ordering = ['nom']
1134 verbose_name = u"Type de rémunération"
1135 verbose_name_plural = u"Types de rémunération"
1136
1137 def __unicode__(self):
1138 if self.archive:
1139 archive = u"(archivé)"
1140 else:
1141 archive = ""
1142 return u'%s %s' % (self.nom, archive)
1143
1144 class TypeRevalorisation(AUFMetadata):
1145 """Justification du changement de la Remuneration.
1146 (Actuellement utilisé dans aucun traitement informatique.)
1147 """
1148 nom = models.CharField(max_length=255)
1149
1150 class Meta:
1151 ordering = ['nom']
1152 verbose_name = u"Type de revalorisation"
1153 verbose_name_plural = u"Types de revalorisation"
1154
1155 def __unicode__(self):
1156 return u'%s' % (self.nom)
1157
1158 class Service(AUFMetadata):
1159 """Unité administrative où les Postes sont rattachés.
1160 """
1161 objects = ServiceManager()
1162
1163 archive = models.BooleanField(verbose_name=u"Archivé", default=False)
1164 nom = models.CharField(max_length=255)
1165
1166 class Meta:
1167 ordering = ['nom']
1168 verbose_name = u"Service"
1169 verbose_name_plural = u"Services"
1170
1171 def __unicode__(self):
1172 if self.archive:
1173 archive = u"(archivé)"
1174 else:
1175 archive = ""
1176 return u'%s %s' % (self.nom, archive)
1177
1178
1179 TYPE_ORGANISME_CHOICES = (
1180 ('MAD', 'Mise à disposition'),
1181 ('DET', 'Détachement'),
1182 )
1183
1184 class OrganismeBstg(AUFMetadata):
1185 """Organisation d'où provient un Employe mis à disposition (MAD) de
1186 ou détaché (DET) à l'AUF à titre gratuit.
1187
1188 (BSTG = bien et service à titre gratuit.)
1189 """
1190 nom = models.CharField(max_length=255)
1191 type = models.CharField(max_length=10, choices=TYPE_ORGANISME_CHOICES)
1192 pays = models.ForeignKey(ref.Pays, to_field='code',
1193 db_column='pays',
1194 related_name='organismes_bstg',
1195 null=True, blank=True)
1196
1197 class Meta:
1198 ordering = ['type', 'nom']
1199 verbose_name = u"Organisme BSTG"
1200 verbose_name_plural = u"Organismes BSTG"
1201
1202 def __unicode__(self):
1203 return u'%s (%s)' % (self.nom, self.get_type_display())
1204
1205 prefix_implantation = "pays__region"
1206 def get_regions(self):
1207 return [self.pays.region]
1208
1209
1210 class Statut(AUFMetadata):
1211 """Statut de l'Employe dans le cadre d'un Dossier particulier.
1212 """
1213 # Identification
1214 code = models.CharField(max_length=25, unique=True, help_text="Saisir un code court mais lisible pour ce statut : le code est utilisé pour associer les statuts aux autres données tout en demeurant plus lisible qu'un identifiant numérique.")
1215 nom = models.CharField(max_length=255)
1216
1217 class Meta:
1218 ordering = ['code']
1219 verbose_name = u"Statut d'employé"
1220 verbose_name_plural = u"Statuts d'employé"
1221
1222 def __unicode__(self):
1223 return u'%s : %s' % (self.code, self.nom)
1224
1225
1226 TYPE_CLASSEMENT_CHOICES = (
1227 ('S', 'S -Soutien'),
1228 ('T', 'T - Technicien'),
1229 ('P', 'P - Professionel'),
1230 ('C', 'C - Cadre'),
1231 ('D', 'D - Direction'),
1232 ('SO', 'SO - Sans objet [expatriés]'),
1233 ('HG', 'HG - Hors grille [direction]'),
1234 )
1235
1236 class ClassementManager(models.Manager):
1237 """
1238 Ordonner les spcéfiquement les classements.
1239 """
1240 def get_query_set(self):
1241 qs = super(self.__class__, self).get_query_set()
1242 qs = qs.extra(select={'ponderation': 'FIND_IN_SET(type,"SO,HG,S,T,P,C,D")'})
1243 qs = qs.extra(order_by=('ponderation', 'echelon', 'degre', ))
1244 return qs.all()
1245
1246
1247 class Classement_(AUFMetadata):
1248 """Éléments de classement de la
1249 "Grille générique de classement hiérarchique".
1250
1251 Utile pour connaître, pour un Dossier, le salaire de base théorique lié au
1252 classement dans la grille. Le classement donne le coefficient utilisé dans:
1253
1254 salaire de base = coefficient * valeur du point de l'Implantation du Poste
1255 """
1256 objects = ClassementManager()
1257
1258 # Identification
1259 type = models.CharField(max_length=10, choices=TYPE_CLASSEMENT_CHOICES)
1260 echelon = models.IntegerField(verbose_name=u"Échelon", blank=True, default=0)
1261 degre = models.IntegerField(verbose_name=u"Degré", blank=True, default=0)
1262 coefficient = models.FloatField(default=0, verbose_name=u"Coefficient",
1263 null=True)
1264 # Méta
1265 # annee # au lieu de date_debut et date_fin
1266 commentaire = models.TextField(null=True, blank=True)
1267
1268 class Meta:
1269 abstract = True
1270 ordering = ['type','echelon','degre','coefficient']
1271 verbose_name = u"Classement"
1272 verbose_name_plural = u"Classements"
1273
1274 def __unicode__(self):
1275 return u'%s.%s.%s' % (self.type, self.echelon, self.degre, )
1276
1277 class Classement(Classement_):
1278 __doc__ = Classement_.__doc__
1279
1280
1281 class TauxChange_(AUFMetadata):
1282 """Taux de change de la devise vers l'euro (EUR)
1283 pour chaque année budgétaire.
1284 """
1285 # Identification
1286 devise = models.ForeignKey('Devise', db_column='devise')
1287 annee = models.IntegerField(verbose_name = u"Année")
1288 taux = models.FloatField(verbose_name = u"Taux vers l'euro")
1289
1290 class Meta:
1291 abstract = True
1292 ordering = ['-annee', 'devise__code']
1293 verbose_name = u"Taux de change"
1294 verbose_name_plural = u"Taux de change"
1295
1296 def __unicode__(self):
1297 return u'%s : %s € (%s)' % (self.devise, self.taux, self.annee)
1298
1299
1300 class TauxChange(TauxChange_):
1301 __doc__ = TauxChange_.__doc__
1302
1303 class ValeurPointManager(NoDeleteManager):
1304
1305 def get_query_set(self):
1306 return super(ValeurPointManager, self).get_query_set().select_related('devise', 'implantation')
1307
1308
1309 class ValeurPoint_(AUFMetadata):
1310 """Utile pour connaître, pour un Dossier, le salaire de base théorique lié
1311 au classement dans la grille. La ValeurPoint s'obtient par l'implantation
1312 du Poste de ce Dossier : dossier.poste.implantation (pseudo code).
1313
1314 salaire de base = coefficient * valeur du point de l'Implantation du Poste
1315 """
1316
1317 actuelles = ValeurPointManager()
1318
1319 valeur = models.FloatField(null=True)
1320 devise = models.ForeignKey('Devise', db_column='devise', related_name='+',)
1321 implantation = models.ForeignKey(ref.Implantation,
1322 db_column='implantation',
1323 related_name='%(app_label)s_valeur_point')
1324 # Méta
1325 annee = models.IntegerField()
1326
1327 class Meta:
1328 ordering = ['-annee', 'implantation__nom']
1329 abstract = True
1330 verbose_name = u"Valeur du point"
1331 verbose_name_plural = u"Valeurs du point"
1332
1333 def __unicode__(self):
1334 return u'%s %s %s [%s] %s' % (self.devise.code, self.annee, self.valeur, self.implantation.nom_court, self.devise.nom)
1335
1336
1337 class ValeurPoint(ValeurPoint_):
1338 __doc__ = ValeurPoint_.__doc__
1339
1340
1341
1342 class Devise(AUFMetadata):
1343 """Devise monétaire.
1344 """
1345
1346 objects = DeviseManager()
1347
1348 archive = models.BooleanField(verbose_name=u"Archivé", default=False)
1349 code = models.CharField(max_length=10, unique=True)
1350 nom = models.CharField(max_length=255)
1351
1352 class Meta:
1353 ordering = ['code']
1354 verbose_name = u"Devise"
1355 verbose_name_plural = u"Devises"
1356
1357 def __unicode__(self):
1358 return u'%s - %s' % (self.code, self.nom)
1359
1360 class TypeContrat(AUFMetadata):
1361 """Type de contrat.
1362 """
1363 nom = models.CharField(max_length=255)
1364 nom_long = models.CharField(max_length=255)
1365
1366 class Meta:
1367 ordering = ['nom']
1368 verbose_name = u"Type de contrat"
1369 verbose_name_plural = u"Types de contrat"
1370
1371 def __unicode__(self):
1372 return u'%s' % (self.nom)
1373
1374
1375 ### AUTRES
1376
1377 class ResponsableImplantation(AUFMetadata):
1378 """Le responsable d'une implantation.
1379 Anciennement géré sur le Dossier du responsable.
1380 """
1381 employe = models.ForeignKey('Employe', db_column='employe',
1382 related_name='+',
1383 null=True, blank=True)
1384 implantation = models.ForeignKey(ref.Implantation,
1385 db_column='implantation', related_name='+',
1386 unique=True)
1387
1388 def __unicode__(self):
1389 return u'%s : %s' % (self.implantation, self.employe)
1390
1391 class Meta:
1392 ordering = ['implantation__nom']
1393 verbose_name = "Responsable d'implantation"
1394 verbose_name_plural = "Responsables d'implantation"
1395