[#2872] Ne pas planter lorsqu'une implantation n'a pas de valeur du point
[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 nom = models.CharField(verbose_name="Nom", max_length=255)
492 fichier = models.FileField(verbose_name="Fichier",
493 upload_to=employe_piece_dispatch,
494 storage=storage_prive)
495
496 class Meta:
497 ordering = ['nom']
498 verbose_name = u"Employé pièce"
499 verbose_name_plural = u"Employé pièces"
500
501 def __unicode__(self):
502 return u'%s' % (self.nom)
503
504 class EmployeCommentaire(Commentaire):
505 employe = models.ForeignKey('Employe', db_column='employe',
506 related_name='+')
507
508 class Meta:
509 verbose_name = u"Employé commentaire"
510 verbose_name_plural = u"Employé commentaires"
511
512
513 LIEN_PARENTE_CHOICES = (
514 ('Conjoint', 'Conjoint'),
515 ('Conjointe', 'Conjointe'),
516 ('Fille', 'Fille'),
517 ('Fils', 'Fils'),
518 )
519
520 class AyantDroit(AUFMetadata):
521 """Personne en relation avec un Employe.
522 """
523 # Identification
524 nom = models.CharField(max_length=255)
525 prenom = models.CharField(max_length=255,
526 verbose_name = u"Prénom",)
527 nom_affichage = models.CharField(max_length=255,
528 verbose_name = u"Nom d'affichage",
529 null=True, blank=True)
530 nationalite = models.ForeignKey(ref.Pays, to_field='code',
531 db_column='nationalite',
532 related_name='ayantdroits_nationalite',
533 verbose_name = u"Nationalité",
534 null=True, blank=True)
535 date_naissance = models.DateField(verbose_name = u"Date de naissance",
536 help_text=HELP_TEXT_DATE,
537 validators=[validate_date_passee],
538 null=True, blank=True)
539 genre = models.CharField(max_length=1, choices=GENRE_CHOICES)
540
541 # Relation
542 employe = models.ForeignKey('Employe', db_column='employe',
543 related_name='ayantdroits',
544 verbose_name = u"Employé")
545 lien_parente = models.CharField(max_length=10,
546 choices=LIEN_PARENTE_CHOICES,
547 verbose_name = u"Lien de parenté",
548 null=True, blank=True)
549
550 class Meta:
551 ordering = ['nom', ]
552 verbose_name = u"Ayant droit"
553 verbose_name_plural = u"Ayants droit"
554
555 def __unicode__(self):
556 return u'%s %s [%s]' % (self.nom.upper(), self.prenom, self.id)
557
558 prefix_implantation = "employe__dossiers__poste__implantation__region"
559 def get_regions(self):
560 regions = []
561 for d in self.employe.dossiers.all():
562 regions.append(d.poste.implantation.region)
563 return regions
564
565
566 class AyantDroitCommentaire(Commentaire):
567 ayant_droit = models.ForeignKey('AyantDroit', db_column='ayant_droit',
568 related_name='+')
569
570
571 ### DOSSIER
572
573 STATUT_RESIDENCE_CHOICES = (
574 ('local', 'Local'),
575 ('expat', 'Expatrié'),
576 )
577
578 COMPTE_COMPTA_CHOICES = (
579 ('coda', 'CODA'),
580 ('scs', 'SCS'),
581 ('aucun', 'Aucun'),
582 )
583
584 class Dossier_(AUFMetadata, DevisableMixin):
585 """Le Dossier regroupe les informations relatives à l'occupation
586 d'un Poste par un Employe. Un seul Dossier existe par Poste occupé
587 par un Employe.
588
589 Plusieurs Contrats peuvent être associés au Dossier.
590 Une structure de Remuneration est rattachée au Dossier. Un Poste pour
591 lequel aucun Dossier n'existe est un poste vacant.
592 """
593
594 objects = DossierManager()
595
596 # TODO: OneToOne ??
597 statut = models.ForeignKey('Statut', related_name='+', null=True)
598 organisme_bstg = models.ForeignKey('OrganismeBstg',
599 db_column='organisme_bstg',
600 related_name='+',
601 verbose_name = u"Organisme",
602 help_text="Si détaché (DET) ou \
603 mis à disposition (MAD), \
604 préciser l'organisme.",
605 null=True, blank=True)
606
607 # Recrutement
608 remplacement = models.BooleanField(default=False)
609 remplacement_de = models.ForeignKey('self', related_name='+',
610 help_text=u"Taper le nom de l'employé",
611 null=True, blank=True)
612 statut_residence = models.CharField(max_length=10, default='local',
613 verbose_name = u"Statut", null=True,
614 choices=STATUT_RESIDENCE_CHOICES)
615
616 # Rémunération
617 classement = models.ForeignKey('Classement', db_column='classement',
618 related_name='+',
619 null=True, blank=True)
620 regime_travail = models.DecimalField(max_digits=12, null=True,
621 decimal_places=2,
622 default=REGIME_TRAVAIL_DEFAULT,
623 verbose_name = u"Régime de travail",
624 help_text="% du temps complet")
625 regime_travail_nb_heure_semaine = models.DecimalField(max_digits=12,
626 decimal_places=2, null=True,
627 default=REGIME_TRAVAIL_NB_HEURE_SEMAINE_DEFAULT,
628 verbose_name=u"Nb. heures par semaine",
629 help_text=REGIME_TRAVAIL_NB_HEURE_SEMAINE_HELP_TEXT)
630
631 # Occupation du Poste par cet Employe (anciennement "mandat")
632 date_debut = models.DateField(verbose_name = u"Date de début d'occupation \
633 de poste",)
634 date_fin = models.DateField(verbose_name = u"Date de fin d'occupation \
635 de poste",
636 null=True, blank=True)
637
638 # Comptes
639 # TODO?
640
641 class Meta:
642 abstract = True
643 ordering = ['employe__nom', ]
644 verbose_name = u"Dossier"
645 verbose_name_plural = "Dossiers"
646
647 def salaire_theorique(self):
648 annee = date.today().year
649 coeff = self.classement.coefficient
650 implantation = self.poste.implantation
651 point = ValeurPoint.objects.get(implantation=implantation, annee=annee)
652
653 montant = coeff * point.valeur
654 devise = point.devise
655 return {'montant':montant, 'devise':devise}
656
657 def __unicode__(self):
658 poste = self.poste.nom
659 if self.employe.genre == 'F':
660 poste = self.poste.nom_feminin
661 return u'%s - %s' % (self.employe, poste)
662
663 prefix_implantation = "poste__implantation__region"
664 def get_regions(self):
665 return [self.poste.implantation.region]
666
667
668 def remunerations(self):
669 key = "%s_remunerations" % self._meta.app_label
670 remunerations = getattr(self, key)
671 return remunerations.all().order_by('-date_debut')
672
673 def remunerations_en_cours(self):
674 q = Q(date_fin__exact=None) | Q(date_fin__gt=datetime.date.today())
675 return self.remunerations().all().filter(q).order_by('date_debut')
676
677 def get_salaire(self):
678 try:
679 return [r for r in self.remunerations().order_by('-date_debut') if r.type_id == 1][0]
680 except:
681 return None
682
683 def get_salaire_euros(self):
684 tx = self.taux_devise()
685 return (float)(tx) * (float)(self.salaire)
686
687 def get_remunerations_brutes(self):
688 """
689 1 Salaire de base
690 3 Indemnité de base
691 4 Indemnité d'expatriation
692 5 Indemnité pour frais
693 6 Indemnité de logement
694 7 Indemnité de fonction
695 8 Indemnité de responsabilité
696 9 Indemnité de transport
697 10 Indemnité compensatrice
698 11 Indemnité de subsistance
699 12 Indemnité différentielle
700 13 Prime d'installation
701 14 Billet d'avion
702 15 Déménagement
703 16 Indemnité de départ
704 18 Prime de 13ième mois
705 19 Prime d'intérim
706 """
707 ids = [1,3,4,5,6,7,8,9,10,11,12,13,14,15,16,18,19]
708 return [r for r in self.remunerations_en_cours().all() if r.type_id in ids]
709
710 def get_charges_salariales(self):
711 """
712 20 Charges salariales ?
713 """
714 ids = [20, ]
715 return [r for r in self.remunerations_en_cours().all() if r.type_id in ids]
716
717 def get_charges_patronales(self):
718 """
719 17 Charges patronales
720 """
721 ids = [17, ]
722 return [r for r in self.remunerations_en_cours().all() if r.type_id in ids]
723
724 def get_remunerations_tierces(self):
725 """
726 2 Salaire MAD
727 """
728 return [r for r in self.remunerations_en_cours().all() if r.type_id in (2, )]
729
730 # DEVISE LOCALE
731
732 def get_total_local_charges_salariales(self):
733 devise = self.poste.get_devise()
734 total = 0.0
735 for r in self.get_charges_salariales():
736 if r.devise != devise:
737 return None
738 total += float(r.montant)
739 return total
740
741 def get_total_local_charges_patronales(self):
742 devise = self.poste.get_devise()
743 total = 0.0
744 for r in self.get_charges_patronales():
745 if r.devise != devise:
746 return None
747 total += float(r.montant)
748 return total
749
750 def get_local_salaire_brut(self):
751 """
752 somme des rémuérations brutes
753 """
754 devise = self.poste.get_devise()
755 total = 0.0
756 for r in self.get_remunerations_brutes():
757 if r.devise != devise:
758 return None
759 total += float(r.montant)
760 return total
761
762 def get_local_salaire_net(self):
763 """
764 salaire brut - charges salariales
765 """
766 devise = self.poste.get_devise()
767 total_charges = 0.0
768 for r in self.get_charges_salariales():
769 if r.devise != devise:
770 return None
771 total_charges += float(r.montant)
772 return self.get_local_salaire_brut() - total_charges
773
774 def get_local_couts_auf(self):
775 """
776 salaire net + charges patronales
777 """
778 devise = self.poste.get_devise()
779 total_charges = 0.0
780 for r in self.get_charges_patronales():
781 if r.devise != devise:
782 return None
783 total_charges += float(r.montant)
784 return self.get_local_salaire_net() + total_charges
785
786 def get_total_local_remunerations_tierces(self):
787 devise = self.poste.get_devise()
788 total = 0.0
789 for r in self.get_remunerations_tierces():
790 if r.devise != devise:
791 return None
792 total += float(r.montant)
793 return total
794
795 # DEVISE EURO
796
797 def get_total_charges_salariales(self):
798 total = 0.0
799 for r in self.get_charges_salariales():
800 total += r.montant_euros()
801 return total
802
803 def get_total_charges_patronales(self):
804 total = 0.0
805 for r in self.get_charges_patronales():
806 total += r.montant_euros()
807 return total
808
809 def get_salaire_brut(self):
810 """
811 somme des rémuérations brutes
812 """
813 total = 0.0
814 for r in self.get_remunerations_brutes():
815 total += r.montant_euros()
816 return total
817
818 def get_salaire_net(self):
819 """
820 salaire brut - charges salariales
821 """
822 total_charges = 0.0
823 for r in self.get_charges_salariales():
824 total_charges += r.montant_euros()
825 return self.get_salaire_brut() - total_charges
826
827 def get_couts_auf(self):
828 """
829 salaire net + charges patronales
830 """
831 total_charges = 0.0
832 for r in self.get_charges_patronales():
833 total_charges += r.montant_euros()
834 return self.get_salaire_net() + total_charges
835
836 def get_total_remunerations_tierces(self):
837 total = 0.0
838 for r in self.get_remunerations_tierces():
839 total += r.montant_euros()
840 return total
841
842
843 class Dossier(Dossier_):
844 __doc__ = Dossier_.__doc__
845 poste = models.ForeignKey('%s.Poste' % app_context(),
846 db_column='poste',
847 related_name='%(app_label)s_dossiers',
848 help_text=u"Taper le nom du poste ou du type de poste",
849 )
850 employe = models.ForeignKey('Employe', db_column='employe',
851 help_text=u"Taper le nom de l'employé",
852 related_name='%(app_label)s_dossiers',
853 verbose_name=u"Employé")
854 principal = models.BooleanField(verbose_name=u"Principal?", default=True,
855 help_text=u"Ce Dossier est pour le principal Poste occupé par l'Employé")
856
857
858 class DossierPiece_(models.Model):
859 """Documents relatifs au Dossier (à l'occupation de ce poste par employé).
860 Ex.: Lettre de motivation.
861 """
862 dossier = models.ForeignKey('%s.Dossier' % app_context(), db_column='dossier', related_name='%(app_label)s_dossierpieces')
863 nom = models.CharField(verbose_name = u"Nom", max_length=255)
864 fichier = models.FileField(verbose_name = u"Fichier",
865 upload_to=dossier_piece_dispatch,
866 storage=storage_prive)
867
868 class Meta:
869 abstract = True
870 ordering = ['nom']
871
872 def __unicode__(self):
873 return u'%s' % (self.nom)
874
875 class DossierPiece(DossierPiece_):
876 pass
877
878 class DossierCommentaire_(Commentaire):
879 dossier = models.ForeignKey('%s.Dossier' % app_context(), db_column='dossier', related_name='+')
880 class Meta:
881 abstract = True
882
883 class DossierCommentaire(DossierCommentaire_):
884 pass
885
886 class DossierComparaison_(models.Model, DevisableMixin):
887 """
888 Photo d'une comparaison salariale au moment de l'embauche.
889 """
890 dossier = models.ForeignKey('%s.Dossier' % app_context(), related_name='%(app_label)s_comparaisons')
891 objects = DossierComparaisonManager()
892
893 implantation = models.ForeignKey(ref.Implantation, related_name="+", null=True, blank=True)
894 poste = models.CharField(max_length=255, null=True, blank=True)
895 personne = models.CharField(max_length=255, null=True, blank=True)
896 montant = models.IntegerField(null=True)
897 devise = models.ForeignKey('Devise', related_name='+', null=True, blank=True)
898
899 class Meta:
900 abstract = True
901
902 def __unicode__(self):
903 return "%s (%s)" % (self.poste, self.personne)
904
905
906 class DossierComparaison(DossierComparaison_):
907 pass
908
909 ### RÉMUNÉRATION
910
911 class RemunerationMixin(AUFMetadata):
912 dossier = models.ForeignKey('%s.Dossier' % app_context(), db_column='dossier', related_name='%(app_label)s_remunerations')
913 # Identification
914 type = models.ForeignKey('TypeRemuneration', db_column='type',
915 related_name='+',
916 verbose_name = u"Type de rémunération")
917 type_revalorisation = models.ForeignKey('TypeRevalorisation',
918 db_column='type_revalorisation',
919 related_name='+',
920 verbose_name = u"Type de revalorisation",
921 null=True, blank=True)
922 montant = models.DecimalField(null=True, blank=True,
923 default=0, max_digits=12, decimal_places=2)
924 # Annuel (12 mois, 52 semaines, 364 jours?)
925 devise = models.ForeignKey('Devise', db_column='devise', related_name='+',)
926 # commentaire = precision
927 commentaire = models.CharField(max_length=255, null=True, blank=True)
928 # date_debut = anciennement date_effectif
929 date_debut = models.DateField(verbose_name = u"Date de début",
930 null=True, blank=True)
931 date_fin = models.DateField(verbose_name = u"Date de fin",
932 null=True, blank=True)
933
934 class Meta:
935 abstract = True
936 ordering = ['type__nom', '-date_fin']
937
938 def __unicode__(self):
939 return u'%s %s (%s)' % (self.montant, self.devise.code, self.type.nom)
940
941 class Remuneration_(RemunerationMixin, DevisableMixin):
942 """Structure de rémunération (données budgétaires) en situation normale
943 pour un Dossier. Si un Evenement existe, utiliser la structure de
944 rémunération EvenementRemuneration de cet événement.
945 """
946
947 def montant_mois(self):
948 return round(self.montant / 12, 2)
949
950 def montant_avec_regime(self):
951 return round(self.montant * (self.dossier.regime_travail/100), 2)
952
953 def montant_euro_mois(self):
954 return round(self.montant_euros() / 12, 2)
955
956 def __unicode__(self):
957 try:
958 devise = self.devise.code
959 except:
960 devise = "???"
961 return "%s %s" % (self.montant, devise)
962
963 class Meta:
964 abstract = True
965 verbose_name = u"Rémunération"
966 verbose_name_plural = u"Rémunérations"
967
968
969 class Remuneration(Remuneration_):
970 pass
971
972
973 ### CONTRATS
974
975 class ContratManager(NoDeleteManager):
976 def get_query_set(self):
977 return super(ContratManager, self).get_query_set().select_related('dossier', 'dossier__poste')
978
979
980 class Contrat_(AUFMetadata):
981 """Document juridique qui encadre la relation de travail d'un Employe
982 pour un Poste particulier. Pour un Dossier (qui documente cette
983 relation de travail) plusieurs contrats peuvent être associés.
984 """
985 objects = ContratManager()
986 dossier = models.ForeignKey('%s.Dossier' % app_context(), db_column='dossier', related_name='%(app_label)s_contrats')
987 type_contrat = models.ForeignKey('TypeContrat', db_column='type_contrat',
988 related_name='+',
989 verbose_name = u"type de contrat")
990 date_debut = models.DateField(verbose_name = u"Date de début")
991 date_fin = models.DateField(verbose_name = u"Date de fin",
992 null=True, blank=True)
993 fichier = models.FileField(verbose_name = u"Fichier",
994 upload_to=contrat_dispatch,
995 storage=storage_prive,
996 null=True, blank=True)
997
998 class Meta:
999 abstract = True
1000 ordering = ['dossier__employe__nom']
1001 verbose_name = u"Contrat"
1002 verbose_name_plural = u"Contrats"
1003
1004 def __unicode__(self):
1005 return u'%s - %s' % (self.dossier, self.id)
1006
1007 class Contrat(Contrat_):
1008 pass
1009
1010
1011 ### ÉVÉNEMENTS
1012
1013 #class Evenement_(AUFMetadata):
1014 # """Un Evenement sert à déclarer une situation temporaire (exceptionnelle)
1015 # d'un Dossier qui vient altérer des informations normales liées à un Dossier
1016 # (ex.: la Remuneration).
1017 #
1018 # Ex.: congé de maternité, maladie...
1019 #
1020 # Lors de ces situations exceptionnelles, l'Employe a un régime de travail
1021 # différent et une rémunération en conséquence. On souhaite toutefois
1022 # conserver le Dossier intact afin d'éviter une re-saisie des données lors
1023 # du retour à la normale.
1024 # """
1025 # dossier = models.ForeignKey('%s.Dossier' % app_context(), db_column='dossier',
1026 # related_name='+')
1027 # nom = models.CharField(max_length=255)
1028 # date_debut = models.DateField(verbose_name = u"Date de début")
1029 # date_fin = models.DateField(verbose_name = u"Date de fin",
1030 # null=True, blank=True)
1031 #
1032 # class Meta:
1033 # abstract = True
1034 # ordering = ['nom']
1035 # verbose_name = u"Évènement"
1036 # verbose_name_plural = u"Évènements"
1037 #
1038 # def __unicode__(self):
1039 # return u'%s' % (self.nom)
1040 #
1041 #
1042 #class Evenement(Evenement_):
1043 # __doc__ = Evenement_.__doc__
1044 #
1045 #
1046 #class EvenementRemuneration_(RemunerationMixin):
1047 # """Structure de rémunération liée à un Evenement qui remplace
1048 # temporairement la Remuneration normale d'un Dossier, pour toute la durée
1049 # de l'Evenement.
1050 # """
1051 # evenement = models.ForeignKey("Evenement", db_column='evenement',
1052 # related_name='+',
1053 # verbose_name = u"Évènement")
1054 # # TODO : le champ dossier hérité de Remuneration doit être dérivé
1055 # # de l'Evenement associé
1056 #
1057 # class Meta:
1058 # abstract = True
1059 # ordering = ['evenement', 'type__nom', '-date_fin']
1060 # verbose_name = u"Évènement - rémunération"
1061 # verbose_name_plural = u"Évènements - rémunérations"
1062 #
1063 #
1064 #class EvenementRemuneration(EvenementRemuneration_):
1065 # __doc__ = EvenementRemuneration_.__doc__
1066 #
1067 # class Meta:
1068 # abstract = True
1069 #
1070 #
1071 #class EvenementRemuneration(EvenementRemuneration_):
1072 # __doc__ = EvenementRemuneration_.__doc__
1073 # TODO? class ContratPiece(models.Model):
1074
1075
1076 ### RÉFÉRENCES RH
1077
1078 class FamilleEmploi(AUFMetadata):
1079 """Catégorie utilisée dans la gestion des Postes.
1080 Catégorie supérieure à TypePoste.
1081 """
1082 nom = models.CharField(max_length=255)
1083
1084 class Meta:
1085 ordering = ['nom']
1086 verbose_name = u"Famille d'emploi"
1087 verbose_name_plural = u"Familles d'emploi"
1088
1089 def __unicode__(self):
1090 return u'%s' % (self.nom)
1091
1092 class TypePoste(AUFMetadata):
1093 """Catégorie de Poste.
1094 """
1095 nom = models.CharField(max_length=255)
1096 nom_feminin = models.CharField(max_length=255,
1097 verbose_name = u"Nom féminin")
1098
1099 is_responsable = models.BooleanField(default=False,
1100 verbose_name = u"Poste de responsabilité")
1101 famille_emploi = models.ForeignKey('FamilleEmploi',
1102 db_column='famille_emploi',
1103 related_name='+',
1104 verbose_name = u"famille d'emploi")
1105
1106 class Meta:
1107 ordering = ['nom']
1108 verbose_name = u"Type de poste"
1109 verbose_name_plural = u"Types de poste"
1110
1111 def __unicode__(self):
1112 return u'%s' % (self.nom)
1113
1114
1115 TYPE_PAIEMENT_CHOICES = (
1116 (u'Régulier', u'Régulier'),
1117 (u'Ponctuel', u'Ponctuel'),
1118 )
1119
1120 NATURE_REMUNERATION_CHOICES = (
1121 (u'Accessoire', u'Accessoire'),
1122 (u'Charges', u'Charges'),
1123 (u'Indemnité', u'Indemnité'),
1124 (u'RAS', u'Rémunération autre source'),
1125 (u'Traitement', u'Traitement'),
1126 )
1127
1128 class TypeRemuneration(AUFMetadata):
1129 """Catégorie de Remuneration.
1130 """
1131 objects = TypeRemunerationManager()
1132
1133 nom = models.CharField(max_length=255)
1134 type_paiement = models.CharField(max_length=30,
1135 choices=TYPE_PAIEMENT_CHOICES,
1136 verbose_name = u"Type de paiement")
1137 nature_remuneration = models.CharField(max_length=30,
1138 choices=NATURE_REMUNERATION_CHOICES,
1139 verbose_name = u"Nature de la rémunération")
1140 archive = models.BooleanField(verbose_name=u"Archivé", default=False)
1141
1142 class Meta:
1143 ordering = ['nom']
1144 verbose_name = u"Type de rémunération"
1145 verbose_name_plural = u"Types de rémunération"
1146
1147 def __unicode__(self):
1148 if self.archive:
1149 archive = u"(archivé)"
1150 else:
1151 archive = ""
1152 return u'%s %s' % (self.nom, archive)
1153
1154 class TypeRevalorisation(AUFMetadata):
1155 """Justification du changement de la Remuneration.
1156 (Actuellement utilisé dans aucun traitement informatique.)
1157 """
1158 nom = models.CharField(max_length=255)
1159
1160 class Meta:
1161 ordering = ['nom']
1162 verbose_name = u"Type de revalorisation"
1163 verbose_name_plural = u"Types de revalorisation"
1164
1165 def __unicode__(self):
1166 return u'%s' % (self.nom)
1167
1168 class Service(AUFMetadata):
1169 """Unité administrative où les Postes sont rattachés.
1170 """
1171 objects = ServiceManager()
1172
1173 archive = models.BooleanField(verbose_name=u"Archivé", default=False)
1174 nom = models.CharField(max_length=255)
1175
1176 class Meta:
1177 ordering = ['nom']
1178 verbose_name = u"Service"
1179 verbose_name_plural = u"Services"
1180
1181 def __unicode__(self):
1182 if self.archive:
1183 archive = u"(archivé)"
1184 else:
1185 archive = ""
1186 return u'%s %s' % (self.nom, archive)
1187
1188
1189 TYPE_ORGANISME_CHOICES = (
1190 ('MAD', 'Mise à disposition'),
1191 ('DET', 'Détachement'),
1192 )
1193
1194 class OrganismeBstg(AUFMetadata):
1195 """Organisation d'où provient un Employe mis à disposition (MAD) de
1196 ou détaché (DET) à l'AUF à titre gratuit.
1197
1198 (BSTG = bien et service à titre gratuit.)
1199 """
1200 nom = models.CharField(max_length=255)
1201 type = models.CharField(max_length=10, choices=TYPE_ORGANISME_CHOICES)
1202 pays = models.ForeignKey(ref.Pays, to_field='code',
1203 db_column='pays',
1204 related_name='organismes_bstg',
1205 null=True, blank=True)
1206
1207 class Meta:
1208 ordering = ['type', 'nom']
1209 verbose_name = u"Organisme BSTG"
1210 verbose_name_plural = u"Organismes BSTG"
1211
1212 def __unicode__(self):
1213 return u'%s (%s)' % (self.nom, self.get_type_display())
1214
1215 prefix_implantation = "pays__region"
1216 def get_regions(self):
1217 return [self.pays.region]
1218
1219
1220 class Statut(AUFMetadata):
1221 """Statut de l'Employe dans le cadre d'un Dossier particulier.
1222 """
1223 # Identification
1224 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.")
1225 nom = models.CharField(max_length=255)
1226
1227 class Meta:
1228 ordering = ['code']
1229 verbose_name = u"Statut d'employé"
1230 verbose_name_plural = u"Statuts d'employé"
1231
1232 def __unicode__(self):
1233 return u'%s : %s' % (self.code, self.nom)
1234
1235
1236 TYPE_CLASSEMENT_CHOICES = (
1237 ('S', 'S -Soutien'),
1238 ('T', 'T - Technicien'),
1239 ('P', 'P - Professionel'),
1240 ('C', 'C - Cadre'),
1241 ('D', 'D - Direction'),
1242 ('SO', 'SO - Sans objet [expatriés]'),
1243 ('HG', 'HG - Hors grille [direction]'),
1244 )
1245
1246 class ClassementManager(models.Manager):
1247 """
1248 Ordonner les spcéfiquement les classements.
1249 """
1250 def get_query_set(self):
1251 qs = super(self.__class__, self).get_query_set()
1252 qs = qs.extra(select={'ponderation': 'FIND_IN_SET(type,"SO,HG,S,T,P,C,D")'})
1253 qs = qs.extra(order_by=('ponderation', 'echelon', 'degre', ))
1254 return qs.all()
1255
1256
1257 class Classement_(AUFMetadata):
1258 """Éléments de classement de la
1259 "Grille générique de classement hiérarchique".
1260
1261 Utile pour connaître, pour un Dossier, le salaire de base théorique lié au
1262 classement dans la grille. Le classement donne le coefficient utilisé dans:
1263
1264 salaire de base = coefficient * valeur du point de l'Implantation du Poste
1265 """
1266 objects = ClassementManager()
1267
1268 # Identification
1269 type = models.CharField(max_length=10, choices=TYPE_CLASSEMENT_CHOICES)
1270 echelon = models.IntegerField(verbose_name=u"Échelon", blank=True, default=0)
1271 degre = models.IntegerField(verbose_name=u"Degré", blank=True, default=0)
1272 coefficient = models.FloatField(default=0, verbose_name=u"Coefficient",
1273 null=True)
1274 # Méta
1275 # annee # au lieu de date_debut et date_fin
1276 commentaire = models.TextField(null=True, blank=True)
1277
1278 class Meta:
1279 abstract = True
1280 ordering = ['type','echelon','degre','coefficient']
1281 verbose_name = u"Classement"
1282 verbose_name_plural = u"Classements"
1283
1284 def __unicode__(self):
1285 return u'%s.%s.%s' % (self.type, self.echelon, self.degre, )
1286
1287 class Classement(Classement_):
1288 __doc__ = Classement_.__doc__
1289
1290
1291 class TauxChange_(AUFMetadata):
1292 """Taux de change de la devise vers l'euro (EUR)
1293 pour chaque année budgétaire.
1294 """
1295 # Identification
1296 devise = models.ForeignKey('Devise', db_column='devise')
1297 annee = models.IntegerField(verbose_name = u"Année")
1298 taux = models.FloatField(verbose_name = u"Taux vers l'euro")
1299
1300 class Meta:
1301 abstract = True
1302 ordering = ['-annee', 'devise__code']
1303 verbose_name = u"Taux de change"
1304 verbose_name_plural = u"Taux de change"
1305
1306 def __unicode__(self):
1307 return u'%s : %s € (%s)' % (self.devise, self.taux, self.annee)
1308
1309
1310 class TauxChange(TauxChange_):
1311 __doc__ = TauxChange_.__doc__
1312
1313 class ValeurPointManager(NoDeleteManager):
1314
1315 def get_query_set(self):
1316 return super(ValeurPointManager, self).get_query_set().select_related('devise', 'implantation')
1317
1318
1319 class ValeurPoint_(AUFMetadata):
1320 """Utile pour connaître, pour un Dossier, le salaire de base théorique lié
1321 au classement dans la grille. La ValeurPoint s'obtient par l'implantation
1322 du Poste de ce Dossier : dossier.poste.implantation (pseudo code).
1323
1324 salaire de base = coefficient * valeur du point de l'Implantation du Poste
1325 """
1326
1327 actuelles = ValeurPointManager()
1328
1329 valeur = models.FloatField(null=True)
1330 devise = models.ForeignKey('Devise', db_column='devise', related_name='+',)
1331 implantation = models.ForeignKey(ref.Implantation,
1332 db_column='implantation',
1333 related_name='%(app_label)s_valeur_point')
1334 # Méta
1335 annee = models.IntegerField()
1336
1337 class Meta:
1338 ordering = ['-annee', 'implantation__nom']
1339 abstract = True
1340 verbose_name = u"Valeur du point"
1341 verbose_name_plural = u"Valeurs du point"
1342
1343 def __unicode__(self):
1344 return u'%s %s %s [%s] %s' % (self.devise.code, self.annee, self.valeur, self.implantation.nom_court, self.devise.nom)
1345
1346
1347 class ValeurPoint(ValeurPoint_):
1348 __doc__ = ValeurPoint_.__doc__
1349
1350
1351
1352 class Devise(AUFMetadata):
1353 """Devise monétaire.
1354 """
1355
1356 objects = DeviseManager()
1357
1358 archive = models.BooleanField(verbose_name=u"Archivé", default=False)
1359 code = models.CharField(max_length=10, unique=True)
1360 nom = models.CharField(max_length=255)
1361
1362 class Meta:
1363 ordering = ['code']
1364 verbose_name = u"Devise"
1365 verbose_name_plural = u"Devises"
1366
1367 def __unicode__(self):
1368 return u'%s - %s' % (self.code, self.nom)
1369
1370 class TypeContrat(AUFMetadata):
1371 """Type de contrat.
1372 """
1373 nom = models.CharField(max_length=255)
1374 nom_long = models.CharField(max_length=255)
1375
1376 class Meta:
1377 ordering = ['nom']
1378 verbose_name = u"Type de contrat"
1379 verbose_name_plural = u"Types de contrat"
1380
1381 def __unicode__(self):
1382 return u'%s' % (self.nom)
1383
1384
1385 ### AUTRES
1386
1387 class ResponsableImplantation(AUFMetadata):
1388 """Le responsable d'une implantation.
1389 Anciennement géré sur le Dossier du responsable.
1390 """
1391 employe = models.ForeignKey('Employe', db_column='employe',
1392 related_name='+',
1393 null=True, blank=True)
1394 implantation = models.ForeignKey(ref.Implantation,
1395 db_column='implantation', related_name='+',
1396 unique=True)
1397
1398 def __unicode__(self):
1399 return u'%s : %s' % (self.implantation, self.employe)
1400
1401 class Meta:
1402 ordering = ['implantation__nom']
1403 verbose_name = "Responsable d'implantation"
1404 verbose_name_plural = "Responsables d'implantation"
1405