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