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