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