Finished with new calcs for remunerations
[auf_rh_dae.git] / project / rh / views.py
1 # -*- encoding: utf-8 -*-
2
3 from collections import defaultdict
4 from datetime import date
5 from decimal import Decimal
6
7 import pygraphviz as pgv
8 from reversion.models import Revision
9
10 from auf.django.references import models as ref
11 from django.conf import settings
12 from django.contrib.auth.decorators import login_required
13 from django.core.urlresolvers import reverse
14 from django.db.models import Q
15 from django.http import HttpResponse
16 from django.shortcuts import render, get_object_or_404, redirect
17
18 from project.decorators import drh_or_admin_required
19 from project.decorators import region_protected
20 from project.groups import get_employe_from_user
21 from project import groups
22 from project.decorators import in_one_of_group, in_drh_or_admin
23
24 from project.rh import ods
25 from project.rh import graph as rh_graph
26 from project.rh import models as rh
27 from project.rh.change_list import RechercheTemporelle
28 from project.rh.forms import MasseSalarialeForm
29 from project.rh.lib import get_lookup_params
30 from project.rh.templatetags.rapports import SortHeaders
31 from project.rh.historique import get_active_revisions, TodoForm
32
33 TWOPLACES = rh.TWOPLACES
34
35
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)
48
49 return liste
50
51
52 @login_required
53 @drh_or_admin_required
54 def profil(request):
55 """Profil personnel de l'employé - éditable"""
56 employe = get_employe_from_user(request.user)
57 c = {
58 'user': request.user,
59 'employe': employe,
60 }
61 return render(request, 'rh/profil.html', c)
62
63
64 @login_required
65 @drh_or_admin_required
66 def employes_liste(request):
67 """Liste des employés."""
68 today = date.today()
69 employes = rh.Employe.objects \
70 .exclude(dossiers__date_debut__gt=today) \
71 .exclude(dossiers__date_fin__lt=today) \
72 .order_by('nom')
73 c = {
74 'user': request.user,
75 'employes': employes,
76 }
77 return render(request, 'rh/employes_liste.html', c)
78
79
80 @login_required
81 @drh_or_admin_required
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 = {
89 'user': request.user,
90 'employe': employe,
91 }
92 return render(request, 'rh/employe.html', c)
93
94
95 @login_required
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))
101 def rapports_contrat(request):
102 # statut default = actif?
103 if 'HTTP_REFERER' in request.META.keys():
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
111
112 contrats = rh.Contrat.objects.select_related(
113 'dossier', 'dossier__poste', 'dossier__poste__implantation',
114 'type_contrat', 'dossier__employe'
115 )
116
117 # filters et recherche temporelle
118 cl = RechercheTemporelle(dict(request.GET.items()), rh.Contrat)
119 lookup_params = get_lookup_params(request)
120 lookup_params = cl.purge_params(lookup_params)
121 q_temporel = cl.get_q_temporel(contrats)
122 q = Q(**lookup_params) & q_temporel
123 contrats = contrats.filter(q)
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:
128 employe = get_employe_from_user(request.user)
129 q = q & Q(
130 dossier__poste__implantation__zone_administrative=(
131 employe.implantation.zone_administrative
132 )
133 )
134
135 contrats = contrats.filter(q)
136
137 # tri
138 if 'o' in request.GET:
139 contrats = contrats.order_by(
140 ('-' if request.GET.get('ot') == "desc" else '') + request.GET['o']
141 )
142
143 # affichage
144 employes = set([c.dossier.employe_id for c in contrats])
145 headers = [
146 ("dossier__employe__id", u"#"),
147 ("dossier__employe__nom", u"Employé"),
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"),
153 ("dossier__poste__implantation__zone_administrative", u"Zone administrative"),
154 ("dossier__poste__implantation", u"Implantation"),
155 ]
156 h = SortHeaders(
157 request, headers, order_field_type="ot", order_field="o",
158 not_sortable=('dossier',)
159 )
160
161 c = {
162 'cl': cl,
163 'title': 'Rapport des contrats',
164 'contrats': contrats,
165 'count_employe': len(employes),
166 'headers': list(h.headers()),
167 }
168 return render(request, 'rh/rapports/contrats.html', c)
169
170
171 @login_required
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))
177 def rapports_employes_sans_contrat(request):
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 """
183 lookup_params = get_lookup_params(request)
184
185 # régionalisation
186 user_groups = [g.name for g in request.user.groups.all()]
187 if groups.CORRESPONDANT_RH in user_groups or\
188 groups.ADMINISTRATEURS in user_groups or\
189 groups.DIRECTEUR_DE_BUREAU in user_groups:
190 employe = get_employe_from_user(request.user)
191 lookup_params['poste__implantation__zone_administrative'] = \
192 employe.implantation.zone_administrative
193
194 dossiers = rh.Dossier.objects.sans_contrats_ou_echus(**lookup_params)
195 dossiers_ids = [d.id for d in dossiers]
196 employes = rh.Employe.objects.filter(id__in=dossiers_ids).distinct().count()
197
198 # tri
199 if 'o' in request.GET:
200 dossiers = dossiers.order_by(
201 ('-' if request.GET.get('ot') == "desc" else '') + request.GET['o']
202 )
203
204 # affichage
205 headers = [
206 ("employe__id", u"#"),
207 ("employe__nom", u"Employé"),
208 ("dossier", u"Dossier : Poste"),
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"),
213 ("poste__implantation__zone_administrative__code", u"Zone administrative"),
214 ("poste__implantation__nom", u"Implantation"),
215 ]
216 h = SortHeaders(
217 request, headers, order_field_type="ot", order_field="o",
218 not_sortable=('dossier',)
219 )
220
221 c = {
222 'title': u'Rapport des employés sans contrat ou contrat échu',
223 'employes': employes,
224 'dossiers': dossiers,
225 'headers': list(h.headers()),
226 }
227
228 return render(request, 'rh/rapports/employes_sans_contrat.html', c)
229
230
231 @login_required
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))
237 def rapports_masse_salariale(request):
238 form = MasseSalarialeForm(request.user, request.GET)
239 if 'annee' in request.GET and form.is_valid():
240 zone_administrative = form.cleaned_data['zone_administrative']
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',
253 'poste__implantation__zone_administrative',
254 'poste__implantation__adresse_physique_pays',
255 'employe', 'poste__type_poste', 'classement',
256 'statut', 'organisme_bstg'
257 ) \
258 .extra(
259 select={
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 )
272 }
273 )
274 if zone_administrative:
275 dossiers = dossiers.filter(poste__implantation__zone_administrative=zone_administrative)
276 if implantation:
277 dossiers = dossiers.filter(poste__implantation=implantation)
278
279 # Récupérer les rémunérations actives
280 remun_actives = rh.Remuneration.objects \
281 .actifs(annee=annee) \
282 .select_related('devise', 'type')
283
284 remuns = remun_actives.extra(
285 tables=['rh_tauxchange'],
286 where=[
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)',
292 ],
293 params=[annee],
294 select={
295 'taux_change': 'rh_tauxchange.taux'
296 }
297 )
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
303 if zone_administrative:
304 remuns = remuns.filter(dossier__poste__implantation__zone_administrative=zone_administrative)
305 if implantation:
306 remuns = remuns.filter(dossier__poste__implantation=implantation)
307 remuns_par_dossier = defaultdict(list)
308 types_remun = set()
309 for remun in remuns:
310 types_remun.add(remun.type_id)
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():
316 if type.id in types_remun:
317 types_remun_par_nature[type.nature_remuneration].append(type)
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 ]
330
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))
351 )
352 taux_change = Decimal(1)
353
354 remuns_par_type = defaultdict(lambda: 0)
355
356 for remun in remuns:
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)
364 else:
365 montant_ajuste = montant_ajuste_euros.quantize(TWOPLACES)
366 devise = 'EUR'
367 remuns_par_type[remun.type_id] += montant_ajuste
368
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 })
436
437 # Récupérer les postes actifs pour déterminer le nombre de jours
438 # vacants.
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 )
454 if zone_administrative:
455 postes = postes.filter(implantation__zone_administrative=zone_administrative)
456 if implantation:
457 postes = postes.filter(implantation=implantation)
458 postes = list(postes)
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
477 )
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)
483
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
497
498 local_expatrie = '/'.join(
499 (['L'] if poste.local else []) +
500 (['E'] if poste.expatrie else [])
501 )
502
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,
535 'traitements': [0] * len(titres_traitements),
536 'total_traitements': salaire,
537 'indemnites': [0] * len(titres_indemnites),
538 'total_indemnites': indemnites,
539 'primes': [0] * len(titres_primes),
540 'total_primes': 0,
541 'charges': [0] * len(titres_charges),
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 })
576
577
578 @login_required
579 @drh_or_admin_required
580 def rapports_postes_modelisation(request):
581 c = {}
582 data = []
583
584 for categorie in rh.CategorieEmploi.objects.all():
585 types = rh.TypePoste.objects.filter(categorie_emploi=categorie)
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(),
592 'type': categorie,
593 })
594
595 data.append({
596 'categorie': categorie,
597 'nb_types': types.count(),
598 'types': data_types
599 })
600
601 c['data'] = data
602
603 return render(request, 'rh/rapports/postes_modelisation.html', c)
604
605
606 @login_required
607 @drh_or_admin_required
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
626 return render(request, 'rh/rapports/postes_implantation.html', c)
627
628
629 @login_required
630 @drh_or_admin_required
631 def rapports_postes_service(request):
632 c = {}
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()
637 data.append({'service': s, 'num_postes': num_postes, 'postes': postes})
638
639 c['data'] = data
640 return render(request, 'rh/rapports/postes_service.html', c)
641
642
643 @region_protected(rh.Dossier)
644 def dossier_apercu(request, dossier_id):
645 d = get_object_or_404(rh.Dossier, pk=dossier_id)
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
655 c = {
656 'title': u"Dossier %s" % (d, ),
657 'is_popup': request.GET.get('_popup', False),
658 'devises': devises(),
659 'dossier': d,
660 'annees': annees,
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(),
664 'media_url': settings.PRIVE_MEDIA_URL,
665 }
666 return render(request, 'admin/rh/dossier/apercu.html', c)
667
668
669 @region_protected(rh.Poste)
670 def poste_apercu(request, poste_id):
671 p = get_object_or_404(rh.Poste, pk=poste_id)
672 c = {
673 'title': u"Poste %s" % (p, ),
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 ),
690 'media_url': settings.PRIVE_MEDIA_URL,
691 }
692 return render(request, 'admin/rh/poste/apercu.html', c)
693
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))
700 def employe_apercu(request, employe_id):
701 employe = get_object_or_404(rh.Employe, pk=employe_id)
702 user_groups = [g.name for g in request.user.groups.all()]
703 dossiers = None
704
705 if in_drh_or_admin(request.user):
706 q = Q(employe=employe)
707
708 if groups.CORRESPONDANT_RH in user_groups or\
709 groups.ADMINISTRATEURS in user_groups or\
710 groups.DIRECTEUR_DE_BUREAU in user_groups:
711 employe_connecte = get_employe_from_user(request.user)
712 q = Q(employe=employe) & Q(
713 poste__implantation__zone_administrative=(
714 employe_connecte.implantation.zone_administrative
715 )
716 )
717
718 dossiers = rh.Dossier.objects.filter(q).order_by('-date_debut')
719
720 dossier_principal = employe.dossier_principal()
721
722 c = {
723 'title': u"Employe %s" % (employe, ),
724 'is_popup': request.GET.get('_popup', False),
725 'employe': employe,
726 'dossiers': dossiers,
727 'dossier_principal': dossier_principal,
728 'media_url': settings.PRIVE_MEDIA_URL,
729 'annee': date.today().year -1,
730 'devises': devises(),
731 }
732 return render(request, 'admin/rh/employe/apercu.html', c)
733
734
735 @login_required
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))
741 def organigrammes_employe(request, id, level="all"):
742 poste = get_object_or_404(rh.Poste, pk=id)
743 dossiers_by_poste = dict(
744 (d.poste_id, d)
745 for d in rh.Dossier.objects.select_related('employe', 'poste').all()
746 )
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)
751 graph = pgv.AGraph()
752
753 if rh.Poste.objects.filter(responsable=poste).count() > 0:
754 postes_handle = [poste]
755 while postes_handle:
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
761 ).exclude(responsable=None).all()
762
763 for p in postes_handle:
764 if p.responsable_id != p.id:
765 graph.add_edge(
766 dossiers_by_poste[p.responsable_id].poste_id, p.id
767 )
768
769 else:
770 graph.add_node(poste.id)
771
772 if level != "all":
773 postes_niveau = [poste.id]
774 for niveau in range(int(level)):
775 postes_niveau = [
776 p.id for p in
777 rh.Poste.objects.filter(responsable__in=postes_niveau).all()
778 ]
779
780 while postes_niveau:
781 postes_niveau = [
782 p.id for p in
783 rh.Poste.objects.filter(responsable__in=postes_niveau).all()
784 ]
785 if postes_niveau:
786 for p in postes_niveau:
787 if graph.has_node(p):
788 graph.delete_node(p)
789
790 a = graph
791 a.name = name.encode('ascii', 'xmlcharrefreplace')
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
798 rh_graph.bind_poste_to_graph(request.user, a, postes_by_id)
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 }
808
809 if 'forcer' in request.GET:
810 response = HttpResponse(svg, content_type='image/svg+xml')
811 response['Content-Disposition'] = \
812 'attachment; filename=organigramme.svg'
813 return response
814
815 return render(request, 'rh/organigrammes/employe.html', c,
816 content_type="image/svg+xml"
817 )
818
819
820 @login_required
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))
826 def organigrammes_service(request, id):
827 service = get_object_or_404(rh.Service, pk=id)
828 svg = rh_graph.organigramme_postes_cluster( \
829 request.user, \
830 cluster_filter={"service": service}, \
831 titre=u"Organigramme du service %s" % service.nom,
832 cluster_titre=service.nom)
833
834 return render(request, 'rh/organigrammes/vide.html', {
835 'svg': svg
836 }, content_type="image/svg+xml")
837
838
839 @login_required
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))
845 def organigrammes_implantation(request, id):
846 implantation = get_object_or_404(ref.Implantation, pk=id)
847 svg = rh_graph.organigramme_postes_cluster( \
848 request.user, \
849 cluster_filter={"implantation": implantation}, \
850 titre=u"Organigramme de l'implantation %s" % implantation.nom,
851 cluster_titre=implantation.nom)
852 return render(request, 'rh/organigrammes/vide.html', {
853 'svg': svg
854 }, content_type="image/svg+xml")
855
856
857 @login_required
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))
863 def organigrammes_region(request, id):
864 region = get_object_or_404(ref.Region, pk=id)
865 svg = rh_graph.organigramme_postes_cluster( \
866 request.user, \
867 cluster_filter={"implantation__region": region}, \
868 titre=u"Organigramme du bureau de %s" % region.nom,
869 cluster_titre=region.nom)
870
871 return render(request, 'rh/organigrammes/vide.html', {
872 'svg': svg
873 }, content_type="image/svg+xml")
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
882 revisions = get_active_revisions()
883 paginator = Paginator(revisions, 50)
884 page = request.GET.get('page')
885 try:
886 page_revisions = paginator.page(page)
887 except:
888 page_revisions = paginator.page(1)
889
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:
899
900 form = TodoForm(revisions=results)
901
902 c = {
903 'headers': ('Date', 'Auteur', 'Type', 'Objet', 'Historique',
904 'Commentaire', ),
905 'revisions': results,
906 'page': page_revisions,
907 'form': form,
908 }
909
910 return render(request, 'rh/rapports/historique_des_modifications.html', c)