PEP8
[auf_rh_dae.git] / project / rh / views.py
1 # -*- encoding: utf-8 -*-
2
3 import urllib
4 from datetime import date
5 import StringIO
6
7 import pygraphviz as pgv
8
9 from django import forms
10 from django.conf import settings
11 from django.contrib.auth.decorators import login_required
12 from django.core.servers.basehttp import FileWrapper
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
17
18 from auf.django.references import models as ref
19
20 from project.decorators import drh_or_admin_required
21 from project.decorators import region_protected
22 from project.groups import get_employe_from_user
23 from project.groups import grp_drh, grp_correspondants_rh
24
25 from project.rh import models as rh
26 from project.rh import graph as rh_graph
27 from project.rh.change_list import RechercheTemporelle
28 from project.rh.lib import get_lookup_params
29 from project.rh.masse_salariale import MasseSalariale
30 from project.rh.templatetags.rapports import SortHeaders
31
32
33 @login_required
34 def profil(request):
35 """Profil personnel de l'employé - éditable"""
36 employe = get_employe_from_user(request.user)
37 c = {
38 'user': request.user,
39 'employe': employe,
40 }
41 return render(request, 'rh/profil.html', c)
42
43
44 @login_required
45 def employes_liste(request):
46 """Liste des employés."""
47 today = date.today()
48 employes = rh.Employe.objects \
49 .exclude(dossiers__date_debut__gt=today) \
50 .exclude(dossiers__date_fin__lt=today) \
51 .order_by('nom')
52 c = {
53 'user': request.user,
54 'employes': employes,
55 }
56 return render(request, 'rh/employes_liste.html', c)
57
58
59 @login_required
60 def employe(request, id):
61 """Information publique sur un employé."""
62 try:
63 employe = rh.Employe.objects.get(pk=id)
64 except:
65 employe = rh.Employe.objects.none()
66 c = {
67 'user': request.user,
68 'employe': employe,
69 }
70 return render(request, 'rh/employe.html', c)
71
72
73 @login_required
74 @drh_or_admin_required
75 def rapports_contrat(request):
76 # statut default = actif?
77 if 'HTTP_REFERER' in request.META.keys():
78 referer = request.META['HTTP_REFERER']
79 referer = "/".join(referer.split('/')[3:])
80 referer = "/%s" % referer.split('?')[0]
81 if referer != reverse('rhr_contrats'):
82 params = request.GET.copy()
83 params.update({'statut': 'Actif'})
84 request.GET = params
85
86 contrats = rh.Contrat.objects.select_related(
87 'dossier', 'dossier__poste', 'dossier__poste__implantation',
88 'type_contrat', 'dossier__employe'
89 )
90
91 # filters et recherche temporelle
92 cl = RechercheTemporelle(dict(request.GET.items()), rh.Contrat)
93 lookup_params = get_lookup_params(request)
94 lookup_params = cl.purge_params(lookup_params)
95 q_temporel = cl.get_q_temporel(contrats)
96 q = Q(**lookup_params) & q_temporel
97 contrats = contrats.filter(q).exclude(dossier__employe__supprime=1)
98
99 # tri
100 if 'o' in request.GET:
101 contrats = contrats.order_by(
102 ('-' if request.GET.get('ot') == "desc" else '') + request.GET['o']
103 )
104
105 # affichage
106 employes = set([c.dossier.employe_id for c in contrats])
107
108 headers = [
109 ("dossier__employe__id", u"#"),
110 ("dossier__employe__nom", u"Employé"),
111 ("dossier", u"Dossier : Poste"),
112 ("type_contrat__nom", u"Contrat"),
113 ("date_debut", u"Début contrat"),
114 ("date_fin", u"Fin contrat"),
115 ("dossier__statut_residence", u"Statut"),
116 ("dossier__poste__implantation__region", u"Région"),
117 ("dossier__poste__implantation", u"Implantation"),
118 ]
119 h = SortHeaders(
120 request, headers, order_field_type="ot", order_field="o",
121 not_sortable=('dossier',)
122 )
123
124 c = {
125 'cl': cl,
126 'title': 'Rapport des contrats',
127 'contrats': contrats,
128 'count_employe': len(employes),
129 'headers': list(h.headers()),
130 }
131 return render(request, 'rh/rapports/contrats.html', c)
132
133
134 @login_required
135 @drh_or_admin_required
136 def rapports_masse_salariale(request):
137
138 class RechercheTemporelle(forms.Form):
139 CHOICE_ANNEES = range(
140 rh.Remuneration.objects.exclude(date_debut=None)
141 .order_by('date_debut')[0].date_debut.year,
142 date.today().year + 1
143 )
144
145 annee = forms.CharField(
146 initial=date.today().year,
147 widget=forms.Select(
148 choices=((a, a) for a in reversed(CHOICE_ANNEES))
149 )
150 )
151
152 region = forms.CharField(
153 widget=forms.Select(choices=[('', '')] +
154 [(i.id, i) for i in ref.Region.objects.all()]
155 )
156 )
157
158 implantation = forms.CharField(
159 widget=forms.Select(choices=[('', '')] +
160 [(i.id, i) for i in ref.Implantation.objects.all()]
161 )
162 )
163
164 #date_debut = forms.DateField(widget=adminwidgets.AdminDateWidget)
165 #date_fin = forms.DateField(widget=adminwidgets.AdminDateWidget)
166
167 form = RechercheTemporelle(request.GET)
168 get_filtre = [
169 (k, v) for k, v in request.GET.items()
170 if k not in ('date_debut', 'date_fin', 'implantation')
171 ]
172 query_string = urllib.urlencode(get_filtre)
173
174 date_debut = None
175 date_fin = None
176 if request.GET.get('annee', None):
177 date_debut = "01-01-%s" % request.GET.get('annee', None)
178 date_fin = "31-12-%s" % request.GET.get('annee', None)
179
180 implantation = request.GET.get('implantation')
181 region = request.GET.get('region')
182
183 custom_filter = {}
184 if implantation:
185 custom_filter['dossier__poste__implantation'] = implantation
186 if region:
187 custom_filter['dossier__poste__implantation__region'] = region
188
189 c = {
190 'title': 'Rapport de masse salariale',
191 'form': form,
192 'headers': [],
193 'query_string': query_string,
194 }
195 if date_debut or date_fin:
196 masse = MasseSalariale(date_debut, date_fin, custom_filter,
197 request.GET.get('ne_pas_grouper', False))
198 if masse.rapport:
199 if request.GET.get('ods'):
200 for h in (
201 h for h in masse.headers if 'background-color' in h[2]
202 ):
203 del h[2]['background-color']
204 masse.ods()
205 output = StringIO.StringIO()
206 masse.doc.save(output)
207 output.seek(0)
208
209 response = HttpResponse(
210 FileWrapper(output),
211 content_type=(
212 'application/vnd.oasis.opendocument.spreadsheet'
213 )
214 )
215 response['Content-Disposition'] = \
216 'attachment; filename=Masse Salariale %s.ods' % \
217 masse.annee
218 return response
219 else:
220 c['rapport'] = masse.rapport
221 c['header_keys'] = [h[0] for h in masse.headers]
222 #on enleve le background pour le header
223 for h in (
224 h for h in masse.headers if 'background-color' in h[2]
225 ):
226 h[2]['background'] = 'none'
227 h = SortHeaders(request, masse.headers, order_field_type="ot",
228 not_sortable=c['header_keys'], order_field="o")
229 c['headers'] = list(h.headers())
230 for key, nom, opts in masse.headers:
231 c['headers']
232 c['total'] = masse.grand_totaux[0]
233 c['total_euro'] = masse.grand_totaux[1]
234 c['colspan'] = len(c['header_keys']) - 1
235 get_filtre.append(('ods', True))
236 query_string = urllib.urlencode(get_filtre)
237 c['url_ods'] = "%s?%s" % (
238 reverse('rhr_masse_salariale'), query_string)
239
240 return render(request, 'rh/rapports/masse_salariale.html', c)
241
242
243 @login_required
244 @drh_or_admin_required
245 def rapports_employes_sans_contrat(request):
246 lookup_params = get_lookup_params(request)
247
248 # contrats échus
249 contrats_echus = rh.Contrat.objects.filter(
250 date_fin__lt=date.today()
251 ).filter(
252 **lookup_params
253 )
254
255 # dossiers en cours sans contrat
256 dossiers_sans_contrat = rh.Dossier.objects.filter(
257 Q(date_fin=None) | Q(date_fin__gt=date.today()),
258 ).exclude(
259 date_debut__gt=date.today()
260 ).filter(
261 rh_contrats__in=contrats_echus
262 )
263
264 # employés sans contrat
265 employes = rh.Employe.objects \
266 .filter(rh_dossiers__in=dossiers_sans_contrat).distinct()
267 if 'o' in request.GET:
268 employes = employes.order_by(
269 ('-' if request.GET.get('ot') == "desc" else '') + request.GET['o']
270 )
271
272 # affichage
273 headers = [
274 ("id", u"#"),
275 ("nom", u"Employé"),
276 ("dossier", u"Dossier : Poste"),
277 ("dossier_date_debut", u"Début dossier"),
278 ("dossier_date_fin", u"Fin dossier"),
279 ]
280 h = SortHeaders(
281 request, headers, order_field_type="ot", order_field="o",
282 not_sortable=('dossier', 'dossier_date_debut', 'dossier_date_fin')
283 )
284
285 c = {
286 'title': u'Rapport des employés sans contrat',
287 'employes': employes,
288 'dossiers_sans_contrat': dossiers_sans_contrat,
289 'headers': list(h.headers()),
290 }
291
292 return render(request, 'rh/rapports/employes_sans_contrat.html', c)
293
294
295 @login_required
296 @drh_or_admin_required
297 def rapports_postes_modelisation(request):
298 c = {}
299 data = []
300
301 for categorie in rh.CategorieEmploi.objects.all():
302 types = rh.TypePoste.objects.filter(categorie_emploi=categorie)
303 data_types = []
304 for t in types.all():
305 postes = rh.Poste.objects.filter(type_poste=t)
306 data_types.append({
307 'num_postes': postes.count(),
308 'postes': postes.all(),
309 'type': categorie,
310 })
311
312 data.append({
313 'categorie': categorie,
314 'nb_types': types.count(),
315 'types': data_types
316 })
317
318 c['data'] = data
319
320 return render(request, 'rh/rapports/postes_modelisation.html', c)
321
322
323 @login_required
324 @drh_or_admin_required
325 def rapports_postes_implantation(request):
326 c = {}
327 data = []
328 for r in ref.Region.objects.all():
329 implantations = []
330 for i in ref.Implantation.objects.filter(region=r):
331 implantations.append({
332 'implantation': i,
333 'postes': rh.Poste.objects.filter(implantation=i),
334 'num_postes': rh.Poste.objects.filter(implantation=i).count(),
335 })
336 data.append({
337 'region': r,
338 'implantations': implantations
339 })
340
341 c['data'] = data
342
343 return render(request, 'rh/rapports/postes_implantation.html', c)
344
345
346 @login_required
347 @drh_or_admin_required
348 def rapports_postes_service(request):
349 c = {}
350 data = []
351 for s in rh.Service.objects.all():
352 postes = rh.Poste.objects.filter(service=s).all()
353 num_postes = rh.Poste.objects.filter(service=s).count()
354 data.append({'service': s, 'num_postes': num_postes, 'postes': postes})
355
356 c['data'] = data
357 return render(request, 'rh/rapports/postes_service.html', c)
358
359
360 @region_protected(rh.Dossier)
361 def dossier_apercu(request, dossier_id):
362 d = get_object_or_404(rh.Dossier, pk=dossier_id)
363 c = {
364 'title': u"Dossier %s" % (d, ),
365 'is_popup': request.GET.get('_popup', False),
366 'dossier': d,
367 'pieces': rh.DossierPiece.objects.filter(dossier__exact=d),
368 'contrats': rh.Contrat.objects.filter(dossier__exact=d),
369 'commentaires': rh.DossierCommentaire.objects.filter(dossier=d).all(),
370 'media_url': settings.PRIVE_MEDIA_URL,
371 }
372 return render(request, 'admin/rh/dossier/apercu.html', c)
373
374
375 @region_protected(rh.Poste)
376 def poste_apercu(request, poste_id):
377 p = get_object_or_404(rh.Poste, pk=poste_id)
378 c = {
379 'title': u"Poste %s" % (p, ),
380 'is_popup': request.GET.get('_popup', False),
381 'poste': p,
382 'financements': (
383 rh.PosteFinancement.objects.filter(poste=poste_id).all()
384 ),
385 'pieces': rh.PostePiece.objects.filter(poste=poste_id).all(),
386 'dossiers': (
387 rh.Dossier.objects.filter(poste=poste_id)
388 .order_by("-date_debut").all()
389 ),
390 'comparaisons': (
391 rh.PosteComparaison.objects.filter(poste=poste_id).all()
392 ),
393 'commentaires': (
394 rh.PosteCommentaire.objects.filter(poste=poste_id).all()
395 ),
396 'media_url': settings.PRIVE_MEDIA_URL,
397 }
398 return render(request, 'admin/rh/poste/apercu.html', c)
399
400
401 def employe_apercu(request, employe_id):
402 employe = get_object_or_404(rh.Employe, pk=employe_id)
403 user_groups = request.user.groups.all()
404 dossiers = None
405
406 if request.user.is_superuser or \
407 grp_drh in user_groups:
408 q = Q(employe=employe)
409 if grp_correspondants_rh in user_groups:
410 regions = [
411 d.poste.implantation.region for d in employe.rh_dossiers.all()
412 ]
413 q = Q(employe=employe) & Q(implantation__region__in=regions)
414
415 dossiers = rh.Dossier.objects.filter(q).order_by('-date_debut')
416
417 c = {
418 'title': u"Employe %s" % (employe, ),
419 'is_popup': request.GET.get('_popup', False),
420 'employe': employe,
421 'dossiers': dossiers,
422 'media_url': settings.PRIVE_MEDIA_URL,
423 }
424 return render(request, 'admin/rh/employe/apercu.html', c)
425
426
427 @login_required
428 @drh_or_admin_required
429 def organigrammes_employe(request, id, level="all"):
430 poste = get_object_or_404(rh.Poste, pk=id)
431 dossiers_by_poste = dict(
432 (d.poste_id, d)
433 for d in rh.Dossier.objects.select_related('employe', 'poste').all()
434 )
435 postes_by_id = dict((p.id, p) for p in rh.Poste.objects.all())
436
437 e = dossiers_by_poste[poste.id].employe
438 name = u"Organigramme de [%s] %s %s" % (e.id, e.nom.upper(), e.prenom)
439 graph = pgv.AGraph()
440
441 if rh.Poste.objects.filter(responsable=poste).count() > 0:
442 postes_handle = [poste]
443 while postes_handle:
444 postes_handle = rh.Poste.objects.select_related('implantation') \
445 .filter(
446 Q(date_fin__gt=date.today()) | Q(date_fin=None),
447 Q(date_debut__lt=date.today()) | Q(date_debut=None),
448 responsable__in=postes_handle
449 ).exclude(supprime=True).exclude(responsable=None).all()
450
451 for p in postes_handle:
452 if p.responsable_id != p.id:
453 graph.add_edge(
454 dossiers_by_poste[p.responsable_id].poste_id, p.id
455 )
456
457 else:
458 graph.add_node(poste.id)
459
460 if level != "all":
461 postes_niveau = [poste.id]
462 for niveau in range(int(level)):
463 postes_niveau = [
464 p.id for p in
465 rh.Poste.objects.filter(responsable__in=postes_niveau).all()
466 ]
467
468 while postes_niveau:
469 postes_niveau = [
470 p.id for p in
471 rh.Poste.objects.filter(responsable__in=postes_niveau).all()
472 ]
473 if postes_niveau:
474 for p in postes_niveau:
475 if graph.has_node(p):
476 graph.delete_node(p)
477
478 a = graph
479 a.name = name.encode('ascii', 'xmlcharrefreplace')
480
481 poste_remontant = poste
482 while poste_remontant.responsable_id:
483 a.add_edge(poste_remontant.responsable_id, poste_remontant.id)
484 poste_remontant = poste_remontant.responsable
485
486 rh_graph.bind_poste_to_graph(a, postes_by_id)
487 #a.graph_attr['normalize'] = True
488 #a.graph_attr['level'] = 2
489 a.layout(prog='dot')
490
491 svg = a.draw(format='svg')
492
493 c = {
494 'svg': svg
495 }
496
497 if 'forcer' in request.GET:
498 response = HttpResponse(svg, content_type='image/svg+xml')
499 response['Content-Disposition'] = \
500 'attachment; filename=organigramme.svg'
501 return response
502
503 return render(request, 'rh/organigrammes/employe.html', c,
504 content_type="image/svg+xml"
505 )
506
507
508 @login_required
509 @drh_or_admin_required
510 def organigrammes_service(request, id):
511 service = get_object_or_404(rh.Service, pk=id)
512 svg = rh_graph.organigramme_postes_cluster( \
513 cluster_filter={"service": service}, \
514 titre=u"Organigramme du service %s" % service.nom,
515 cluster_titre=service.nom)
516
517 c = {
518 'svg': svg
519 }
520
521 return render(request, 'rh/organigrammes/vide.html', c,
522 content_type="image/svg+xml"
523 )
524
525
526 @login_required
527 @drh_or_admin_required
528 def organigrammes_implantation(request, id):
529 implantation = get_object_or_404(ref.Implantation, pk=id)
530 svg = rh_graph.organigramme_postes_cluster( \
531 cluster_filter={"implantation": implantation}, \
532 titre=u"Organigramme de l'implantation %s" % implantation.nom,
533 cluster_titre=implantation.nom)
534
535 c = {
536 'svg': svg
537 }
538
539 return render(request, 'rh/organigrammes/vide.html', c,
540 content_type="image/svg+xml"
541 )
542
543
544 @login_required
545 @drh_or_admin_required
546 def organigrammes_region(request, id):
547 region = get_object_or_404(ref.Region, pk=id)
548 svg = rh_graph.organigramme_postes_cluster( \
549 cluster_filter={"implantation__region": region}, \
550 titre=u"Organigramme du bureau de %s" % region.nom,
551 cluster_titre=region.nom)
552
553 c = {
554 'svg': svg
555 }
556
557 return render(request,
558 'rh/organigrammes/vide.html', c,
559 content_type="image/svg+xml"
560 )