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