Commit | Line | Data |
---|---|---|
e9bbd6ba | 1 | # -*- encoding: utf-8 -*- |
22343fe7 | 2 | |
fc62be5d | 3 | from collections import defaultdict |
c9122f1d | 4 | from datetime import date |
fc62be5d | 5 | from decimal import Decimal |
9b0a8554 | 6 | |
9f55f18a | 7 | import pygraphviz as pgv |
edbc9e37 | 8 | from reversion.models import Revision |
f80b83c6 | 9 | |
d104b0ae | 10 | from auf.django.references import models as ref |
4ba84959 | 11 | from django.conf import settings |
e90b19fd | 12 | from django.contrib.auth.decorators import login_required |
dcd1b959 | 13 | from django.core.urlresolvers import reverse |
fbe60a9d | 14 | from django.db.models import Q |
4ba84959 | 15 | from django.http import HttpResponse |
edbc9e37 | 16 | from django.shortcuts import render, get_object_or_404, redirect |
8e26a99b | 17 | |
018c8eaf | 18 | from project.decorators import drh_or_admin_required |
ae99002a | 19 | from project.decorators import region_protected |
afd3be54 | 20 | from project.groups import get_employe_from_user |
c3550a05 OL |
21 | from project import groups |
22 | from project.decorators import in_one_of_group, in_drh_or_admin | |
e9bbd6ba | 23 | |
fc62be5d | 24 | from project.rh import ods |
4ba84959 | 25 | from project.rh import graph as rh_graph |
fc62be5d | 26 | from project.rh import models as rh |
890b80e7 | 27 | from project.rh.change_list import RechercheTemporelle |
fc62be5d | 28 | from project.rh.forms import MasseSalarialeForm |
d104b0ae | 29 | from project.rh.lib import get_lookup_params |
890b80e7 | 30 | from project.rh.templatetags.rapports import SortHeaders |
edbc9e37 | 31 | from project.rh.historique import get_active_revisions, TodoForm |
9817fed5 | 32 | |
e0a465f2 | 33 | TWOPLACES = rh.TWOPLACES |
fc62be5d | 34 | |
22343fe7 | 35 | |
6219cf26 BS |
36 | def devises(): |
37 | liste = [] | |
38 | for d in rh.Devise.objects.all(): | |
39 | annee = date.today().year | |
40 | taux = rh.TauxChange.objects.filter(annee=annee, devise=d) | |
41 | data = {} | |
42 | if len(taux) == 0: | |
43 | data['taux_euro'] = 0 | |
44 | else: | |
45 | data['taux_euro'] = taux[0].taux | |
46 | data['devise_code'] = d.id | |
47 | liste.append(data) | |
7d8f6789 | 48 | |
6219cf26 BS |
49 | return liste |
50 | ||
51 | ||
c9122f1d | 52 | @login_required |
d9a82745 | 53 | @drh_or_admin_required |
c9122f1d | 54 | def profil(request): |
55 | """Profil personnel de l'employé - éditable""" | |
d104b0ae | 56 | employe = get_employe_from_user(request.user) |
7d0c2206 | 57 | c = { |
8b3f6ff5 DB |
58 | 'user': request.user, |
59 | 'employe': employe, | |
7d0c2206 | 60 | } |
890b80e7 | 61 | return render(request, 'rh/profil.html', c) |
22343fe7 OL |
62 | |
63 | ||
3411ac33 | 64 | @login_required |
d9a82745 | 65 | @drh_or_admin_required |
a9faef67 | 66 | def employes_liste(request): |
67 | """Liste des employés.""" | |
5ea6b5bb | 68 | today = date.today() |
69 | employes = rh.Employe.objects \ | |
5ea6b5bb | 70 | .exclude(dossiers__date_debut__gt=today) \ |
71 | .exclude(dossiers__date_fin__lt=today) \ | |
72 | .order_by('nom') | |
a9faef67 | 73 | c = { |
22343fe7 OL |
74 | 'user': request.user, |
75 | 'employes': employes, | |
a9faef67 | 76 | } |
890b80e7 | 77 | return render(request, 'rh/employes_liste.html', c) |
22343fe7 OL |
78 | |
79 | ||
3411ac33 | 80 | @login_required |
d9a82745 | 81 | @drh_or_admin_required |
a9faef67 | 82 | def employe(request, id): |
83 | """Information publique sur un employé.""" | |
84 | try: | |
85 | employe = rh.Employe.objects.get(pk=id) | |
86 | except: | |
87 | employe = rh.Employe.objects.none() | |
88 | c = { | |
22343fe7 OL |
89 | 'user': request.user, |
90 | 'employe': employe, | |
a9faef67 | 91 | } |
890b80e7 | 92 | return render(request, 'rh/employe.html', c) |
63e17dff PP |
93 | |
94 | ||
08faf06e | 95 | @login_required |
3383b2d1 OL |
96 | @in_one_of_group((groups.CORRESPONDANT_RH, |
97 | groups.ADMINISTRATEURS, | |
98 | groups.DIRECTEUR_DE_BUREAU, | |
99 | groups.DRH_NIVEAU_1, | |
100 | groups.DRH_NIVEAU_2)) | |
f2d65e83 | 101 | def rapports_contrat(request): |
dd18f6e3 | 102 | # statut default = actif? |
dcd1b959 | 103 | if 'HTTP_REFERER' in request.META.keys(): |
58348377 EMS |
104 | referer = request.META['HTTP_REFERER'] |
105 | referer = "/".join(referer.split('/')[3:]) | |
106 | referer = "/%s" % referer.split('?')[0] | |
107 | if referer != reverse('rhr_contrats'): | |
108 | params = request.GET.copy() | |
109 | params.update({'statut': 'Actif'}) | |
110 | request.GET = params | |
f2d65e83 | 111 | |
00ca4d9f EMS |
112 | contrats = rh.Contrat.objects.select_related( |
113 | 'dossier', 'dossier__poste', 'dossier__poste__implantation', | |
114 | 'type_contrat', 'dossier__employe' | |
115 | ) | |
22343fe7 | 116 | |
dd18f6e3 | 117 | # filters et recherche temporelle |
6bee05ff | 118 | cl = RechercheTemporelle(dict(request.GET.items()), rh.Contrat) |
dd18f6e3 | 119 | lookup_params = get_lookup_params(request) |
f0f6b03e | 120 | lookup_params = cl.purge_params(lookup_params) |
dcd1b959 OL |
121 | q_temporel = cl.get_q_temporel(contrats) |
122 | q = Q(**lookup_params) & q_temporel | |
45066657 | 123 | contrats = contrats.filter(q) |
3383b2d1 OL |
124 | user_groups = [g.name for g in request.user.groups.all()] |
125 | if groups.CORRESPONDANT_RH in user_groups or\ | |
126 | groups.ADMINISTRATEURS in user_groups or\ | |
127 | groups.DIRECTEUR_DE_BUREAU in user_groups: | |
f96ec25c | 128 | employe = get_employe_from_user(request.user) |
b0cf30b8 EMS |
129 | q = q & Q( |
130 | dossier__poste__implantation__zone_administrative=( | |
131 | employe.implantation.zone_administrative | |
132 | ) | |
133 | ) | |
f96ec25c | 134 | |
c3550a05 | 135 | contrats = contrats.filter(q) |
f0f6b03e | 136 | |
dd18f6e3 | 137 | # tri |
65e02621 | 138 | if 'o' in request.GET: |
00ca4d9f EMS |
139 | contrats = contrats.order_by( |
140 | ('-' if request.GET.get('ot') == "desc" else '') + request.GET['o'] | |
141 | ) | |
f2d65e83 | 142 | |
dd18f6e3 | 143 | # affichage |
8ee135b7 | 144 | employes = set([c.dossier.employe_id for c in contrats]) |
65e02621 | 145 | headers = [ |
dd18f6e3 | 146 | ("dossier__employe__id", u"#"), |
d50a5e5d | 147 | ("dossier__employe__nom", u"Employé"), |
dd18f6e3 DB |
148 | ("dossier", u"Dossier : Poste"), |
149 | ("type_contrat__nom", u"Contrat"), | |
150 | ("date_debut", u"Début contrat"), | |
151 | ("date_fin", u"Fin contrat"), | |
152 | ("dossier__statut_residence", u"Statut"), | |
b0cf30b8 | 153 | ("dossier__poste__implantation__zone_administrative", u"Zone administrative"), |
d50a5e5d | 154 | ("dossier__poste__implantation", u"Implantation"), |
65e02621 | 155 | ] |
dd18f6e3 DB |
156 | h = SortHeaders( |
157 | request, headers, order_field_type="ot", order_field="o", | |
158 | not_sortable=('dossier',) | |
159 | ) | |
65e02621 | 160 | |
f2d65e83 | 161 | c = { |
00ca4d9f | 162 | 'cl': cl, |
f2d65e83 PP |
163 | 'title': 'Rapport des contrats', |
164 | 'contrats': contrats, | |
00ca4d9f | 165 | 'count_employe': len(employes), |
65e02621 | 166 | 'headers': list(h.headers()), |
f2d65e83 | 167 | } |
890b80e7 | 168 | return render(request, 'rh/rapports/contrats.html', c) |
e2c0b1ac PP |
169 | |
170 | ||
02c1b3dc | 171 | @login_required |
3383b2d1 OL |
172 | @in_one_of_group((groups.CORRESPONDANT_RH, |
173 | groups.ADMINISTRATEURS, | |
174 | groups.DIRECTEUR_DE_BUREAU, | |
175 | groups.DRH_NIVEAU_1, | |
176 | groups.DRH_NIVEAU_2)) | |
6b7cf654 | 177 | def rapports_employes_sans_contrat(request): |
5db1c5a3 DB |
178 | """ |
179 | Employé sans contrat = a un Dossier qui n'a pas de Contrat associé | |
180 | Employé avec contrat échu = a un Dossier avec un Contrat se terminant hier | |
181 | au plus tard... (aujourd'hui = ok, pas date de fin = illimité donc ok) | |
182 | """ | |
1a7b9e36 | 183 | lookup_params = get_lookup_params(request) |
c8b22fd1 | 184 | |
82c5e37d | 185 | # régionalisation |
3383b2d1 | 186 | user_groups = [g.name for g in request.user.groups.all()] |
3383b2d1 OL |
187 | if groups.CORRESPONDANT_RH in user_groups or\ |
188 | groups.ADMINISTRATEURS in user_groups or\ | |
189 | groups.DIRECTEUR_DE_BUREAU in user_groups: | |
219ae355 | 190 | employe = get_employe_from_user(request.user) |
b0cf30b8 EMS |
191 | lookup_params['poste__implantation__zone_administrative'] = \ |
192 | employe.implantation.zone_administrative | |
219ae355 | 193 | |
a20fa2a4 | 194 | dossiers = rh.Dossier.objects.sans_contrats_ou_echus(**lookup_params) |
a20fa2a4 OL |
195 | dossiers_ids = [d.id for d in dossiers] |
196 | employes = rh.Employe.objects.filter(id__in=dossiers_ids).distinct().count() | |
a623d1b2 | 197 | |
5db1c5a3 | 198 | # tri |
c8b22fd1 | 199 | if 'o' in request.GET: |
5db1c5a3 | 200 | dossiers = dossiers.order_by( |
00ca4d9f EMS |
201 | ('-' if request.GET.get('ot') == "desc" else '') + request.GET['o'] |
202 | ) | |
c8b22fd1 | 203 | |
6b7cf654 | 204 | # affichage |
c8b22fd1 | 205 | headers = [ |
5db1c5a3 DB |
206 | ("employe__id", u"#"), |
207 | ("employe__nom", u"Employé"), | |
1a7b9e36 | 208 | ("dossier", u"Dossier : Poste"), |
5db1c5a3 DB |
209 | ("rh_contrats__type_contrat", u"Contrat"), |
210 | ("rh_contrats__date_debut", u"Début contrat"), | |
211 | ("rh_contrats__date_fin", u"Fin contrat"), | |
212 | ("statut_residence", u"Statut"), | |
b0cf30b8 | 213 | ("poste__implantation__zone_administrative__code", u"Zone administrative"), |
5db1c5a3 | 214 | ("poste__implantation__nom", u"Implantation"), |
c8b22fd1 | 215 | ] |
00ca4d9f EMS |
216 | h = SortHeaders( |
217 | request, headers, order_field_type="ot", order_field="o", | |
5db1c5a3 | 218 | not_sortable=('dossier',) |
00ca4d9f | 219 | ) |
c8b22fd1 JPC |
220 | |
221 | c = { | |
5db1c5a3 | 222 | 'title': u'Rapport des employés sans contrat ou contrat échu', |
c8b22fd1 | 223 | 'employes': employes, |
5db1c5a3 | 224 | 'dossiers': dossiers, |
c8b22fd1 JPC |
225 | 'headers': list(h.headers()), |
226 | } | |
227 | ||
890b80e7 | 228 | return render(request, 'rh/rapports/employes_sans_contrat.html', c) |
00ca4d9f | 229 | |
c8b22fd1 JPC |
230 | |
231 | @login_required | |
3383b2d1 OL |
232 | @in_one_of_group((groups.CORRESPONDANT_RH, |
233 | groups.ADMINISTRATEURS, | |
234 | groups.DIRECTEUR_DE_BUREAU, | |
235 | groups.DRH_NIVEAU_1, | |
236 | groups.DRH_NIVEAU_2)) | |
98d6eb6c | 237 | def rapports_masse_salariale(request): |
fc62be5d EMS |
238 | form = MasseSalarialeForm(request.user, request.GET) |
239 | if 'annee' in request.GET and form.is_valid(): | |
b0cf30b8 | 240 | zone_administrative = form.cleaned_data['zone_administrative'] |
fc62be5d EMS |
241 | implantation = form.cleaned_data['implantation'] |
242 | annee = form.cleaned_data['annee'] | |
243 | debut_annee = date(annee, 1, 1) | |
244 | fin_annee = date(annee, 12, 31) | |
245 | jours_annee = (fin_annee - debut_annee).days + 1 | |
246 | today = date.today() | |
247 | ||
248 | # Récupérer les dossiers actifs | |
249 | dossiers = rh.Dossier.objects \ | |
250 | .actifs(annee=annee) \ | |
251 | .select_related( | |
252 | 'poste', 'poste__implantation', | |
b0cf30b8 | 253 | 'poste__implantation__zone_administrative', |
fc62be5d EMS |
254 | 'poste__implantation__adresse_physique_pays', |
255 | 'employe', 'poste__type_poste', 'classement', | |
256 | 'statut', 'organisme_bstg' | |
257 | ) \ | |
258 | .extra( | |
fc62be5d | 259 | select={ |
471fd30f EMS |
260 | 'valeur_point': ( |
261 | 'SELECT valeur FROM rh_valeurpoint ' | |
262 | 'WHERE annee = %d ' | |
263 | 'AND implantation = ref_implantation.id' % annee | |
264 | ), | |
265 | 'valeur_point_devise': ( | |
266 | 'SELECT d.code ' | |
267 | 'FROM rh_valeurpoint vp ' | |
268 | 'INNER JOIN rh_devise d ON d.id = vp.devise ' | |
269 | 'WHERE annee = %d ' | |
270 | 'AND implantation = ref_implantation.id' % annee | |
271 | ) | |
fc62be5d | 272 | } |
48c0abfe | 273 | ) |
b0cf30b8 EMS |
274 | if zone_administrative: |
275 | dossiers = dossiers.filter(poste__implantation__zone_administrative=zone_administrative) | |
fc62be5d EMS |
276 | if implantation: |
277 | dossiers = dossiers.filter(poste__implantation=implantation) | |
278 | ||
279 | # Récupérer les rémunérations actives | |
a6ed66f9 | 280 | remun_actives = rh.Remuneration.objects \ |
fc62be5d | 281 | .actifs(annee=annee) \ |
a6ed66f9 OL |
282 | .select_related('devise', 'type') |
283 | ||
284 | remuns = remun_actives.extra( | |
fc62be5d EMS |
285 | tables=['rh_tauxchange'], |
286 | where=[ | |
e92cf771 EMS |
287 | 'rh_tauxchange.devise = rh_devise.id', |
288 | 'rh_tauxchange.annee = ' | |
289 | '(SELECT MAX(annee) FROM rh_tauxchange ' | |
290 | 'WHERE devise = rh_devise.id ' | |
291 | 'AND annee <= %s)', | |
fc62be5d EMS |
292 | ], |
293 | params=[annee], | |
294 | select={ | |
295 | 'taux_change': 'rh_tauxchange.taux' | |
296 | } | |
297 | ) | |
a6ed66f9 OL |
298 | |
299 | if len(remun_actives) != len(remuns): | |
300 | raise rh.RemunIntegrityException("Toutes les remunerations ne disposent pas d'un " | |
301 | "taux de change pour l'année %d" % annee) | |
302 | ||
b0cf30b8 EMS |
303 | if zone_administrative: |
304 | remuns = remuns.filter(dossier__poste__implantation__zone_administrative=zone_administrative) | |
471fd30f EMS |
305 | if implantation: |
306 | remuns = remuns.filter(dossier__poste__implantation=implantation) | |
fc62be5d | 307 | remuns_par_dossier = defaultdict(list) |
471fd30f | 308 | types_remun = set() |
fc62be5d | 309 | for remun in remuns: |
471fd30f | 310 | types_remun.add(remun.type_id) |
fc62be5d EMS |
311 | remuns_par_dossier[remun.dossier_id].append(remun) |
312 | ||
313 | # Récupérer les types de rémunération par nature | |
314 | types_remun_par_nature = defaultdict(list) | |
315 | for type in rh.TypeRemuneration.objects.all(): | |
471fd30f EMS |
316 | if type.id in types_remun: |
317 | types_remun_par_nature[type.nature_remuneration].append(type) | |
fc62be5d EMS |
318 | titres_traitements = [ |
319 | t.nom for t in types_remun_par_nature[u'Traitement'] | |
320 | ] | |
321 | titres_indemnites = [ | |
322 | t.nom for t in types_remun_par_nature[u'Indemnité'] | |
323 | ] | |
324 | titres_primes = [ | |
325 | t.nom for t in types_remun_par_nature[u'Accessoire'] | |
326 | ] | |
327 | titres_charges = [ | |
328 | t.nom for t in types_remun_par_nature[u'Charges'] | |
329 | ] | |
df37184c | 330 | |
fc62be5d EMS |
331 | # Boucler sur les dossiers et préprarer les lignes du rapport |
332 | lignes = [] | |
333 | masse_salariale_totale = 0 | |
334 | for dossier in dossiers: | |
335 | debut_cette_annee = \ | |
336 | max(dossier.date_debut or debut_annee, debut_annee) | |
337 | fin_cette_annee = \ | |
338 | min(dossier.date_fin or fin_annee, fin_annee) | |
339 | jours = (fin_cette_annee - debut_cette_annee).days + 1 | |
340 | ||
341 | remuns = remuns_par_dossier[dossier.id] | |
342 | devises = set(remun.devise.code for remun in remuns) | |
343 | if len(devises) == 1: | |
344 | devise = remuns[0].devise.code | |
345 | montant_remun = lambda r: r.montant | |
346 | taux_change = Decimal(str(remuns[0].taux_change)) | |
347 | else: | |
348 | devise = 'EUR' | |
349 | montant_remun = lambda r: ( | |
350 | r.montant * Decimal(str(r.taux_change)) | |
48c0abfe | 351 | ) |
fc62be5d EMS |
352 | taux_change = Decimal(1) |
353 | ||
354 | remuns_par_type = defaultdict(lambda: 0) | |
e0a465f2 | 355 | |
fc62be5d | 356 | for remun in remuns: |
e0a465f2 BS |
357 | montant_ajuste_euros = remun.montant_ajuste_euros(annee=annee) |
358 | if len(devises) == 1: | |
359 | devise = remuns[0].devise.code | |
360 | montant_ajuste = ( | |
361 | montant_ajuste_euros / | |
362 | Decimal(str(remuns[0].taux_change)) | |
363 | ).quantize(TWOPLACES) | |
ff9bc43d | 364 | else: |
e0a465f2 BS |
365 | montant_ajuste = montant_ajuste_euros.quantize(TWOPLACES) |
366 | devise = 'EUR' | |
367 | remuns_par_type[remun.type_id] += montant_ajuste | |
368 | ||
fc62be5d EMS |
369 | traitements = [ |
370 | remuns_par_type[type.id] | |
371 | for type in types_remun_par_nature[u'Traitement'] | |
372 | ] | |
373 | indemnites = [ | |
374 | remuns_par_type[type.id] | |
375 | for type in types_remun_par_nature[u'Indemnité'] | |
376 | ] | |
377 | primes = [ | |
378 | remuns_par_type[type.id] | |
379 | for type in types_remun_par_nature[u'Accessoire'] | |
380 | ] | |
381 | charges = [ | |
382 | remuns_par_type[type.id] | |
383 | for type in types_remun_par_nature[u'Charges'] | |
384 | ] | |
385 | masse_salariale = sum(remuns_par_type.values()) | |
386 | masse_salariale_eur = ( | |
387 | masse_salariale * taux_change | |
388 | ).quantize(TWOPLACES) | |
389 | masse_salariale_totale += masse_salariale_eur | |
390 | ||
391 | if dossier.valeur_point and dossier.classement \ | |
392 | and dossier.classement.coefficient and dossier.regime_travail: | |
393 | salaire_theorique = (Decimal(str( | |
394 | dossier.valeur_point * dossier.classement.coefficient | |
395 | )) * dossier.regime_travail / 100).quantize(TWOPLACES) | |
396 | else: | |
397 | salaire_theorique = None | |
398 | ||
399 | lignes.append({ | |
400 | 'dossier': dossier, | |
401 | 'poste': dossier.poste, | |
402 | 'date_debut': ( | |
403 | dossier.date_debut | |
404 | if dossier.date_debut and dossier.date_debut.year == annee | |
405 | else None | |
406 | ), | |
407 | 'date_fin': ( | |
408 | dossier.date_fin | |
409 | if dossier.date_fin and dossier.date_fin.year == annee | |
410 | else None | |
411 | ), | |
412 | 'jours': jours, | |
413 | 'devise': devise, | |
414 | 'valeur_point': dossier.valeur_point, | |
415 | 'valeur_point_devise': dossier.valeur_point_devise, | |
416 | 'regime_travail': dossier.regime_travail, | |
417 | 'local_expatrie': { | |
418 | 'local': 'L', 'expat': 'E' | |
419 | }.get(dossier.statut_residence), | |
420 | 'salaire_bstg': remuns_par_type[2], | |
421 | 'salaire_bstg_eur': ( | |
422 | (remuns_par_type[2] * taux_change).quantize(TWOPLACES) | |
423 | ), | |
424 | 'salaire_theorique': salaire_theorique, | |
425 | 'traitements': traitements, | |
426 | 'total_traitements': sum(traitements), | |
427 | 'indemnites': indemnites, | |
428 | 'total_indemnites': sum(indemnites), | |
429 | 'primes': primes, | |
430 | 'total_primes': sum(primes), | |
431 | 'charges': charges, | |
432 | 'total_charges': sum(charges), | |
433 | 'masse_salariale': masse_salariale, | |
434 | 'masse_salariale_eur': masse_salariale_eur, | |
435 | }) | |
00ca4d9f | 436 | |
fc62be5d EMS |
437 | # Récupérer les postes actifs pour déterminer le nombre de jours |
438 | # vacants. | |
50e2340a EMS |
439 | postes = rh.Poste.objects.actifs(annee=annee) \ |
440 | .select_related( | |
441 | 'devise_max', 'valeur_point_max', 'classement_max' | |
442 | ) \ | |
443 | .extra( | |
444 | tables=['rh_tauxchange'], | |
445 | where=[ | |
446 | 'rh_tauxchange.annee = %s', | |
447 | 'rh_tauxchange.devise = rh_devise.id' | |
448 | ], | |
449 | params=[annee], | |
450 | select={ | |
451 | 'taux_change': 'rh_tauxchange.taux' | |
452 | } | |
453 | ) | |
b0cf30b8 EMS |
454 | if zone_administrative: |
455 | postes = postes.filter(implantation__zone_administrative=zone_administrative) | |
50e2340a EMS |
456 | if implantation: |
457 | postes = postes.filter(implantation=implantation) | |
458 | postes = list(postes) | |
fc62be5d EMS |
459 | postes_par_id = dict((poste.id, poste) for poste in postes) |
460 | jours_vacants_date = dict( | |
461 | (poste.id, max(today, poste.date_debut or today)) | |
462 | for poste in postes | |
463 | ) | |
464 | jours_vacants = defaultdict(lambda: 0) | |
465 | for dossier in rh.Dossier.objects.actifs(annee=annee) \ | |
466 | .order_by('date_debut'): | |
467 | if dossier.poste_id not in jours_vacants_date: | |
468 | continue | |
469 | derniere_date = jours_vacants_date[dossier.poste_id] | |
470 | if dossier.date_debut is not None: | |
471 | jours_vacants[dossier.poste_id] += max(( | |
472 | dossier.date_debut - derniere_date | |
473 | ).days - 1, 0) | |
474 | jours_vacants_date[dossier.poste_id] = max( | |
475 | min(dossier.date_fin or fin_annee, fin_annee), | |
476 | derniere_date | |
48c0abfe | 477 | ) |
24b5b57f EMS |
478 | for poste_id, derniere_date in jours_vacants_date.iteritems(): |
479 | jours_vacants[poste_id] += max(( | |
480 | min(postes_par_id[poste_id].date_fin or fin_annee, fin_annee) - | |
481 | derniere_date | |
482 | ).days, 0) | |
48c0abfe | 483 | |
fc62be5d EMS |
484 | # Ajouter les lignes des postes vacants au rapport |
485 | for poste_id, jours in jours_vacants.iteritems(): | |
486 | if jours == 0: | |
487 | continue | |
488 | poste = postes_par_id[poste_id] | |
489 | if poste.valeur_point_max and poste.classement_max \ | |
490 | and poste.classement_max.coefficient and poste.regime_travail: | |
491 | salaire_theorique = (Decimal(str( | |
492 | poste.valeur_point_max.valeur * | |
493 | poste.classement_max.coefficient | |
494 | )) * poste.regime_travail / 100).quantize(TWOPLACES) | |
495 | else: | |
496 | salaire_theorique = None | |
df37184c | 497 | |
fc62be5d EMS |
498 | local_expatrie = '/'.join( |
499 | (['L'] if poste.local else []) + | |
500 | (['E'] if poste.expatrie else []) | |
501 | ) | |
df37184c | 502 | |
fc62be5d EMS |
503 | salaire = ( |
504 | poste.salaire_max * jours / jours_annee * | |
505 | poste.regime_travail / 100 | |
506 | ).quantize(TWOPLACES) | |
507 | indemnites = ( | |
508 | poste.indemn_max * jours / jours_annee * | |
509 | poste.regime_travail / 100 | |
510 | ).quantize(TWOPLACES) | |
511 | charges = ( | |
512 | poste.autre_max * jours / jours_annee * | |
513 | poste.regime_travail / 100 | |
514 | ).quantize(TWOPLACES) | |
515 | masse_salariale = salaire + indemnites + charges | |
516 | masse_salariale_eur = ( | |
517 | masse_salariale * Decimal(str(poste.taux_change)) | |
518 | ).quantize(TWOPLACES) | |
519 | masse_salariale_totale += masse_salariale_eur | |
520 | ||
521 | lignes.append({ | |
522 | 'poste': poste, | |
523 | 'regime_travail': poste.regime_travail, | |
524 | 'local_expatrie': local_expatrie, | |
525 | 'jours': jours, | |
526 | 'devise': poste.devise_max and poste.devise_max.code, | |
527 | 'valeur_point': ( | |
528 | poste.valeur_point_max and poste.valeur_point_max.valeur | |
529 | ), | |
530 | 'valeur_point_devise': ( | |
531 | poste.valeur_point_max and \ | |
532 | poste.valeur_point_max.devise.code | |
533 | ), | |
534 | 'salaire_theorique': salaire_theorique, | |
24b5b57f EMS |
535 | 'traitements': [0] * len(titres_traitements), |
536 | 'total_traitements': salaire, | |
537 | 'indemnites': [0] * len(titres_indemnites), | |
fc62be5d | 538 | 'total_indemnites': indemnites, |
24b5b57f EMS |
539 | 'primes': [0] * len(titres_primes), |
540 | 'total_primes': 0, | |
541 | 'charges': [0] * len(titres_charges), | |
fc62be5d EMS |
542 | 'total_charges': charges, |
543 | 'masse_salariale': masse_salariale, | |
544 | 'masse_salariale_eur': masse_salariale_eur | |
545 | }) | |
546 | if 'ods' in request.GET: | |
547 | doc = ods.masse_salariale( | |
548 | lignes=lignes, | |
549 | annee=annee, | |
550 | titres_traitements=titres_traitements, | |
551 | titres_indemnites=titres_indemnites, | |
552 | titres_primes=titres_primes, | |
553 | titres_charges=titres_charges, | |
554 | masse_salariale_totale=masse_salariale_totale | |
555 | ) | |
556 | response = HttpResponse( | |
557 | mimetype='vnd.oasis.opendocument.spreadsheet' | |
558 | ) | |
559 | response['Content-Disposition'] = 'filename=masse-salariale.ods' | |
560 | doc.write(response) | |
561 | return response | |
562 | else: | |
563 | return render(request, 'rh/rapports/masse_salariale.html', { | |
564 | 'form': form, | |
565 | 'titres_traitements': titres_traitements, | |
566 | 'titres_indemnites': titres_indemnites, | |
567 | 'titres_primes': titres_primes, | |
568 | 'titres_charges': titres_charges, | |
569 | 'masse_salariale_totale': masse_salariale_totale, | |
570 | 'lignes': lignes, | |
571 | 'annee': annee | |
572 | }) | |
573 | return render(request, 'rh/rapports/masse_salariale.html', { | |
574 | 'form': form | |
575 | }) | |
98d6eb6c JPC |
576 | |
577 | ||
578 | @login_required | |
579 | @drh_or_admin_required | |
857b5c24 JPC |
580 | def rapports_postes_modelisation(request): |
581 | c = {} | |
582 | data = [] | |
3c1ba807 | 583 | |
7bf28694 EMS |
584 | for categorie in rh.CategorieEmploi.objects.all(): |
585 | types = rh.TypePoste.objects.filter(categorie_emploi=categorie) | |
3c1ba807 JPC |
586 | data_types = [] |
587 | for t in types.all(): | |
588 | postes = rh.Poste.objects.filter(type_poste=t) | |
589 | data_types.append({ | |
590 | 'num_postes': postes.count(), | |
591 | 'postes': postes.all(), | |
7bf28694 | 592 | 'type': categorie, |
857b5c24 | 593 | }) |
3c1ba807 | 594 | |
857b5c24 | 595 | data.append({ |
7bf28694 | 596 | 'categorie': categorie, |
3c1ba807 | 597 | 'nb_types': types.count(), |
00ca4d9f EMS |
598 | 'types': data_types |
599 | }) | |
857b5c24 JPC |
600 | |
601 | c['data'] = data | |
602 | ||
890b80e7 | 603 | return render(request, 'rh/rapports/postes_modelisation.html', c) |
857b5c24 JPC |
604 | |
605 | ||
606 | @login_required | |
607 | @drh_or_admin_required | |
783e077a JPC |
608 | def rapports_postes_implantation(request): |
609 | c = {} | |
610 | data = [] | |
611 | for r in ref.Region.objects.all(): | |
612 | implantations = [] | |
613 | for i in ref.Implantation.objects.filter(region=r): | |
614 | implantations.append({ | |
615 | 'implantation': i, | |
616 | 'postes': rh.Poste.objects.filter(implantation=i), | |
617 | 'num_postes': rh.Poste.objects.filter(implantation=i).count(), | |
618 | }) | |
619 | data.append({ | |
620 | 'region': r, | |
621 | 'implantations': implantations | |
622 | }) | |
623 | ||
624 | c['data'] = data | |
625 | ||
890b80e7 | 626 | return render(request, 'rh/rapports/postes_implantation.html', c) |
783e077a JPC |
627 | |
628 | ||
629 | @login_required | |
630 | @drh_or_admin_required | |
631 | def rapports_postes_service(request): | |
632 | c = {} | |
9988aafb JPC |
633 | data = [] |
634 | for s in rh.Service.objects.all(): | |
635 | postes = rh.Poste.objects.filter(service=s).all() | |
636 | num_postes = rh.Poste.objects.filter(service=s).count() | |
22343fe7 | 637 | data.append({'service': s, 'num_postes': num_postes, 'postes': postes}) |
783e077a | 638 | |
9988aafb | 639 | c['data'] = data |
890b80e7 | 640 | return render(request, 'rh/rapports/postes_service.html', c) |
32373f2e | 641 | |
d104b0ae | 642 | |
32373f2e | 643 | @region_protected(rh.Dossier) |
3ebc0952 | 644 | def dossier_apercu(request, dossier_id): |
baf9b78c | 645 | d = get_object_or_404(rh.Dossier, pk=dossier_id) |
6219cf26 BS |
646 | annees = [ |
647 | x.year for x in d.remunerations().values_list( | |
648 | 'date_debut', flat=True) if x is not None] | |
649 | min_y, max_y = ( | |
650 | (min(annees), max(annees)) if len(annees) | |
651 | else (date.today().year, date.today().year) | |
652 | ) | |
653 | annees = reversed(range(min_y, max_y + 1)) | |
654 | ||
3ebc0952 | 655 | c = { |
838bc59d | 656 | 'title': u"Dossier %s" % (d, ), |
00ca4d9f | 657 | 'is_popup': request.GET.get('_popup', False), |
6219cf26 | 658 | 'devises': devises(), |
00ca4d9f | 659 | 'dossier': d, |
6219cf26 | 660 | 'annees': annees, |
00ca4d9f EMS |
661 | 'pieces': rh.DossierPiece.objects.filter(dossier__exact=d), |
662 | 'contrats': rh.Contrat.objects.filter(dossier__exact=d), | |
663 | 'commentaires': rh.DossierCommentaire.objects.filter(dossier=d).all(), | |
abf91905 | 664 | 'media_url': settings.PRIVE_MEDIA_URL, |
3ebc0952 | 665 | } |
890b80e7 | 666 | return render(request, 'admin/rh/dossier/apercu.html', c) |
00ca4d9f | 667 | |
4a1f2ece | 668 | |
fbe60a9d OL |
669 | @region_protected(rh.Poste) |
670 | def poste_apercu(request, poste_id): | |
c8cb18e3 | 671 | p = get_object_or_404(rh.Poste, pk=poste_id) |
fbe60a9d | 672 | c = { |
c8cb18e3 | 673 | 'title': u"Poste %s" % (p, ), |
00ca4d9f EMS |
674 | 'is_popup': request.GET.get('_popup', False), |
675 | 'poste': p, | |
676 | 'financements': ( | |
677 | rh.PosteFinancement.objects.filter(poste=poste_id).all() | |
678 | ), | |
679 | 'pieces': rh.PostePiece.objects.filter(poste=poste_id).all(), | |
680 | 'dossiers': ( | |
681 | rh.Dossier.objects.filter(poste=poste_id) | |
682 | .order_by("-date_debut").all() | |
683 | ), | |
684 | 'comparaisons': ( | |
685 | rh.PosteComparaison.objects.filter(poste=poste_id).all() | |
686 | ), | |
687 | 'commentaires': ( | |
688 | rh.PosteCommentaire.objects.filter(poste=poste_id).all() | |
689 | ), | |
abf91905 | 690 | 'media_url': settings.PRIVE_MEDIA_URL, |
fbe60a9d | 691 | } |
890b80e7 | 692 | return render(request, 'admin/rh/poste/apercu.html', c) |
00ca4d9f | 693 | |
3383b2d1 OL |
694 | @login_required |
695 | @in_one_of_group((groups.CORRESPONDANT_RH, | |
696 | groups.ADMINISTRATEURS, | |
697 | groups.DIRECTEUR_DE_BUREAU, | |
698 | groups.DRH_NIVEAU_1, | |
699 | groups.DRH_NIVEAU_2)) | |
4a1f2ece OL |
700 | def employe_apercu(request, employe_id): |
701 | employe = get_object_or_404(rh.Employe, pk=employe_id) | |
3383b2d1 | 702 | user_groups = [g.name for g in request.user.groups.all()] |
fbe60a9d OL |
703 | dossiers = None |
704 | ||
cd4c1b65 | 705 | if in_drh_or_admin(request.user): |
fbe60a9d | 706 | q = Q(employe=employe) |
22343fe7 | 707 | |
3383b2d1 OL |
708 | if groups.CORRESPONDANT_RH in user_groups or\ |
709 | groups.ADMINISTRATEURS in user_groups or\ | |
710 | groups.DIRECTEUR_DE_BUREAU in user_groups: | |
bcc0c52d | 711 | employe_connecte = get_employe_from_user(request.user) |
b0cf30b8 EMS |
712 | q = Q(employe=employe) & Q( |
713 | poste__implantation__zone_administrative=( | |
714 | employe_connecte.implantation.zone_administrative | |
715 | ) | |
716 | ) | |
717 | ||
22343fe7 OL |
718 | dossiers = rh.Dossier.objects.filter(q).order_by('-date_debut') |
719 | ||
e0a465f2 BS |
720 | dossier_principal = employe.dossier_principal() |
721 | ||
4a1f2ece | 722 | c = { |
ce740bb5 | 723 | 'title': u"Employe %s" % (employe, ), |
00ca4d9f EMS |
724 | 'is_popup': request.GET.get('_popup', False), |
725 | 'employe': employe, | |
726 | 'dossiers': dossiers, | |
e0a465f2 | 727 | 'dossier_principal': dossier_principal, |
abf91905 | 728 | 'media_url': settings.PRIVE_MEDIA_URL, |
d2c401bc | 729 | 'annee': date.today().year, |
e0a465f2 | 730 | 'devises': devises(), |
4a1f2ece | 731 | } |
890b80e7 | 732 | return render(request, 'admin/rh/employe/apercu.html', c) |
00ca4d9f | 733 | |
150d83ec | 734 | |
77bd83d1 | 735 | @login_required |
3383b2d1 OL |
736 | @in_one_of_group((groups.CORRESPONDANT_RH, |
737 | groups.ADMINISTRATEURS, | |
738 | groups.DIRECTEUR_DE_BUREAU, | |
739 | groups.DRH_NIVEAU_1, | |
740 | groups.DRH_NIVEAU_2)) | |
56264a85 | 741 | def organigrammes_employe(request, id, level="all"): |
08faf06e | 742 | poste = get_object_or_404(rh.Poste, pk=id) |
00ca4d9f EMS |
743 | dossiers_by_poste = dict( |
744 | (d.poste_id, d) | |
745 | for d in rh.Dossier.objects.select_related('employe', 'poste').all() | |
746 | ) | |
5c0f1778 JPC |
747 | postes_by_id = dict((p.id, p) for p in rh.Poste.objects.all()) |
748 | ||
749 | e = dossiers_by_poste[poste.id].employe | |
750 | name = u"Organigramme de [%s] %s %s" % (e.id, e.nom.upper(), e.prenom) | |
58014aec | 751 | graph = pgv.AGraph() |
5c0f1778 | 752 | |
f187a10f | 753 | if rh.Poste.objects.filter(responsable=poste).count() > 0: |
58014aec JPC |
754 | postes_handle = [poste] |
755 | while postes_handle: | |
00ca4d9f EMS |
756 | postes_handle = rh.Poste.objects.select_related('implantation') \ |
757 | .filter( | |
758 | Q(date_fin__gt=date.today()) | Q(date_fin=None), | |
759 | Q(date_debut__lt=date.today()) | Q(date_debut=None), | |
760 | responsable__in=postes_handle | |
c3550a05 | 761 | ).exclude(responsable=None).all() |
32373f2e | 762 | |
58014aec JPC |
763 | for p in postes_handle: |
764 | if p.responsable_id != p.id: | |
00ca4d9f EMS |
765 | graph.add_edge( |
766 | dossiers_by_poste[p.responsable_id].poste_id, p.id | |
767 | ) | |
13ad5ad5 | 768 | |
f187a10f | 769 | else: |
5c0f1778 JPC |
770 | graph.add_node(poste.id) |
771 | ||
4afec2e1 JPC |
772 | if level != "all": |
773 | postes_niveau = [poste.id] | |
774 | for niveau in range(int(level)): | |
00ca4d9f EMS |
775 | postes_niveau = [ |
776 | p.id for p in | |
777 | rh.Poste.objects.filter(responsable__in=postes_niveau).all() | |
778 | ] | |
4afec2e1 JPC |
779 | |
780 | while postes_niveau: | |
00ca4d9f EMS |
781 | postes_niveau = [ |
782 | p.id for p in | |
783 | rh.Poste.objects.filter(responsable__in=postes_niveau).all() | |
784 | ] | |
4afec2e1 JPC |
785 | if postes_niveau: |
786 | for p in postes_niveau: | |
787 | if graph.has_node(p): | |
4cc81304 | 788 | graph.delete_node(p) |
4afec2e1 | 789 | |
58014aec | 790 | a = graph |
4afec2e1 | 791 | a.name = name.encode('ascii', 'xmlcharrefreplace') |
5c0f1778 JPC |
792 | |
793 | poste_remontant = poste | |
794 | while poste_remontant.responsable_id: | |
795 | a.add_edge(poste_remontant.responsable_id, poste_remontant.id) | |
796 | poste_remontant = poste_remontant.responsable | |
797 | ||
7000b7b3 | 798 | rh_graph.bind_poste_to_graph(request.user, a, postes_by_id) |
5c0f1778 JPC |
799 | #a.graph_attr['normalize'] = True |
800 | #a.graph_attr['level'] = 2 | |
801 | a.layout(prog='dot') | |
802 | ||
803 | svg = a.draw(format='svg') | |
804 | ||
805 | c = { | |
806 | 'svg': svg | |
807 | } | |
a251ac8d JPC |
808 | |
809 | if 'forcer' in request.GET: | |
890b80e7 | 810 | response = HttpResponse(svg, content_type='image/svg+xml') |
00ca4d9f EMS |
811 | response['Content-Disposition'] = \ |
812 | 'attachment; filename=organigramme.svg' | |
a251ac8d JPC |
813 | return response |
814 | ||
890b80e7 DB |
815 | return render(request, 'rh/organigrammes/employe.html', c, |
816 | content_type="image/svg+xml" | |
00ca4d9f | 817 | ) |
5c0f1778 JPC |
818 | |
819 | ||
820 | @login_required | |
3383b2d1 OL |
821 | @in_one_of_group((groups.CORRESPONDANT_RH, |
822 | groups.ADMINISTRATEURS, | |
823 | groups.DIRECTEUR_DE_BUREAU, | |
824 | groups.DRH_NIVEAU_1, | |
825 | groups.DRH_NIVEAU_2)) | |
5c0f1778 | 826 | def organigrammes_service(request, id): |
5c0f1778 | 827 | service = get_object_or_404(rh.Service, pk=id) |
82af5c19 | 828 | svg = rh_graph.organigramme_postes_cluster( \ |
7000b7b3 | 829 | request.user, \ |
82af5c19 JPC |
830 | cluster_filter={"service": service}, \ |
831 | titre=u"Organigramme du service %s" % service.nom, | |
832 | cluster_titre=service.nom) | |
5c0f1778 | 833 | |
d104b0ae | 834 | return render(request, 'rh/organigrammes/vide.html', { |
82af5c19 | 835 | 'svg': svg |
d104b0ae | 836 | }, content_type="image/svg+xml") |
00ca4d9f | 837 | |
5c0f1778 | 838 | |
82af5c19 | 839 | @login_required |
3383b2d1 OL |
840 | @in_one_of_group((groups.CORRESPONDANT_RH, |
841 | groups.ADMINISTRATEURS, | |
842 | groups.DIRECTEUR_DE_BUREAU, | |
843 | groups.DRH_NIVEAU_1, | |
844 | groups.DRH_NIVEAU_2)) | |
82af5c19 | 845 | def organigrammes_implantation(request, id): |
82af5c19 JPC |
846 | implantation = get_object_or_404(ref.Implantation, pk=id) |
847 | svg = rh_graph.organigramme_postes_cluster( \ | |
7000b7b3 | 848 | request.user, \ |
82af5c19 JPC |
849 | cluster_filter={"implantation": implantation}, \ |
850 | titre=u"Organigramme de l'implantation %s" % implantation.nom, | |
851 | cluster_titre=implantation.nom) | |
d104b0ae | 852 | return render(request, 'rh/organigrammes/vide.html', { |
5c0f1778 | 853 | 'svg': svg |
d104b0ae | 854 | }, content_type="image/svg+xml") |
00ca4d9f | 855 | |
5c0f1778 | 856 | |
9da4c195 | 857 | @login_required |
3383b2d1 OL |
858 | @in_one_of_group((groups.CORRESPONDANT_RH, |
859 | groups.ADMINISTRATEURS, | |
860 | groups.DIRECTEUR_DE_BUREAU, | |
861 | groups.DRH_NIVEAU_1, | |
862 | groups.DRH_NIVEAU_2)) | |
9da4c195 | 863 | def organigrammes_region(request, id): |
9da4c195 JPC |
864 | region = get_object_or_404(ref.Region, pk=id) |
865 | svg = rh_graph.organigramme_postes_cluster( \ | |
7000b7b3 | 866 | request.user, \ |
9da4c195 JPC |
867 | cluster_filter={"implantation__region": region}, \ |
868 | titre=u"Organigramme du bureau de %s" % region.nom, | |
869 | cluster_titre=region.nom) | |
870 | ||
d104b0ae | 871 | return render(request, 'rh/organigrammes/vide.html', { |
9da4c195 | 872 | 'svg': svg |
d104b0ae | 873 | }, content_type="image/svg+xml") |
8e26a99b OL |
874 | |
875 | ||
876 | @login_required | |
877 | @drh_or_admin_required | |
878 | def historique_des_modifications(request,): | |
879 | ||
880 | from django.core.paginator import Paginator | |
881 | ||
edbc9e37 | 882 | revisions = get_active_revisions() |
45c66f19 | 883 | paginator = Paginator(revisions, 50) |
8e26a99b OL |
884 | page = request.GET.get('page') |
885 | try: | |
886 | page_revisions = paginator.page(page) | |
887 | except: | |
888 | page_revisions = paginator.page(1) | |
889 | ||
edbc9e37 OL |
890 | results = page_revisions.object_list |
891 | if request.POST: | |
892 | form = TodoForm(request.POST, revisions=results) | |
893 | if form.is_valid(): | |
894 | for id in form.cleaned_data['items']: | |
895 | revision = Revision.objects.get(id=id) | |
896 | rh.ModificationTraite(revision=revision).save() | |
897 | return redirect(reverse('rhr_historique_des_modifications')) | |
898 | else: | |
8e26a99b | 899 | |
edbc9e37 OL |
900 | form = TodoForm(revisions=results) |
901 | ||
8e26a99b | 902 | c = { |
edbc9e37 | 903 | 'headers': ('Date', 'Auteur', 'Type', 'Objet', 'Historique', |
d61cd885 | 904 | 'Commentaire', ), |
8e26a99b OL |
905 | 'revisions': results, |
906 | 'page': page_revisions, | |
edbc9e37 | 907 | 'form': form, |
8e26a99b OL |
908 | } |
909 | ||
910 | return render(request, 'rh/rapports/historique_des_modifications.html', c) |