1 # -*- encoding: utf-8 -*-
4 from datetime
import date
5 from itertools
import izip
8 import pygraphviz
as pgv
10 from django
import forms
11 from django
.conf
import settings
12 from django
.contrib
.auth
.decorators
import login_required
13 from django
.core
.servers
.basehttp
import FileWrapper
14 from django
.core
.urlresolvers
import reverse
15 from django
.db
.models
import Q
16 from django
.http
import HttpResponse
17 from django
.shortcuts
import render
, get_object_or_404
19 from auf
.django
.references
import models
as ref
21 from project
.decorators
import redirect_interdiction
22 from project
.decorators
import drh_or_admin_required
23 from project
.decorators
import region_protected
24 from project
.groups
import get_employe_from_user
25 from project
.groups
import grp_drh
, grp_correspondants_rh
27 from project
.rh
import models
as rh
28 from project
.rh
import graph
as rh_graph
29 from project
.rh
.change_list
import RechercheTemporelle
30 from project
.rh
.lib
import calc_remun
, get_lookup_params
31 from project
.rh
.masse_salariale
import MasseSalariale
32 from project
.rh
.templatetags
.rapports
import SortHeaders
37 """Profil personnel de l'employé - éditable"""
38 employe
= get_employe_from_user(user
)
43 return render(request
, 'rh/profil.html', c
)
47 def employes_liste(request
):
48 """Liste des employés."""
50 employes
= rh
.Employe
.objects \
51 .exclude(dossiers__date_debut__gt
=today
) \
52 .exclude(dossiers__date_fin__lt
=today
) \
58 return render(request
, 'rh/employes_liste.html', c
)
62 def employe(request
, id):
63 """Information publique sur un employé."""
65 employe
= rh
.Employe
.objects
.get(pk
=id)
67 employe
= rh
.Employe
.objects
.none()
72 return render(request
, 'rh/employe.html', c
)
76 @drh_or_admin_required
77 def rapports_contrat(request
):
78 # statut default = actif?
79 if 'HTTP_REFERER' in request
.META
.keys():
80 referer
= request
.META
['HTTP_REFERER']
81 referer
= "/".join(referer
.split('/')[3:])
82 referer
= "/%s" % referer
.split('?')[0]
83 if referer
!= reverse('rhr_contrats'):
84 params
= request
.GET
.copy()
85 params
.update({'statut': 'Actif'})
88 contrats
= rh
.Contrat
.objects
.select_related(
89 'dossier', 'dossier__poste', 'dossier__poste__implantation',
90 'type_contrat', 'dossier__employe'
93 # filters et recherche temporelle
94 cl
= RechercheTemporelle(dict(request
.GET
.items()), rh
.Contrat
)
95 lookup_params
= get_lookup_params(request
)
96 lookup_params
= cl
.purge_params(lookup_params
)
97 q_temporel
= cl
.get_q_temporel(contrats
)
98 q
= Q(**lookup_params
) & q_temporel
99 contrats
= contrats
.filter(q
).exclude(dossier__employe__supprime
=1)
102 if 'o' in request
.GET
:
103 contrats
= contrats
.order_by(
104 ('-' if request
.GET
.get('ot') == "desc" else '') + request
.GET
['o']
108 employes
= set([c
.dossier
.employe_id
for c
in contrats
])
111 ("dossier__employe__id", u
"#"),
112 ("dossier__employe__nom", u
"Employé"),
113 ("dossier", u
"Dossier : Poste"),
114 ("type_contrat__nom", u
"Contrat"),
115 ("date_debut", u
"Début contrat"),
116 ("date_fin", u
"Fin contrat"),
117 ("dossier__statut_residence", u
"Statut"),
118 ("dossier__poste__implantation__region", u
"Région"),
119 ("dossier__poste__implantation", u
"Implantation"),
122 request
, headers
, order_field_type
="ot", order_field
="o",
123 not_sortable
=('dossier',)
128 'title': 'Rapport des contrats',
129 'contrats': contrats
,
130 'count_employe': len(employes
),
131 'headers': list(h
.headers()),
133 return render(request
, 'rh/rapports/contrats.html', c
)
137 @drh_or_admin_required
138 def rapports_employes_sans_contrat(request
):
140 Employé sans contrat = a un Dossier qui n'a pas de Contrat associé
141 Employé avec contrat échu = a un Dossier avec un Contrat se terminant hier
142 au plus tard... (aujourd'hui = ok, pas date de fin = illimité donc ok)
144 lookup_params
= get_lookup_params(request
)
147 contrats_echus
= rh
.Contrat
.objects
.filter(
148 date_fin__lt
=date
.today()
153 # dossiers en cours sans contrat ou contrats échus
154 dossiers
= rh
.Dossier
.objects
.filter(
155 Q(date_debut
=None) |
Q(date_debut__lte
=date
.today()),
157 Q(date_fin
=None) |
Q(date_fin__gte
=date
.today()),
159 date_debut__gt
=date
.today()
161 # sans contrat | contrat échu
162 Q(rh_contrats
=None) |
Q(rh_contrats__in
=contrats_echus
)
165 # employés sans contrat ou contrats échus
166 employes
= rh
.Employe
.objects
.filter(rh_dossiers__in
=dossiers
) \
170 if 'o' in request
.GET
:
171 dossiers
= dossiers
.order_by(
172 ('-' if request
.GET
.get('ot') == "desc" else '') + request
.GET
['o']
177 ("employe__id", u
"#"),
178 ("employe__nom", u
"Employé"),
179 ("dossier", u
"Dossier : Poste"),
180 ("rh_contrats__type_contrat", u
"Contrat"),
181 ("rh_contrats__date_debut", u
"Début contrat"),
182 ("rh_contrats__date_fin", u
"Fin contrat"),
183 ("statut_residence", u
"Statut"),
184 ("poste__implantation__region__code", u
"Région"),
185 ("poste__implantation__nom", u
"Implantation"),
188 request
, headers
, order_field_type
="ot", order_field
="o",
189 not_sortable
=('dossier',)
193 'title': u
'Rapport des employés sans contrat ou contrat échu',
194 'employes': employes
,
195 'dossiers': dossiers
,
196 'headers': list(h
.headers()),
199 return render(request
, 'rh/rapports/employes_sans_contrat.html', c
)
203 @drh_or_admin_required
204 def rapports_masse_salariale(request
):
206 class RechercheTemporelle(forms
.Form
):
207 CHOICE_ANNEES
= range(
208 rh
.Remuneration
.objects
.exclude(date_debut
=None)
209 .order_by('date_debut')[0].date_debut
.year
,
210 date
.today().year
+ 1
213 annee
= forms
.CharField(
214 initial
=date
.today().year
,
216 choices
=((a
, a
) for a
in reversed(CHOICE_ANNEES
))
220 region
= forms
.CharField(
221 widget
=forms
.Select(choices
=[('', '')] +
222 [(i
.id, i
) for i
in ref
.Region
.objects
.all()]
226 implantation
= forms
.CharField(
227 widget
=forms
.Select(choices
=[('', '')] +
228 [(i
.id, i
) for i
in ref
.Implantation
.objects
.all()]
232 #date_debut = forms.DateField(widget=adminwidgets.AdminDateWidget)
233 #date_fin = forms.DateField(widget=adminwidgets.AdminDateWidget)
235 form
= RechercheTemporelle(request
.GET
)
237 (k
, v
) for k
, v
in request
.GET
.items()
238 if k
not in ('date_debut', 'date_fin', 'implantation')
240 query_string
= urllib
.urlencode(get_filtre
)
244 if request
.GET
.get('annee', None):
245 date_debut
= "01-01-%s" % request
.GET
.get('annee', None)
246 date_fin
= "31-12-%s" % request
.GET
.get('annee', None)
248 implantation
= request
.GET
.get('implantation')
249 region
= request
.GET
.get('region')
253 custom_filter
['dossier__poste__implantation'] = implantation
255 custom_filter
['dossier__poste__implantation__region'] = region
258 'title': 'Rapport de masse salariale',
261 'query_string': query_string
,
263 if date_debut
or date_fin
:
264 masse
= MasseSalariale(date_debut
, date_fin
, custom_filter
,
265 request
.GET
.get('ne_pas_grouper', False))
267 if request
.GET
.get('ods'):
269 h
for h
in masse
.headers
if 'background-color' in h
[2]
271 del h
[2]['background-color']
273 output
= StringIO
.StringIO()
274 masse
.doc
.save(output
)
277 response
= HttpResponse(
280 'application/vnd.oasis.opendocument.spreadsheet'
283 response
['Content-Disposition'] = \
284 'attachment; filename=Masse Salariale %s.ods' % \
288 c
['rapport'] = masse
.rapport
289 c
['header_keys'] = [h
[0] for h
in masse
.headers
]
290 #on enleve le background pour le header
292 h
for h
in masse
.headers
if 'background-color' in h
[2]
294 h
[2]['background'] = 'none'
295 h
= SortHeaders(request
, masse
.headers
, order_field_type
="ot",
296 not_sortable
=c
['header_keys'], order_field
="o")
297 c
['headers'] = list(h
.headers())
298 for key
, nom
, opts
in masse
.headers
:
300 c
['total'] = masse
.grand_totaux
[0]
301 c
['total_euro'] = masse
.grand_totaux
[1]
302 c
['colspan'] = len(c
['header_keys']) - 1
303 get_filtre
.append(('ods', True))
304 query_string
= urllib
.urlencode(get_filtre
)
305 c
['url_ods'] = "%s?%s" % (
306 reverse('rhr_masse_salariale'), query_string
)
308 return render(request
, 'rh/rapports/masse_salariale.html', c
)
312 @drh_or_admin_required
313 def rapports_postes_modelisation(request
):
317 for categorie
in rh
.CategorieEmploi
.objects
.all():
318 types
= rh
.TypePoste
.objects
.filter(categorie_emploi
=categorie
)
320 for t
in types
.all():
321 postes
= rh
.Poste
.objects
.filter(type_poste
=t
)
323 'num_postes': postes
.count(),
324 'postes': postes
.all(),
329 'categorie': categorie
,
330 'nb_types': types
.count(),
336 return render(request
, 'rh/rapports/postes_modelisation.html', c
)
340 @drh_or_admin_required
341 def rapports_postes_implantation(request
):
344 for r
in ref
.Region
.objects
.all():
346 for i
in ref
.Implantation
.objects
.filter(region
=r
):
347 implantations
.append({
349 'postes': rh
.Poste
.objects
.filter(implantation
=i
),
350 'num_postes': rh
.Poste
.objects
.filter(implantation
=i
).count(),
354 'implantations': implantations
359 return render(request
, 'rh/rapports/postes_implantation.html', c
)
363 @drh_or_admin_required
364 def rapports_postes_service(request
):
367 for s
in rh
.Service
.objects
.all():
368 postes
= rh
.Poste
.objects
.filter(service
=s
).all()
369 num_postes
= rh
.Poste
.objects
.filter(service
=s
).count()
370 data
.append({'service': s
, 'num_postes': num_postes
, 'postes': postes
})
373 return render(request
, 'rh/rapports/postes_service.html', c
)
375 @region_protected(rh
.Dossier
)
376 def dossier_apercu(request
, dossier_id
):
377 d
= get_object_or_404(rh
.Dossier
, pk
=dossier_id
)
379 'title': u
"Dossier %s" % (d
, ),
380 'is_popup': request
.GET
.get('_popup', False),
382 'pieces': rh
.DossierPiece
.objects
.filter(dossier__exact
=d
),
383 'contrats': rh
.Contrat
.objects
.filter(dossier__exact
=d
),
384 'commentaires': rh
.DossierCommentaire
.objects
.filter(dossier
=d
).all(),
385 'media_url': settings
.PRIVE_MEDIA_URL
,
387 return render(request
, 'admin/rh/dossier/apercu.html', c
)
390 @region_protected(rh
.Poste
)
391 def poste_apercu(request
, poste_id
):
392 p
= get_object_or_404(rh
.Poste
, pk
=poste_id
)
394 'title': u
"Poste %s" % (p
, ),
395 'is_popup': request
.GET
.get('_popup', False),
398 rh
.PosteFinancement
.objects
.filter(poste
=poste_id
).all()
400 'pieces': rh
.PostePiece
.objects
.filter(poste
=poste_id
).all(),
402 rh
.Dossier
.objects
.filter(poste
=poste_id
)
403 .order_by("-date_debut").all()
406 rh
.PosteComparaison
.objects
.filter(poste
=poste_id
).all()
409 rh
.PosteCommentaire
.objects
.filter(poste
=poste_id
).all()
411 'media_url': settings
.PRIVE_MEDIA_URL
,
413 return render(request
, 'admin/rh/poste/apercu.html', c
)
416 def employe_apercu(request
, employe_id
):
417 employe
= get_object_or_404(rh
.Employe
, pk
=employe_id
)
418 user_groups
= request
.user
.groups
.all()
421 if request
.user
.is_superuser
or \
422 grp_drh
in user_groups
:
423 q
= Q(employe
=employe
)
424 if grp_correspondants_rh
in user_groups
:
426 d
.poste
.implantation
.region
for d
in employe
.rh_dossiers
.all()
428 q
= Q(employe
=employe
) & Q(implantation__region__in
=regions
)
430 dossiers
= rh
.Dossier
.objects
.filter(q
).order_by('-date_debut')
433 'title': u
"Employe %s" % (employe
, ),
434 'is_popup': request
.GET
.get('_popup', False),
436 'dossiers': dossiers
,
437 'media_url': settings
.PRIVE_MEDIA_URL
,
439 return render(request
, 'admin/rh/employe/apercu.html', c
)
443 @drh_or_admin_required
444 def organigrammes_employe(request
, id, level
="all"):
446 poste
= get_object_or_404(rh
.Poste
, pk
=id)
447 dossiers_by_poste
= dict(
449 for d
in rh
.Dossier
.objects
.select_related('employe', 'poste').all()
451 postes_by_id
= dict((p
.id, p
) for p
in rh
.Poste
.objects
.all())
453 e
= dossiers_by_poste
[poste
.id].employe
454 name
= u
"Organigramme de [%s] %s %s" % (e
.id, e
.nom
.upper(), e
.prenom
)
457 if rh
.Poste
.objects
.filter(responsable
=poste
).count() > 0:
458 postes_handle
= [poste
]
460 postes_handle
= rh
.Poste
.objects
.select_related('implantation') \
462 Q(date_fin__gt
=date
.today()) |
Q(date_fin
=None),
463 Q(date_debut__lt
=date
.today()) |
Q(date_debut
=None),
464 responsable__in
=postes_handle
465 ).exclude(supprime
=True).exclude(responsable
=None).all()
467 for p
in postes_handle
:
468 if p
.responsable_id
!= p
.id:
470 dossiers_by_poste
[p
.responsable_id
].poste_id
, p
.id
474 graph
.add_node(poste
.id)
477 postes_niveau
= [poste
.id]
478 for niveau
in range(int(level
)):
481 rh
.Poste
.objects
.filter(responsable__in
=postes_niveau
).all()
487 rh
.Poste
.objects
.filter(responsable__in
=postes_niveau
).all()
490 for p
in postes_niveau
:
491 if graph
.has_node(p
):
495 a
.name
= name
.encode('ascii', 'xmlcharrefreplace')
497 poste_remontant
= poste
498 while poste_remontant
.responsable_id
:
499 a
.add_edge(poste_remontant
.responsable_id
, poste_remontant
.id)
500 poste_remontant
= poste_remontant
.responsable
502 rh_graph
.bind_poste_to_graph(a
, postes_by_id
)
503 #a.graph_attr['normalize'] = True
504 #a.graph_attr['level'] = 2
507 svg
= a
.draw(format
='svg')
513 if 'forcer' in request
.GET
:
514 response
= HttpResponse(svg
, content_type
='image/svg+xml')
515 response
['Content-Disposition'] = \
516 'attachment; filename=organigramme.svg'
519 return render(request
, 'rh/organigrammes/employe.html', c
,
520 content_type
="image/svg+xml"
525 @drh_or_admin_required
526 def organigrammes_service(request
, id):
528 service
= get_object_or_404(rh
.Service
, pk
=id)
529 svg
= rh_graph
.organigramme_postes_cluster( \
530 cluster_filter
={"service": service
}, \
531 titre
=u
"Organigramme du service %s" % service
.nom
,
532 cluster_titre
=service
.nom
)
538 return render(request
, 'rh/organigrammes/vide.html', c
,
539 content_type
="image/svg+xml"
544 @drh_or_admin_required
545 def organigrammes_implantation(request
, id):
547 implantation
= get_object_or_404(ref
.Implantation
, pk
=id)
548 svg
= rh_graph
.organigramme_postes_cluster( \
549 cluster_filter
={"implantation": implantation
}, \
550 titre
=u
"Organigramme de l'implantation %s" % implantation
.nom
,
551 cluster_titre
=implantation
.nom
)
557 return render(request
, 'rh/organigrammes/vide.html', c
,
558 content_type
="image/svg+xml"
563 @drh_or_admin_required
564 def organigrammes_region(request
, id):
566 region
= get_object_or_404(ref
.Region
, pk
=id)
567 svg
= rh_graph
.organigramme_postes_cluster( \
568 cluster_filter
={"implantation__region": region
}, \
569 titre
=u
"Organigramme du bureau de %s" % region
.nom
,
570 cluster_titre
=region
.nom
)
576 return render(request
,
577 'rh/organigrammes/vide.html', c
,
578 content_type
="image/svg+xml"