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