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