af5085271bf39cc8d85cbbb29d335a371f796f29
[auf_rh_dae.git] / project / rh / masse_salariale.py
1 # -*- encoding: utf-8 -*-
2 import time
3 import datetime
4 import csv
5 import StringIO
6 import itertools
7
8 from django.db.models import Q
9
10 from datamaster_modeles import models as ref
11 import rh.ods as ods
12 import rh.models as rh
13
14
15 KEY_DATE_DEBUT = "debut"
16 KEY_DATE_FIN = "fin"
17
18 TYPE_REMUN_BSTG = (3,)
19 TYPE_REMUN_MAD = (2,)
20 TYPE_REMUN_BASE = (1,)
21 TYPE_REMUN_FONC_RESP = (7, 8)
22 TYPE_REMUN_EXPAT = (4,)
23 TYPE_REMUN_LOGEMENT = (6,)
24 TYPE_REMUN_TRANSP = (9,)
25 TYPE_REMUN_13E = (18,)
26 TYPE_PRIME_INTERIM = (19,)
27 TYPE_REMUN_ALL_INDEMNITES = list(itertools.chain(*(TYPE_REMUN_BSTG,
28 TYPE_REMUN_MAD, TYPE_REMUN_BASE, TYPE_REMUN_FONC_RESP,
29 TYPE_REMUN_EXPAT, TYPE_REMUN_LOGEMENT, TYPE_REMUN_TRANSP,
30 TYPE_REMUN_13E, TYPE_PRIME_INTERIM)))
31 TYPE_PRIME_INSTALLATION = (13,)
32 TYPE_PRIME_DEMENAG = (15,)
33 TYPE_PRIME_AVION = (14,)
34 TYPE_PRIME_ALL = list(itertools.chain(
35 *(TYPE_PRIME_INSTALLATION, TYPE_PRIME_DEMENAG, TYPE_PRIME_AVION)
36 ))
37 TYPE_CHARGE_PATRONALE = (17,)
38 TYPE_CHARGE_ALL = list(itertools.chain(*(TYPE_CHARGE_PATRONALE,)))
39 TYPE_NATURE_INDEMN = u"Indemnité"
40 TYPE_NATURE_PAIEMENT = u"Accessoire"
41 TYPE_NATURE_CHARGES = u"Charges"
42 TYPE_NATURE_TRAITEMENT = u"Traitement"
43 HEADER_SEPARATOR = ('sep', ods.Separator, {'columnwidth': '0.4cm'})
44
45
46 class MasseSalariale():
47 """ Rapport de la masse salariale. """
48
49 def __init__(self, date_debut, date_fin, custom_filter=None,
50 ne_pas_grouper=False):
51 """ date_debut: date de début pour les données temporelles
52 date_fin: idem
53 custom_filter: dictionnaire des paramètres à passer au queryset.
54 """
55 if not date_debut and not date_fin:
56 return
57
58 date_debut = datetime.date(
59 *time.strptime(date_debut, "%d-%m-%Y")[0:3]
60 )
61 date_fin = datetime.date(*time.strptime(date_fin, "%d-%m-%Y")[0:3])
62
63 rapport_date_delta = date_fin - date_debut
64 rapport_date_delta += datetime.timedelta(days=1)
65
66 self.annee = date_fin.year
67
68 self.devise_base = rh.Devise.objects.filter(code='EUR')[0]
69 self.taux_change = {}
70
71 q_range = self.build_qs("date_", date_debut, date_fin)
72 q_range_d = self.build_qs("dossier__date_", date_debut, date_fin)
73 remunerations = rh.Remuneration.objects.filter(q_range) \
74 .filter(q_range_d) \
75
76 if custom_filter:
77 remunerations = remunerations.filter(**custom_filter)
78
79 remunerations = remunerations.exclude(supprime=True) \
80 .select_related(
81 "dossier", "dossier_employe", "dossier_poste", "type"
82 )
83
84 contenu = {}
85
86 lineariser_dossiers = not ne_pas_grouper
87
88 for r in remunerations:
89 if lineariser_dossiers:
90 key = r.dossier.employe_id
91 else:
92 key = r.dossier_id
93
94 if key not in contenu:
95 contenu[key] = {
96 'dossiers': set(),
97 'remunerations': []
98 }
99 if lineariser_dossiers:
100 contenu[key]['remunerations'].append(r)
101 else:
102 if r.dossier_id == key:
103 contenu[key]['remunerations'].append(r)
104 contenu[key]['dossiers'].add(r.dossier)
105
106 self.rapport = []
107
108 pays_list = {}
109 for pays in ref.Pays.objects.all():
110 pays_list[pays.id] = pays
111
112 valeurs_point_par_imp = \
113 dict(
114 (v.implantation.id, v) for v in \
115 rh.ValeurPoint.objects.filter(annee=self.annee).all()
116 )
117
118 self.headers = (
119 ('bureau', u"Bureau", {'columnwidth': '2cm'}),
120 ('pays', u"Pays", {'columnwidth': '3.5cm'}),
121 ('implantation', u"Implantation", {'columnwidth': '3cm'}),
122 ('valeur_point', u"Valeur du point",
123 {'columnwidth': '2.3cm'}),
124 ('numero_employe', u"Numéro d'employé",
125 {'columnwidth': '2.4cm'}),
126 ('nom', u"Nom", {'columnwidth': '5.4cm'}),
127 ('prenom', u"Prénom", {'columnwidth': '4.8cm'}),
128 ('type_de_poste', u"Type de poste", {'columnwidth': '2.7cm'}),
129 ('intitule_de_poste', u"Intitulé du poste",
130 {'columnwidth': '7.25cm'}),
131 ('niveau', u"Niveau", {'columnwidth': '1.75cm'}),
132 ('point', u"Point", {'columnwidth': '1.75cm'}),
133 ('regime_de_travail', u"Régime de travail",
134 {'columnwidth': '2cm'}),
135 ('local_expatrie', u"Local / Expatrié",
136 {'columnwidth': '2.25cm'}),
137 ('statut', u"Statut", {'columnwidth': '1.25cm'}),
138 ('date_fin_contrat', u"Date de fin de contrat",
139 {'columnwidth': '2cm'}),
140 HEADER_SEPARATOR,
141 ('date_debut', u"Date de début", {'columnwidth': '1.92cm'}),
142 ('date_fin', u"Date de fin", {'columnwidth': '1.92cm'}),
143 ('nb_jours', u"Nombre de jours", {'columnwidth': '2.82cm'}),
144 HEADER_SEPARATOR,
145 ('devise', u"Devise", {'columnwidth': '1.46cm'}),
146 ('salaire_bstg_annuel', u"Salaire BSTG ANNUEL",
147 {'columnwidth': '2.5cm'}),
148 ('salaire_bstg_total', u"Salaire BSTG total ",
149 {'columnwidth': '2.5cm'}),
150 ('organisme_bstg', u"Organisme BSTG",
151 {'columnwidth': '2.9cm'}),
152 HEADER_SEPARATOR,
153 ('salaire_theorique', u"Salaire théorique",
154 {'columnwidth': '2.5cm'}),
155 ('salaire_base_brut', u"Salaire de base brut",
156 {'columnwidth': '2.5cm'}),
157 ('salaire_complementaire', u"Salaire complémentaire",
158 {'columnwidth': '2.5cm'}),
159 HEADER_SEPARATOR,
160 ('indemnite_fonctions', u"Indemnités de fonctions",
161 {'columnwidth': '2.5cm'}),
162 ('indemnite_expat', u"Indemnités d'expatriation",
163 {'columnwidth': '2.5cm'}),
164 ('indemnite_logement', u"Indemnités de logement",
165 {'columnwidth': '2.5cm'}),
166 ('indemnite_transp', u"Indemnités de transport",
167 {'columnwidth': '2.5cm'}),
168 ('indemnite_13e', u"Indemnités 13e mois",
169 {'columnwidth': '2.5cm'}),
170 ('prime_interim', u"Prime d'intérim",
171 {'columnwidth': '2.5cm'}),
172 ('indemnite_autre', u"Autre indemnités",
173 {'columnwidth': '2.5cm'}),
174 ('indemnite_sous_total', u"Sous-total d'indemnités",
175 {'columnwidth': '2.5cm'}),
176 HEADER_SEPARATOR,
177 ('prime_installation', u"Prime d'installation",
178 {'columnwidth': '2.5cm'}),
179 ('prime_demenagement', u"Prime de déménagement",
180 {'columnwidth': '2.5cm'}),
181 ('prime_avion', u"Prime d'avion",
182 {'columnwidth': '2.5cm'}),
183 ('prime_autre', u"Autre prime",
184 {'columnwidth': '2.5cm'}),
185 ('prime_sous_total', u"Total des primes",
186 {'columnwidth': '2.5cm'}),
187 HEADER_SEPARATOR,
188 ('charges_patronales', u"Charges patronales",
189 {'columnwidth': '2.5cm'}),
190 ('charges_autre', u"Autres charges patronales",
191 {'columnwidth': '2.5cm'}),
192 ('charges_sous_total', u"Sous-total des charges patronales",
193 {'columnwidth': '2.5cm'}),
194 HEADER_SEPARATOR,
195 ('sous_total_traitement_annee', u"Total traitement annuel"),
196 ('sous_total_indemnite_annee', u"Total indemnités annuel"),
197 ('sous_total_accessoire_annee', u"Total accessoires annuel"),
198 ('sous_total_charges_annee', u"Total charges annuel"),
199 HEADER_SEPARATOR,
200 ('masse_salariale', u"Masse salariale annuelle",
201 {'columnwidth': '2.5cm'}),
202 ('masse_salariale_annee', u"Masse salariale %s" % self.annee,
203 {'columnwidth': '2.5cm'}),
204 ('masse_salariale_annee_euro', u"Masse salariale %s EUR" % \
205 self.annee, {'columnwidth': '2.5cm'}),
206 )
207
208 grand_total = 0.0
209 grand_total_euro = 0.0
210
211 for item in contenu.values():
212 dossiers = item['dossiers']
213 remuns = item['remunerations']
214
215 for d in dossiers:
216 if d.principal:
217 dossier = list(dossiers)[0]
218
219 regime = float(dossier.poste.regime_travail) / 100
220
221 if dossier.statut_residence == "expat":
222 statut = "E"
223 else:
224 statut = "L"
225
226 #on détermine la date du début et fin du dossier si année en cours
227 try:
228 d_date_fin = dossier.date_fin \
229 if dossier.date_fin.year == date_fin.year else None
230 except AttributeError:
231 d_date_fin = None
232 try:
233 d_date_debut = dossier.date_debut \
234 if dossier.date_debut.year == date_fin.year else None
235 except AttributeError:
236 d_date_debut = None
237
238 pays = \
239 pays_list[dossier.poste.implantation.adresse_physique_pays.id]
240
241 #on détermine si les rémunérations sont tous dans la même devise
242 devise = remuns[0].devise
243 meme_devise = True
244 for r in remuns[1:]:
245 if devise != r.devise:
246 meme_devise = False
247
248 if not meme_devise:
249 for r in remuns:
250 self.convertir(r)
251
252 bstg_dossier = None
253 for d in dossiers:
254 if d.organisme_bstg:
255 bstg_dossier = d
256
257 bstg_remun = None
258 if bstg_dossier:
259 for r in bstg_dossier.rh_remunerations.all():
260 if r.type.id in TYPE_REMUN_BSTG:
261 bstg_remun = r
262
263 if bstg_remun:
264 bstg_remun_euro = rh.Remuneration(
265 montant=bstg_remun.montant, devise=bstg_remun.devise
266 )
267 self.convertir(bstg_remun_euro)
268
269 salaire_complement = 0.0
270 salaire_base = 0.0
271 indemnites = {
272 'fonc_resp': 0.0,
273 'expat': 0.0,
274 'logement': 0.0,
275 'transp': 0.0,
276 '13e': 0.0,
277 'autre_recurr': 0.0,
278 'interim': 0.0,
279 }
280
281 primes = {
282 'installation': 0.0,
283 'demenagement': 0.0,
284 'avion': 0.0,
285 'autre': 0.0,
286 }
287 charges = {
288 'patronale': 0.0,
289 'autre': 0.0,
290 }
291
292 total_remun_annee = {
293 'traitement': 0.0,
294 'indemnite': 0.0,
295 'accessoire': 0.0,
296 'charges': 0.0,
297 }
298
299 for r in remuns:
300 montant = float(r.montant)
301
302 if r.type_id in TYPE_REMUN_MAD:
303 salaire_complement += montant
304
305 if r.type_id in TYPE_REMUN_BASE:
306 salaire_base += montant
307
308 if r.type_id in TYPE_REMUN_FONC_RESP:
309 indemnites['fonc_resp'] += montant
310
311 if r.type_id in TYPE_REMUN_EXPAT:
312 indemnites['expat'] += montant
313
314 if r.type_id in TYPE_REMUN_LOGEMENT:
315 indemnites['logement'] += montant
316
317 if r.type_id in TYPE_REMUN_TRANSP:
318 indemnites['transp'] += montant
319
320 if r.type_id in TYPE_REMUN_13E:
321 indemnites['13e'] += montant
322
323 if r.type_id in TYPE_PRIME_INTERIM:
324 indemnites['interim'] += montant
325
326 if r.type_id not in TYPE_REMUN_ALL_INDEMNITES \
327 and r.type.nature_remuneration == TYPE_NATURE_INDEMN:
328 indemnites['autre_recurr'] += montant
329
330 if r.type_id in TYPE_PRIME_INSTALLATION:
331 primes['installation'] += montant
332
333 if r.type_id in TYPE_PRIME_DEMENAG:
334 primes['demenagement'] += montant
335
336 if r.type_id in TYPE_PRIME_AVION:
337 primes['avion'] += montant
338
339 if r.type_id not in TYPE_PRIME_ALL and \
340 r.type.nature_remuneration == TYPE_NATURE_PAIEMENT:
341 primes['autre'] += montant
342
343 if r.type_id in TYPE_CHARGE_PATRONALE:
344 charges['patronale'] += montant
345
346 if r.type_id not in TYPE_CHARGE_ALL and \
347 r.type.nature_remuneration == TYPE_NATURE_CHARGES:
348 charges['autre'] += montant
349
350 if r.type.nature_remuneration == TYPE_NATURE_INDEMN:
351 total_remun_annee['indemnite'] += montant
352
353 if r.type.nature_remuneration == TYPE_NATURE_PAIEMENT:
354 total_remun_annee['accessoire'] += montant
355
356 if r.type.nature_remuneration == TYPE_NATURE_CHARGES:
357 total_remun_annee['charges'] += montant
358
359 if r.type.nature_remuneration == TYPE_NATURE_TRAITEMENT:
360 total_remun_annee['traitement'] += montant
361
362 total_indemnites = sum(indemnites.values())
363
364 #Calcul du nombre de jours pour ce dossier.
365 if dossier.date_debut and dossier.date_debut > date_debut and \
366 not dossier.date_fin:
367 date_delta = date_fin - dossier.date_fin
368 elif dossier.date_fin and dossier.date_fin < date_fin and \
369 not dossier.date_debut:
370 date_delta = dossier.date_fin - date_debut
371 elif dossier.date_fin and dossier.date_debut:
372 date_delta = dossier.date_fin - date_debut
373 else:
374 date_delta = rapport_date_delta
375
376 masse_salariale = (salaire_base + total_indemnites + \
377 sum(primes.values()) + sum(charges.values()))
378 masse_salariale_euro = rh.Remuneration(montant=masse_salariale,
379 devise=remuns[0].devise)
380 self.convertir(masse_salariale_euro)
381
382 if dossier.classement and dossier.classement.coefficient:
383 coefficient = dossier.classement.coefficient
384 else:
385 coefficient = ""
386
387 #todo valeur du point si pas présent
388 valeur_point = valeurs_point_par_imp.get(
389 dossier.poste.implantation_id
390 ) or ""
391
392 salaire_theorique = (
393 round(valeur_point.valeur * int(coefficient) * regime, 2) \
394 if valeur_point and coefficient and regime else None)
395
396 rapport_nombre_jours = (float(date_delta.days)
397 / rapport_date_delta.days)
398 item_rapport = {
399 'bureau': dossier.poste.implantation.region.code,
400 'pays': unicode(pays),
401 'implantation': dossier.poste.implantation.nom_court,
402 'type_implantation': dossier.poste.implantation.type,
403 #'imputation': None,
404 'valeur_point': valeur_point,
405 'numero_employe': dossier.employe_id,
406 'nom': dossier.employe.nom.upper(),
407 'prenom': dossier.employe.prenom,
408 'type_de_poste': dossier.poste.type_poste.nom,
409 'intitule_de_poste': dossier.poste.nom_feminin
410 if dossier.employe.genre == "F" else
411 dossier.poste.nom,
412 'niveau': unicode(dossier.classement),
413 'point': coefficient,
414 'regime_de_travail': "%s %%" % int(regime * 100),
415 'local_expatrie': statut,
416 'statut': dossier.statut.code,
417 'date_fin_contrat': dossier.date_fin or "",
418 'date_debut': d_date_debut or "",
419 'date_fin': d_date_fin or "",
420 'nb_jours': date_delta.days,
421 'devise': remuns[0].devise.code,
422 'salaire_bstg_annuel': bstg_remun.montant \
423 if bstg_remun else "",
424 'salaire_bstg_total': bstg_remun_euro.montant \
425 if bstg_remun else "",
426 'organisme_bstg': dossier.organisme_bstg or "",
427 'salaire_theorique': salaire_theorique,
428 'salaire_base_brut': \
429 salaire_base * regime * rapport_nombre_jours,
430 'salaire_complementaire': \
431 salaire_complement * regime *
432 rapport_nombre_jours,
433 #'salaire_total': None
434 'indemnite_fonctions': indemnites['fonc_resp'] * \
435 regime * rapport_nombre_jours,
436 'indemnite_expat': indemnites['expat'] * regime * \
437 rapport_nombre_jours,
438 'indemnite_logement': indemnites['logement'] * \
439 regime * rapport_nombre_jours,
440 'indemnite_transp': indemnites['transp'] * regime * \
441 rapport_nombre_jours,
442 'indemnite_13e': indemnites['13e'] * regime * \
443 rapport_nombre_jours,
444 'prime_interim': indemnites['interim'] * regime * \
445 rapport_nombre_jours,
446 'indemnite_autre': indemnites['autre_recurr'] * \
447 regime * rapport_nombre_jours,
448 'indemnite_sous_total': total_indemnites * regime * \
449 rapport_nombre_jours,
450 'total_brut': (
451 total_indemnites + salaire_base +
452 salaire_complement
453 ) * regime * rapport_nombre_jours,
454 'prime_installation': primes['installation'] * regime * \
455 rapport_nombre_jours,
456 'prime_demenagement': primes['demenagement'] * regime * \
457 rapport_nombre_jours,
458 'prime_avion': primes['avion'] * regime * \
459 rapport_nombre_jours,
460 'prime_autre': primes['autre'] * regime * \
461 rapport_nombre_jours,
462 'prime_sous_total': sum(primes.values()) * regime * \
463 rapport_nombre_jours,
464 'charges_patronales': charges['patronale'],
465 'charges_autre': charges['autre'],
466 'charges_sous_total': sum(charges.values()),
467 'sous_total_traitement_annee': \
468 total_remun_annee['traitement'],
469 'sous_total_indemnite_annee': \
470 total_remun_annee['indemnite'],
471 'sous_total_accessoire_annee': \
472 total_remun_annee['accessoire'],
473 'sous_total_charges_annee': \
474 total_remun_annee['charges'],
475 'masse_salariale': masse_salariale,
476 'masse_salariale_annee': masse_salariale * \
477 regime * rapport_nombre_jours,
478 'masse_salariale_annee_euro': \
479 masse_salariale_euro.montant * regime *
480 rapport_nombre_jours,
481 'sep': ods.Separator(),
482 }
483
484 grand_total += round(masse_salariale, 2)
485 grand_total_euro += round(masse_salariale_euro.montant * regime
486 * (
487 date_delta.days / rapport_date_delta.days
488 ), 2)
489
490 self.rapport.append(item_rapport)
491
492 self.rapport = sorted(self.rapport, key=lambda r: r['nom'])
493
494 self.grand_totaux = (grand_total, grand_total_euro)
495
496 def build_qs(self, prefix, date_debut, date_fin):
497 date_debut_null = \
498 Q(**{"%s%s__isnull" % (prefix, KEY_DATE_DEBUT): True})
499 date_fin_null = \
500 Q(**{"%s%s__isnull" % (prefix, KEY_DATE_FIN): True})
501 date_debut_superieure_ou_egale_a_borne_gauche = \
502 Q(**{"%s%s__gte" % (prefix, KEY_DATE_DEBUT): date_debut})
503 date_debut_inferieure_ou_egale_a_borne_gauche = \
504 Q(**{"%s%s__lte" % (prefix, KEY_DATE_DEBUT): date_debut})
505 date_fin_superieure_ou_egale_a_borne_gauche = \
506 Q(**{"%s%s__gte" % (prefix, KEY_DATE_FIN): date_debut})
507 date_fin_inferieure_ou_egale_a_borne_droite = \
508 Q(**{"%s%s__lte" % (prefix, KEY_DATE_FIN): date_fin})
509 date_debut_inferieure_ou_egale_a_borne_droite = \
510 Q(**{"%s%s__lte" % (prefix, KEY_DATE_DEBUT): date_fin})
511 date_fin_superieure_ou_egale_a_borne_droite = \
512 Q(**{"%s%s__gte" % (prefix, KEY_DATE_FIN): date_fin})
513
514 q_range = \
515 (
516 date_debut_null & date_fin_null
517 ) | (
518 date_debut_inferieure_ou_egale_a_borne_gauche &
519 date_fin_superieure_ou_egale_a_borne_gauche &
520 date_fin_inferieure_ou_egale_a_borne_droite
521 ) | (
522 date_debut_superieure_ou_egale_a_borne_gauche &
523 date_debut_inferieure_ou_egale_a_borne_droite &
524 date_fin_superieure_ou_egale_a_borne_droite
525 ) | (
526 date_debut_inferieure_ou_egale_a_borne_gauche &
527 date_fin_superieure_ou_egale_a_borne_droite
528 ) | (
529 date_debut_null &
530 date_fin_superieure_ou_egale_a_borne_droite
531 ) | (
532 date_debut_inferieure_ou_egale_a_borne_gauche &
533 date_fin_null
534 )
535
536 return q_range
537
538 def convertir(self, remuneration):
539 if remuneration.devise != self.devise_base:
540 try:
541 remuneration.montant = float(remuneration.montant) * \
542 self.trouver_taux(remuneration.devise).taux
543 remuneration.devise = self.devise_base
544 except AttributeError:
545 pass
546
547 def trouver_taux(self, devise):
548 if devise.code not in self.taux_change:
549 try:
550 t = rh.TauxChange.objects.filter(
551 devise=devise, annee=self.annee
552 )[0]
553 except IndexError:
554 return None
555 self.taux_change[devise.code] = t
556 return self.taux_change[devise.code]
557
558 def csv(self):
559 self.csv_handle = StringIO.StringIO()
560 csv_writer = csv.writer(self.csv_handle, delimiter=",",
561 doublequote=False, escapechar="\\", quoting=csv.QUOTE_ALL,
562 )
563 header = [v[0] for v in self.rapport[0]]
564 csv_writer.writerow(header)
565 for row in self.rapport:
566 values = [v[1] for v in row]
567 csv_writer.writerow(
568 [unicode(r).encode('utf-8') for r in values]
569 )
570
571 def ods(self):
572 self.doc = ods.OpenDocumentSpreadsheet()
573 table = self.doc.add_table(name=u'Masse salariale %s' % self.annee)
574
575 for h in self.headers:
576 if len(h) > 2:
577 table.add_column(**h[2])
578
579 table.add_row([h[1] for h in self.headers], rowheight='2cm')
580
581 for r in self.rapport:
582 table.add_row([r[h[0]] for h in self.headers])
583
584 #a.doc.write('hello_world.ods')