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