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