From fc62be5d8aacff0ecc1747e09ba97f92ea656f45 Mon Sep 17 00:00:00 2001 From: Eric Mc Sween Date: Fri, 15 Jun 2012 14:22:19 -0400 Subject: [PATCH] =?utf8?q?[#3068]=20[#3069]=20[#3133]=20R=C3=A9=C3=A9criture?= =?utf8?q?=20du=20rapport=20de=20masse=20salariale?= MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit --- buildout.cfg | 5 +- project/rh/forms.py | 58 ++- project/rh/managers.py | 145 ++++--- project/rh/models.py | 6 +- project/rh/ods.py | 296 +++++++------- project/rh/static/rh/FixedHeader.min.js | 40 ++ .../rh/templates/rh/rapports/masse_salariale.html | 314 ++++++++++----- project/rh/views.py | 410 ++++++++++++++------ project/settings.py | 5 +- versions.cfg | 3 + 10 files changed, 855 insertions(+), 427 deletions(-) create mode 100644 project/rh/static/rh/FixedHeader.min.js diff --git a/buildout.cfg b/buildout.cfg index 0213d45..076ff10 100644 --- a/buildout.cfg +++ b/buildout.cfg @@ -13,8 +13,9 @@ find-links = http://pypi.auf.org/simple/auf.recipe.django/ http://pypi.auf.org/simple/auf.django.admingroup/ http://pypi.auf.org/simple/auf.django.permissions/ http://pypi.auf.org/simple/auf.django.emploi/ - http://pypi.auf.org/django-alphafilter/ http://pypi.auf.org/simple/auf.django.references/ + http://pypi.auf.org/simple/django-alphafilter/ + http://pypi.auf.org/simple/odsgen/ develop = src/qbe src/auf.django.metadata eggs = @@ -40,7 +41,7 @@ eggs = auf.django.skin auf.django.workflow auf.recipe.django - odfpy + odsgen django-picklefield pygraphviz simplejson diff --git a/project/rh/forms.py b/project/rh/forms.py index a1671a2..c2d1aff 100644 --- a/project/rh/forms.py +++ b/project/rh/forms.py @@ -1,9 +1,15 @@ # -*- encoding: utf-8 -*- -from django import forms +from datetime import date + from ajax_select.fields import AutoCompleteSelectField -from project.rh.models import Dossier, Contrat, AyantDroit, Employe, \ - ResponsableImplantation +from auf.django.references import models as ref +from django import forms +from django.db.models import Min + +from project.groups import get_employe_from_user +from project.permissions import user_gere_obj_de_sa_region +from project.rh import models as rh class AjaxSelect(object): @@ -38,13 +44,13 @@ class FormDate(object): class DossierForm(forms.ModelForm, FormDate): class Model: - model = Dossier + model = rh.Dossier class ContratForm(forms.ModelForm, FormDate): class Model: - model = Contrat + model = rh.Contrat class AyantDroitForm(forms.ModelForm, AjaxSelect): @@ -56,11 +62,10 @@ class AyantDroitForm(forms.ModelForm, AjaxSelect): self.fields['date_naissance'].widget = forms.widgets.DateInput() class Meta: - model = AyantDroit + model = rh.AyantDroit class EmployeAdminForm(forms.ModelForm, AjaxSelect): - nationalite = AutoCompleteSelectField( 'pays', help_text="Taper le nom ou le code du pays", required=False ) @@ -69,7 +74,7 @@ class EmployeAdminForm(forms.ModelForm, AjaxSelect): ) class Meta: - model = Employe + model = rh.Employe def __init__(self, *args, **kwargs): super(EmployeAdminForm, self).__init__(*args, **kwargs) @@ -77,8 +82,41 @@ class EmployeAdminForm(forms.ModelForm, AjaxSelect): class ResponsableInlineForm(forms.ModelForm): - employes_actifs = Employe.objects.actifs() + employes_actifs = rh.Employe.objects.actifs() employe = forms.ModelChoiceField(queryset=employes_actifs) class Meta: - model = ResponsableImplantation + model = rh.ResponsableImplantation + + +class MasseSalarialeForm(forms.Form): + region = forms.ModelChoiceField( + label=u'Région', queryset=ref.Region.objects.all(), required=False + ) + implantation = forms.ModelChoiceField( + label=u'Implantation', queryset=ref.Implantation.objects.all(), + required=False + ) + + def __init__(self, user, *args, **kwargs): + super(MasseSalarialeForm, self).__init__(*args, **kwargs) + min_date = rh.Remuneration.objects \ + .aggregate(Min('date_debut'))['date_debut__min'] + years = range( + date.today().year, + min_date.year if min_date else 1997, + -1 + ) + self.fields['annee'] = forms.TypedChoiceField( + label='Année', choices=zip(years, years), coerce=int, + required=False + ) + if user_gere_obj_de_sa_region(user): + employe = get_employe_from_user(user) + self.fields['region'].queryset = ref.Region.objects.filter( + id=employe.implantation.region.id + ) + self.fields['implantation'].queryset = \ + ref.Implantation.objects.filter( + region=employe.implantation.region + ) diff --git a/project/rh/managers.py b/project/rh/managers.py index 15ee63e..605276e 100644 --- a/project/rh/managers.py +++ b/project/rh/managers.py @@ -1,23 +1,17 @@ # -*- encoding: utf-8 -*- -import datetime from datetime import date from django.db import models from django.db.models import Q +from django.db.models.query import QuerySet from auf.django.metadata.managers import NoDeleteManager, NoDeleteQuerySet from project.groups import get_employe_from_user -from project.groups import grp_administrateurs, \ - grp_directeurs_bureau, \ - grp_drh, \ - grp_drh2, \ - grp_accior, \ - grp_abf, \ - grp_haute_direction, \ - grp_service_utilisateurs, \ - grp_correspondants_rh +from project.groups import \ + grp_drh, grp_drh2, grp_accior, grp_abf, grp_haute_direction, \ + grp_service_utilisateurs class SecurityManager(models.Manager): @@ -28,8 +22,8 @@ class SecurityManager(models.Manager): def ma_region_ou_service(self, user): """ Filtrage des postes en fonction du user connecté (region / service) - On s'intéresse aussi au groupe auquel appartient le user car certains groupes - peuvent tout voir. + On s'intéresse aussi au groupe auquel appartient le user car + certains groupes peuvent tout voir. """ employe = get_employe_from_user(user) @@ -37,11 +31,12 @@ class SecurityManager(models.Manager): # TRAITEMENT NORMAL ############################################ # REGION - q = Q(**{ self.prefixe_implantation : employe.implantation.region }) + q = Q(**{self.prefixe_implantation: employe.implantation.region}) # SERVICE - if self.prefixe_service and grp_service_utilisateurs in user.groups.all(): - q = q | Q(**{ self.prefixe_service : employe.service}) + if self.prefixe_service \ + and grp_service_utilisateurs in user.groups.all(): + q = q | Q(**{self.prefixe_service: employe.service}) liste = self.get_query_set().filter(q) @@ -75,35 +70,55 @@ class SecurityManager(models.Manager): return liste +class ActifsQuerySet(QuerySet): + + def _actifs(self, debut_field, fin_field, date_min=None, date_max=None, + annee=None): + qs = self + if annee: + janvier = date(annee, 1, 1) + decembre = date(annee, 12, 31) + date_min = max(janvier, date_min) if date_min else janvier + date_max = min(decembre, date_max) if date_max else decembre + if not date_min and not date_max: + date_min = date_max = date.today() + if date_min: + qs = qs.filter( + Q(**{fin_field + '__gte': date_min}) | + Q(**{fin_field: None}) + ) + if date_max: + qs = qs.filter( + Q(**{debut_field + '__lte': date_max}) | + Q(**{debut_field: None}) + ) + return qs + + def actifs(self, *args, **kwargs): + return self._actifs('date_debut', 'date_fin', *args, **kwargs) + + +class PosteQuerySet(NoDeleteQuerySet, ActifsQuerySet): + pass + + class PosteManager(SecurityManager, NoDeleteManager): - """ - Chargement de tous les objets FK existants sur chaque QuerySet. - """ prefixe_service = "service" prefixe_implantation = "implantation__region" - def actifs(self): - q_actif = Q(date_fin__gt=datetime.datetime.now()) | Q(date_fin__isnull=True) - return super(PosteManager, self).get_query_set().filter(q_actif) + def get_query_set(self): + return PosteQuerySet(self.model).filter(supprime=False) \ + .select_related('type_poste') + + def actifs(self, *args, **kwargs): + return self.get_query_set().actifs(*args, **kwargs) def ma_region_ou_service(self, user): return super(PosteManager, self).ma_region_ou_service(user) - def get_query_set(self): - fkeys = ( - #'id_rh', - #'responsable', - #'implantation', - #'implantation.bureau_rattachement', - 'type_poste', - #'service', - #'classement_min', - #'classement_max', - #'valeur_point_min', - #'valeur_point_max', - ) - return super(PosteManager, self).get_query_set() \ - .select_related(*fkeys).all() + +class DossierQuerySet(NoDeleteQuerySet, ActifsQuerySet): + pass class DossierManager(SecurityManager, NoDeleteManager): @@ -111,36 +126,39 @@ class DossierManager(SecurityManager, NoDeleteManager): prefixe_implantation = "poste__implantation__region" def get_query_set(self): - fkeys = ( - 'poste', - 'employe', - ) - return super(DossierManager, self).get_query_set() \ - .select_related(*fkeys).all() + return DossierQuerySet(self.model) \ + .filter(supprime=False) \ + .select_related('poste', 'employe') - def ma_region_ou_service(self, user): - return super(DossierManager, self).ma_region_ou_service(user) + def actifs(self, *args, **kwargs): + return self.get_query_set().actifs(*args, **kwargs) + + +class RemunerationQuerySet(NoDeleteQuerySet, ActifsQuerySet): + + def actifs(self, *args, **kwargs): + return self \ + ._actifs('date_debut', 'date_fin', *args, **kwargs) \ + ._actifs( + 'dossier__date_debut', 'dossier__date_fin', *args, **kwargs + ) + + +class RemunerationManager(NoDeleteManager): + + def get_query_set(self): + return RemunerationQuerySet(self.model).filter(supprime=False) + + def actifs(self, *args, **kwargs): + return self.get_query_set().actifs(*args, **kwargs) -class EmployeQuerySet(NoDeleteQuerySet): +class EmployeQuerySet(NoDeleteQuerySet, ActifsQuerySet): + def actifs(self, date_min=None, date_max=None, annee=None): - qs = self - if annee: - janvier = date(annee, 1, 1) - decembre = date(annee, 12, 31) - date_min = max(janvier, date_min) if date_min else janvier - date_max = min(decembre, date_max) if date_max else decembre - if not date_min and not date_max: - date_min = date_max = date.today() - if date_min: - qs = qs.filter( - Q(rh_dossiers__date_fin__gte=date_min) | Q(rh_dossiers__date_fin=None) - ) - if date_max: - qs = qs.filter( - Q(rh_dossiers__date_debut__lte=date_max) | Q(rh_dossiers__date_debut=None) - ) - return qs.distinct() + return self._actifs('dossiers__date_debut', 'dossiers__date_fin') \ + .distinct() + class EmployeManager(NoDeleteManager): def get_query_set(self): @@ -150,6 +168,7 @@ class EmployeManager(NoDeleteManager): def actifs(self, *args, **kwargs): return self.get_query_set().actifs(*args, **kwargs) + class PosteComparaisonManager(SecurityManager): use_for_related_fields = True prefixe_implantation = "implantation__region" @@ -163,8 +182,10 @@ class DossierComparaisonManager(SecurityManager): class DeviseManager(NoDeleteManager): pass + class ServiceManager(NoDeleteManager): pass + class TypeRemunerationManager(NoDeleteManager): pass diff --git a/project/rh/models.py b/project/rh/models.py index f39e795..e82c5c1 100644 --- a/project/rh/models.py +++ b/project/rh/models.py @@ -22,7 +22,7 @@ from project.rh.managers import \ PosteManager, DossierManager, EmployeManager, \ DossierComparaisonManager, \ PosteComparaisonManager, DeviseManager, ServiceManager, \ - TypeRemunerationManager + TypeRemunerationManager, RemunerationManager from project.rh.validators import validate_date_passee @@ -440,7 +440,6 @@ class PosteCommentaire(Commentaire): ) - ### EMPLOYÉ/PERSONNE class Employe(AUFMetadata): @@ -1001,7 +1000,6 @@ class DossierPiece(DossierPiece_): ) - class DossierCommentaire(Commentaire): dossier = models.ForeignKey( Dossier, db_column='dossier', related_name='commentaires' @@ -1064,6 +1062,8 @@ class RemunerationMixin(AUFMetadata): date_debut = models.DateField(u"date de début", null=True, blank=True) date_fin = models.DateField(u"date de fin", null=True, blank=True) + objects = RemunerationManager() + class Meta: abstract = True ordering = ['type__nom', '-date_fin'] diff --git a/project/rh/ods.py b/project/rh/ods.py index 07f0976..bb76ae5 100644 --- a/project/rh/ods.py +++ b/project/rh/ods.py @@ -1,152 +1,152 @@ # encoding: utf-8 -from decimal import Decimal - -import odf.opendocument -import odf.style -import odf.table -from odf.style import Style, MasterPage, PageLayout, PageLayoutProperties, \ - TextProperties, GraphicProperties, ParagraphProperties, \ - DrawingPageProperties - - -class Separator(): - - def __unicode__(self): - return u"" - - def __str__(self): - return "" - - -def valuetype(val): - valuetype = "string" - if isinstance(val, str): - valuetype = "string" - if isinstance(val, (int, float, Decimal)): - valuetype = "float" - if isinstance(val, bool): - valuetype = "boolean" - - return valuetype - - -class Wrapper(object): - - def __init__(self, *args, **kwargs): - self.__wrapped = self._wrapper_constructor(*args, **kwargs) - - def __getattr__(self, attr): - return getattr(self.__wrapped, attr) - - -class OpenDocumentSpreadsheet(Wrapper): - _wrapper_constructor = staticmethod( - odf.opendocument.OpenDocumentSpreadsheet +import odsgen + + +def masse_salariale(lignes, annee, titres_traitements, titres_indemnites, + titres_primes, titres_charges, masse_salariale_totale): + doc = odsgen.Document() + doc.add_iso_date_style('iso-date') + + table = doc.add_table(name=u'Masse salariale') + for width in ( + ['1.75cm', '5cm', '4cm', '3cm', '3.5cm', '5cm', '4cm', '6cm', + '14cm', '2.5cm', '1.5cm', '4.5cm', '3cm', '1.5cm', '3.75cm', + '0.5cm', '2.5cm', '2cm', '3cm', '0.5cm', '1.5cm', '4cm', + '3.25cm', '5cm', '0.5cm', '4.25cm'] + + ['5cm'] * len(titres_traitements) + ['4cm', '0.5cm'] + + ['5cm'] * len(titres_indemnites) + ['4cm', '0.5cm'] + + ['5cm'] * len(titres_primes) + ['4cm', '0.5cm'] + + ['5cm'] * len(titres_charges) + ['4cm', '0.5cm'] + + ['3.75cm'] * 4 + ['0.5cm', '2.75cm', '4cm'] + ): + table.add_column(columnwidth=width) + + row = table.add_row() + row.add_cells([ + u'Bureau', u'Pays', u'Implantation', u'Valeur du point', + u"Numéro d'employé", u'Nom', u'Prénom', u'Type de poste', + u'Intitulé du poste', u'Niveau actuel', u'Points', + u'Régime de travail annuel', u'Local / Expatrié', u'Statut', + u'Date de fin de contrat' + ], fontweight='bold') + row.add_cell(backgroundcolor='#d3d3d3') + row.add_cells([ + u'Date de début', u'Date de fin', u'Nombre de jours' + ], fontweight='bold') + row.add_cell(backgroundcolor='#d3d3d3') + row.add_cells([ + u'Devise', u'Salaire BSTG ANNUEL', u'Salaire BSTG EUR', + u'Organisme BSTG' + ], fontweight='bold') + row.add_cell(backgroundcolor='#d3d3d3') + row.add_cells( + [u'Salaire théorique annuel'] + titres_traitements + + [u'Total des traitements'], + backgroundcolor='#ecab44', fontweight='bold' + ) + row.add_cell(backgroundcolor='#d3d3d3') + row.add_cells( + titres_indemnites + [u'Total des indemnités'], + backgroundcolor='#fff840', fontweight='bold' + ) + row.add_cell(backgroundcolor='#d3d3d3') + row.add_cells( + titres_primes + [u'Total des primes'], + backgroundcolor='#d7fb0f', fontweight='bold' + ) + row.add_cell(backgroundcolor='#d3d3d3') + row.add_cells( + titres_charges + [u'Total des charges'], + backgroundcolor='#fb680f', fontweight='bold' + ) + row.add_cell(backgroundcolor='#d3d3d3') + row.add_cell( + u'Total des traitements', backgroundcolor='#ecab44', + fontweight='bold' + ) + row.add_cell( + u'Total des indemnités', backgroundcolor='#fff840', + fontweight='bold' + ) + row.add_cell( + u'Total des primes', backgroundcolor='#d7fb0f', + fontweight='bold' + ) + row.add_cell( + u'Total des charges', backgroundcolor='#fb680f', + fontweight='bold' + ) + row.add_cell(backgroundcolor='#d3d3d3') + row.add_cells( + [u'Masse salariale', u'Masse salariale EUR'], + backgroundcolor='#e6c6ed', fontweight='bold' ) - def __init__(self, *args, **kwargs): - super(OpenDocumentSpreadsheet, self).__init__(*args, **kwargs) - self._automatic_style_idx = 0 - - def add_table(self, **kwargs): - table = Table(**kwargs) - table._doc = self - self.spreadsheet.addElement(table) - return table - - def add_automatic_style(self, **kwargs): - name = 'auto_style_%d' % self._automatic_style_idx - style = odf.style.Style(name=name, **kwargs) - self.automaticstyles.addElement(style) - self._automatic_style_idx += 1 - return style - - -class Table(Wrapper): - _wrapper_constructor = staticmethod(odf.table.Table) - - def add_row(self, values=[], **kwargs): - # attributs appartenant à table-column-poperties -# props = {} -# for attr in ['rowheight']: -# if attr in kwargs: -# props[attr] = kwargs.pop(attr) - - style = self._doc.add_automatic_style(family='table-row') - if 'rowheight' in kwargs: - style.addElement(odf.style.TableRowProperties( - rowheight=kwargs['rowheight'])) - kwargs['stylename'] = style.getAttribute('name') - del kwargs['rowheight'] - - style = {} - - row = TableRow(**kwargs) - row._doc = self._doc - for value in values: - row.add_cell(value, verticalalign='middle', **style) - self.addElement(row) - return row - - def add_column(self, **kwargs): - - # attributs appartenant à table-column-poperties - props = {} - for attr in ['columnwidth']: - if attr in kwargs: - props[attr] = kwargs.pop(attr) - - if props: - style = self._doc.add_automatic_style(family='table-column') - style.addElement(odf.style.TableColumnProperties(**props)) - kwargs['stylename'] = style.getAttribute('name') - col = odf.table.TableColumn(**kwargs) - self.addElement(col) - return col - - -class TableRow(Wrapper): - _wrapper_constructor = staticmethod(odf.table.TableRow) - - def add_cell(self, value=None, **kwargs): - if value: - if isinstance(value, (basestring, unicode)): - kwargs['stringvalue'] = unicode(value) - elif isinstance(value, (int, float, Decimal)): - kwargs['valuetype'] = "float" - kwargs['value'] = float(value) - elif type(value) == type(None) or isinstance(value, Separator): - kwargs['stringvalue'] = u"" - else: - kwargs['stringvalue'] = unicode(value) - - style = self._doc.add_automatic_style(family='table-cell') - if 'verticalalign' in kwargs: - style.addElement(odf.style.TableCellProperties( - verticalalign=kwargs['verticalalign'], wrapoption='wrap')) - del kwargs['verticalalign'] - - if isinstance(value, Separator) or type(value) == type(Separator()): - style.addElement(odf.style.TableCellProperties( - backgroundcolor='#D3D3D3')) - - kwargs['stylename'] = style.getAttribute('name') -# props = {} -# if 'fontweight' in kwargs: -# props['fontweight'] = kwargs.pop('fontweight') -# if 'stringvalue' in kwargs: -# props['stringvalue'] = kwargs.pop('stringvalue') - - cell = odf.table.TableCell(**kwargs) -# if 'fontweight' in props: -# tablecontents = Style(name="Bold", family="paragraph") -# tablecontents.addElement(TextProperties(fontweight="bold")) -# self._doc.styles.addElement(tablecontents) - -# if 'stringvalue' in props: -# p = P(stylename='Bold',text=props['stringvalue']) -# cell.addElement(p) - - self.addElement(cell) - return cell + for ligne in lignes: + row = table.add_row() + row.add_cells([ + ligne['poste'].implantation.region.code, + ligne['poste'].implantation.adresse_physique_pays.nom, + ligne['poste'].implantation.nom_court + ]) + row.add_cell(ligne['valeur_point'], decimalplaces=2) + row.add_cells([ + ligne['dossier'].employe.id or 'VACANT', + ligne['dossier'].employe.nom, + ligne['dossier'].employe.prenom, + ligne['poste'].type_poste.nom, + ligne['poste'].nom, + unicode(ligne['dossier'].classement), + ligne['dossier'].classement + and ligne['dossier'].classement.coefficient, + ligne['regime_travail'], + ligne['local_expatrie'], + ligne['dossier'].statut.code, + ]) + row.add_cell(ligne['dossier'].date_fin, datastylename='iso-date') + row.add_cell(backgroundcolor='#d3d3d3') + row.add_cells([ + ligne['date_debut'], ligne['date_fin'] + ], datastylename='iso-date') + row.add_cell(ligne['jours']) + row.add_cell(backgroundcolor='#d3d3d3') + row.add_cell(ligne['devise']) + row.add_cells([ + ligne['salaire_bstg'], ligne['salaire_bstg_eur'] + ], decimalplaces=2) + row.add_cell( + ligne['dossier'].organisme_bstg + and ligne['dossier'].organisme_bstg.nom, + ) + row.add_cell(backgroundcolor='#d3d3d3') + row.add_cells( + [ligne['salaire_theorique']] + ligne['traitements'] + + [ligne['total_traitements']], + decimalplaces=2 + ) + row.add_cell(backgroundcolor='#d3d3d3') + row.add_cells( + ligne['indemnites'] + [ligne['total_indemnites']], + decimalplaces=2 + ) + row.add_cell(backgroundcolor='#d3d3d3') + row.add_cells( + ligne['primes'] + [ligne['total_primes']], + decimalplaces=2 + ) + row.add_cell(backgroundcolor='#d3d3d3') + row.add_cells( + ligne['charges'] + [ligne['total_charges']], + decimalplaces=2 + ) + row.add_cell(backgroundcolor='#d3d3d3') + row.add_cells([ + ligne['total_traitements'], ligne['total_indemnites'], + ligne['total_primes'], ligne['total_charges'] + ], decimalplaces=2) + row.add_cell(backgroundcolor='#d3d3d3') + row.add_cells([ + ligne['masse_salariale'], ligne['masse_salariale_eur'] + ], decimalplaces=2) + return doc diff --git a/project/rh/static/rh/FixedHeader.min.js b/project/rh/static/rh/FixedHeader.min.js new file mode 100644 index 0000000..7bd887f --- /dev/null +++ b/project/rh/static/rh/FixedHeader.min.js @@ -0,0 +1,40 @@ +/* + * File: FixedHeader.min.js + * Version: 2.0.6 + * Author: Allan Jardine (www.sprymedia.co.uk) + * + * Copyright 2009-2011 Allan Jardine, all rights reserved. + * + * This source file is free software, under either the GPL v2 license or a + * BSD (3 point) style license, as supplied with this software. + * + * This source file is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the license files for details. + */ +var FixedHeader=function(a,c){if("function"!=typeof this.fnInit)alert("FixedHeader warning: FixedHeader must be initialised with the 'new' keyword.");else{var b={aoCache:[],oSides:{top:!0,bottom:!1,left:!1,right:!1},oZIndexes:{top:104,bottom:103,left:102,right:101},oMes:{iTableWidth:0,iTableHeight:0,iTableLeft:0,iTableRight:0,iTableTop:0,iTableBottom:0},oOffset:{top:0},nTable:null,bUseAbsPos:!1,bFooter:!1};this.fnGetSettings=function(){return b};this.fnUpdate=function(){this._fnUpdateClones();this._fnUpdatePositions()}; +this.fnPosition=function(){this._fnUpdatePositions()};this.fnInit(a,c);if("function"==typeof a.fnSettings)a._oPluginFixedHeader=this}}; +FixedHeader.prototype={fnInit:function(a,c){var b=this.fnGetSettings(),d=this;this.fnInitSettings(b,c);if("function"==typeof a.fnSettings){if("functon"==typeof a.fnVersionCheck&&!0!==a.fnVersionCheck("1.6.0")){alert("FixedHeader 2 required DataTables 1.6.0 or later. Please upgrade your DataTables installation");return}var e=a.fnSettings();if(""!=e.oScroll.sX||""!=e.oScroll.sY){alert("FixedHeader 2 is not supported with DataTables' scrolling mode at this time");return}b.nTable=e.nTable;e.aoDrawCallback.push({fn:function(){FixedHeader.fnMeasure(); +d._fnUpdateClones.call(d);d._fnUpdatePositions.call(d)},sName:"FixedHeader"})}else b.nTable=a;b.bFooter=0<$(">tfoot",b.nTable).length?!0:!1;b.bUseAbsPos=jQuery.browser.msie&&("6.0"==jQuery.browser.version||"7.0"==jQuery.browser.version);b.oSides.top&&b.aoCache.push(d._fnCloneTable("fixedHeader","FixedHeader_Header",d._fnCloneThead));b.oSides.bottom&&b.aoCache.push(d._fnCloneTable("fixedFooter","FixedHeader_Footer",d._fnCloneTfoot));b.oSides.left&&b.aoCache.push(d._fnCloneTable("fixedLeft","FixedHeader_Left", +d._fnCloneTLeft));b.oSides.right&&b.aoCache.push(d._fnCloneTable("fixedRight","FixedHeader_Right",d._fnCloneTRight));FixedHeader.afnScroll.push(function(){d._fnUpdatePositions.call(d)});jQuery(window).resize(function(){FixedHeader.fnMeasure();d._fnUpdateClones.call(d);d._fnUpdatePositions.call(d)});FixedHeader.fnMeasure();d._fnUpdateClones();d._fnUpdatePositions()},fnInitSettings:function(a,c){if("undefined"!=typeof c){if("undefined"!=typeof c.top)a.oSides.top=c.top;if("undefined"!=typeof c.bottom)a.oSides.bottom= +c.bottom;if("undefined"!=typeof c.left)a.oSides.left=c.left;if("undefined"!=typeof c.right)a.oSides.right=c.right;if("undefined"!=typeof c.zTop)a.oZIndexes.top=c.zTop;if("undefined"!=typeof c.zBottom)a.oZIndexes.bottom=c.zBottom;if("undefined"!=typeof c.zLeft)a.oZIndexes.left=c.zLeft;if("undefined"!=typeof c.zRight)a.oZIndexes.right=c.zRight;if("undefined"!=typeof c.offsetTop)a.oOffset.top=c.offsetTop}a.bUseAbsPos=jQuery.browser.msie&&("6.0"==jQuery.browser.version||"7.0"==jQuery.browser.version)}, +_fnCloneTable:function(a,c,b){var d=this.fnGetSettings(),e;if("absolute"!=jQuery(d.nTable.parentNode).css("position"))d.nTable.parentNode.style.position="relative";e=d.nTable.cloneNode(!1);e.removeAttribute("id");var f=document.createElement("div");f.style.position="absolute";f.style.top="0px";f.style.left="0px";f.className+=" FixedHeader_Cloned "+a+" "+c;if("fixedHeader"==a)f.style.zIndex=d.oZIndexes.top;if("fixedFooter"==a)f.style.zIndex=d.oZIndexes.bottom;if("fixedLeft"==a)f.style.zIndex=d.oZIndexes.left; +else if("fixedRight"==a)f.style.zIndex=d.oZIndexes.right;e.style.margin="0";f.appendChild(e);document.body.appendChild(f);return{nNode:e,nWrapper:f,sType:a,sPosition:"",sTop:"",sLeft:"",fnClone:b}},_fnMeasure:function(){var a=this.fnGetSettings(),c=a.oMes,b=jQuery(a.nTable),d=b.offset(),e=this._fnSumScroll(a.nTable.parentNode,"scrollTop");this._fnSumScroll(a.nTable.parentNode,"scrollLeft");c.iTableWidth=b.outerWidth();c.iTableHeight=b.outerHeight();c.iTableLeft=d.left+a.nTable.parentNode.scrollLeft; +c.iTableTop=d.top+e;c.iTableRight=c.iTableLeft+c.iTableWidth;c.iTableRight=FixedHeader.oDoc.iWidth-c.iTableLeft-c.iTableWidth;c.iTableBottom=FixedHeader.oDoc.iHeight-c.iTableTop-c.iTableHeight},_fnSumScroll:function(a,c){for(var b=a[c];(a=a.parentNode)&&!("HTML"==a.nodeName||"BODY"==a.nodeName);)b=a[c];return b},_fnUpdatePositions:function(){var a=this.fnGetSettings();this._fnMeasure();for(var c=0,b=a.aoCache.length;cd.iScrollTop+c.oOffset.top?(this._fnUpdateCache(a,"sPosition","absolute","position",e.style),this._fnUpdateCache(a,"sTop",b.iTableTop+"px","top",e.style),this._fnUpdateCache(a,"sLeft",b.iTableLeft+"px","left",e.style)):d.iScrollTop+c.oOffset.top>b.iTableTop+f?(this._fnUpdateCache(a,"sPosition","absolute","position",e.style),this._fnUpdateCache(a,"sTop",b.iTableTop+f+"px","top",e.style),this._fnUpdateCache(a, +"sLeft",b.iTableLeft+"px","left",e.style)):c.bUseAbsPos?(this._fnUpdateCache(a,"sPosition","absolute","position",e.style),this._fnUpdateCache(a,"sTop",d.iScrollTop+"px","top",e.style),this._fnUpdateCache(a,"sLeft",b.iTableLeft+"px","left",e.style)):(this._fnUpdateCache(a,"sPosition","fixed","position",e.style),this._fnUpdateCache(a,"sTop",c.oOffset.top+"px","top",e.style),this._fnUpdateCache(a,"sLeft",b.iTableLeft-d.iScrollLeft+"px","left",e.style))},_fnUpdateCache:function(a,c,b,d,e){a[c]!=b&&(e[d]= +b,a[c]=b)},_fnCloneThead:function(a){var c=this.fnGetSettings(),b=a.nNode;for(a.nWrapper.style.width=jQuery(c.nTable).outerWidth()+"px";0tr th",c.nTable).each(function(a){jQuery("thead>tr th:eq("+a+")",b).width(jQuery(this).width())});jQuery("thead>tr td",c.nTable).each(function(a){jQuery("thead>tr td:eq("+a+")",b).width(jQuery(this).width())})}, +_fnCloneTfoot:function(a){var c=this.fnGetSettings(),b=a.nNode;for(a.nWrapper.style.width=jQuery(c.nTable).outerWidth()+"px";0tr th",c.nTable).each(function(a){jQuery("tfoot:eq(0)>tr th:eq("+a+")",b).width(jQuery(this).width())});jQuery("tfoot:eq(0)>tr td",c.nTable).each(function(a){jQuery("tfoot:eq(0)>tr th:eq("+a+")",b)[0].style.width(jQuery(this).width())})},_fnCloneTLeft:function(a){var c= +this.fnGetSettings(),b=a.nNode,d=$("tbody",c.nTable)[0];for($("tbody tr:eq(0) td",c.nTable);0Rapport année {{ request.GET.annee }}{% endblock %} +{% block content_title %}

Rapport de masse salariale

{% endblock %} {% block extrastyle %} {{ block.super }} {% endblock %} {% block extrahead %} {{ block.super }} - - + + {% endblock %} -{% block contentrapport %} -{% if url_ods %} - -{% endif %} -
- -
-
-

Région

- {{ form.region }} -
-
-

Implantation

- {{ form.implantation }} -
-
-

Année

- {{ form.annee }} -
-
-

 

- -
-
- {% comment %} - - {% endcomment %} -
+{% block content %} + + + {{ form }} + + + + +
+ + +
-
+{% if lignes %} +

+ Masse salariale totale: {{ masse_salariale_totale }} EUR +

- - - - - {% table_header headers %} - - -{% localize on %} -{% spaceless %} -{% for row in rapport %} - - {% for column in header_keys %} - {% if column == 'sep' %} - - {% else %} - {% if row|hash:column|is_float %} - - {% else %} - - {% endif %} - {% endif %} - {% endfor %} - -{% endfor %}{% endspaceless %} +
  - {% comment %} - {% if options.backgroundcolor %} - style="background-color:{{ options.backgroundcolor }}" - {% endif %} - {% endcomment %} - {{ row|hash:column|floatformat:2|localize }} - {% if column != "point" %} - {% if forloop.last or column|contains:"euro" %} - EUR - {% else %} - {{ row.devise }} - {% endif %} - {% endif %} - {{ row|hash:column|default:"" }}
+ - - + + + + + + + + + + + + + + + + + + + + + + + + {% for titre in titres_traitements %} + + {% endfor %} + + {% for titre in titres_indemnites %} + + {% endfor %} + + {% for titre in titres_primes %} + + {% endfor %} + + {% for titre in titres_charges %} + + {% endfor %} + + + + + + + + + + + {% for ligne in lignes %} + + + + + + + + + + + + + + + + + + + + + + + + + {% for traitement in ligne.traitements %} + + {% endfor %} + + {% for indemnite in ligne.indemnites %} + + {% endfor %} + + {% for prime in ligne.primes %} + + {% endfor %} + + {% for charge in ligne.charges %} + + {% endfor %} + + + + + + + + {% endfor %} +
- TOTAL : - {{ total_euro }}BureauPaysImplantationValeur du pointNuméro d'employéNomPrénomType de posteIntitulé du posteNiveau actuelPointsRégime de travail annuelLocal / ExpatriéStatutDate de fin de contratDate de débutDate de finNombre de joursDeviseSalaire BSTG ANNUELSalaire BSTG EUROrganisme BSTG + Salaire théorique annuel + {{ titre }} + Total des traitements + {{ titre }}Total des indemnités{{ titre }}Total des primes{{ titre }}Total des chargesTotal des traitementsTotal des indemnitésTotal des primesTotal des chargesMasse salarialeMasse salariale EUR
{{ ligne.poste.implantation.region.code }}{{ ligne.poste.implantation.adresse_physique_pays.nom }}{{ ligne.poste.implantation.nom_court }} + {% if ligne.valeur_point %} + {{ ligne.valeur_point }} {{ ligne.valeur_point_devise }} + {% endif %} + + {% if ligne.dossier %} + {{ ligne.dossier.employe.id|stringformat:"d" }} + {% else %} + VACANT + {% endif %} + {{ ligne.dossier.employe.nom }}{{ ligne.dossier.employe.prenom }}{{ ligne.poste.type_poste.nom }}{{ ligne.poste.nom }}{{ ligne.dossier.classement }} + {{ ligne.dossier.classement.coefficient|floatformat:2 }} + + {{ ligne.regime_travail|floatformat }} % + {{ ligne.local_expatrie }}{{ ligne.dossier.statut.code }}{{ ligne.dossier.date_fin|date }} + {{ ligne.date_debut|date }} + + {{ ligne.date_fin|date }} + + {{ ligne.jours }} + {{ ligne.devise }} + {% if ligne.salaire_bstg %} + {{ ligne.salaire_bstg }} {{ ligne.devise }} + {% endif %} + + {% if ligne.salaire_bstg_eur %} + {{ ligne.salaire_bstg_eur }} EUR + {% endif %} + {{ ligne.dossier.organisme_bstg.nom }} + {% if ligne.salaire_theorique %} + {{ ligne.salaire_theorique }} {{ ligne.valeur_point_devise }} + {% endif %} + + {% if traitement %} + {{ traitement }} {{ ligne.devise }} + {% endif %} + + {% if ligne.total_traitements %} + {{ ligne.total_traitements }} {{ ligne.devise }} + {% endif %} + + {% if indemnite %} + {{ indemnite }} {{ ligne.devise }} + {% endif %} + + {% if ligne.total_indemnites %} + {{ ligne.total_indemnites }} {{ ligne.devise }} + {% endif %} + + {% if prime %} + {{ prime }} {{ ligne.devise }} + {% endif %} + + {% if ligne.total_primes %} + {{ ligne.total_primes }} {{ ligne.devise }} + {% endif %} + + {% if charge %} + {{ charge }} {{ ligne.devise }} + {% endif %} + + {% if ligne.total_charges %} + {{ ligne.total_charges }} {{ ligne.devise }} + {% endif %} + + {% if ligne.total_traitements %} + {{ ligne.total_traitements }} {{ ligne.devise }} + {% endif %} + + {% if ligne.total_indemnites %} + {{ ligne.total_indemnites }} {{ ligne.devise }} + {% endif %} + + {% if ligne.total_primes %} + {{ ligne.total_primes }} {{ ligne.devise }} + {% endif %} + + {% if ligne.total_charges %} + {{ ligne.total_charges }} {{ ligne.devise }} + {% endif %} + + {{ ligne.masse_salariale }} {{ ligne.devise }} + + {{ ligne.masse_salariale_eur }} EUR +
-{% endlocalize %} +{% endif %} + {% endblock %} diff --git a/project/rh/views.py b/project/rh/views.py index 446a749..38b172e 100644 --- a/project/rh/views.py +++ b/project/rh/views.py @@ -1,34 +1,32 @@ # -*- encoding: utf-8 -*- -import urllib +from collections import defaultdict from datetime import date -import StringIO +from decimal import Decimal import pygraphviz as pgv - -from django import forms +from auf.django.references import models as ref from django.conf import settings from django.contrib.auth.decorators import login_required -from django.core.servers.basehttp import FileWrapper from django.core.urlresolvers import reverse from django.db.models import Q from django.http import HttpResponse from django.shortcuts import render, get_object_or_404 -from auf.django.references import models as ref - from project.decorators import drh_or_admin_required from project.decorators import region_protected -from project.groups import get_employe_from_user -from project.groups import grp_drh, grp_correspondants_rh - -from project.rh import models as rh +from project.groups import \ + get_employe_from_user, grp_drh, grp_correspondants_rh +from project.rh import ods from project.rh import graph as rh_graph +from project.rh import models as rh from project.rh.change_list import RechercheTemporelle +from project.rh.forms import MasseSalarialeForm from project.rh.lib import get_lookup_params -from project.rh.masse_salariale import MasseSalariale from project.rh.templatetags.rapports import SortHeaders +TWOPLACES = Decimal('0.01') + @login_required def profil(request): @@ -104,7 +102,6 @@ def rapports_contrat(request): # affichage employes = set([c.dossier.employe_id for c in contrats]) - headers = [ ("dossier__employe__id", u"#"), ("dossier__employe__nom", u"Employé"), @@ -134,110 +131,305 @@ def rapports_contrat(request): @login_required @drh_or_admin_required def rapports_masse_salariale(request): + form = MasseSalarialeForm(request.user, request.GET) + if 'annee' in request.GET and form.is_valid(): + region = form.cleaned_data['region'] + implantation = form.cleaned_data['implantation'] + annee = form.cleaned_data['annee'] + debut_annee = date(annee, 1, 1) + fin_annee = date(annee, 12, 31) + jours_annee = (fin_annee - debut_annee).days + 1 + today = date.today() + + # Récupérer les dossiers actifs + dossiers = rh.Dossier.objects \ + .actifs(annee=annee) \ + .select_related( + 'poste', 'poste__implantation', + 'poste__implantation__region', + 'poste__implantation__adresse_physique_pays', + 'employe', 'poste__type_poste', 'classement', + 'statut', 'organisme_bstg' + ) \ + .extra( + tables=['rh_valeurpoint', 'rh_devise'], + where=[ + 'rh_valeurpoint.annee = %s', + 'rh_valeurpoint.implantation = ref_implantation.id', + 'rh_devise.id = rh_valeurpoint.devise' + ], + params=[annee], + select={ + 'valeur_point': 'rh_valeurpoint.valeur', + 'valeur_point_devise': 'rh_devise.code' + } + ) + if region: + dossiers = dossiers.filter(poste__implantation__region=region) + if implantation: + dossiers = dossiers.filter(poste__implantation=implantation) + + # Récupérer les rémunérations actives + remuns = rh.Remuneration.objects \ + .actifs(annee=annee) \ + .select_related('devise', 'type') \ + .extra( + tables=['rh_tauxchange'], + where=[ + 'rh_tauxchange.annee = %s', + 'rh_tauxchange.devise = rh_devise.id' + ], + params=[annee], + select={ + 'taux_change': 'rh_tauxchange.taux' + } + ) + remuns_par_dossier = defaultdict(list) + for remun in remuns: + remuns_par_dossier[remun.dossier_id].append(remun) + + # Récupérer les types de rémunération par nature + types_remun_par_nature = defaultdict(list) + for type in rh.TypeRemuneration.objects.all(): + types_remun_par_nature[type.nature_remuneration].append(type) + titres_traitements = [ + t.nom for t in types_remun_par_nature[u'Traitement'] + ] + titres_indemnites = [ + t.nom for t in types_remun_par_nature[u'Indemnité'] + ] + titres_primes = [ + t.nom for t in types_remun_par_nature[u'Accessoire'] + ] + titres_charges = [ + t.nom for t in types_remun_par_nature[u'Charges'] + ] - class RechercheTemporelle(forms.Form): - CHOICE_ANNEES = range( - rh.Remuneration.objects.exclude(date_debut=None) - .order_by('date_debut')[0].date_debut.year, - date.today().year + 1 - ) + # Boucler sur les dossiers et préprarer les lignes du rapport + lignes = [] + masse_salariale_totale = 0 + for dossier in dossiers: + debut_cette_annee = \ + max(dossier.date_debut or debut_annee, debut_annee) + fin_cette_annee = \ + min(dossier.date_fin or fin_annee, fin_annee) + jours = (fin_cette_annee - debut_cette_annee).days + 1 + + remuns = remuns_par_dossier[dossier.id] + devises = set(remun.devise.code for remun in remuns) + if len(devises) == 1: + devise = remuns[0].devise.code + montant_remun = lambda r: r.montant + taux_change = Decimal(str(remuns[0].taux_change)) + else: + devise = 'EUR' + montant_remun = lambda r: ( + r.montant * Decimal(str(r.taux_change)) + ) + taux_change = Decimal(1) + + remuns_par_type = defaultdict(lambda: 0) + for remun in remuns: + remuns_par_type[remun.type_id] += ( + montant_remun(remun) * (( + min(remun.date_fin or fin_annee, fin_annee) - + max(remun.date_debut or debut_annee, debut_annee) + ).days + 1) / jours_annee + ).quantize(TWOPLACES) + traitements = [ + remuns_par_type[type.id] + for type in types_remun_par_nature[u'Traitement'] + ] + indemnites = [ + remuns_par_type[type.id] + for type in types_remun_par_nature[u'Indemnité'] + ] + primes = [ + remuns_par_type[type.id] + for type in types_remun_par_nature[u'Accessoire'] + ] + charges = [ + remuns_par_type[type.id] + for type in types_remun_par_nature[u'Charges'] + ] + masse_salariale = sum(remuns_par_type.values()) + masse_salariale_eur = ( + masse_salariale * taux_change + ).quantize(TWOPLACES) + masse_salariale_totale += masse_salariale_eur + + if dossier.valeur_point and dossier.classement \ + and dossier.classement.coefficient and dossier.regime_travail: + salaire_theorique = (Decimal(str( + dossier.valeur_point * dossier.classement.coefficient + )) * dossier.regime_travail / 100).quantize(TWOPLACES) + else: + salaire_theorique = None + + lignes.append({ + 'dossier': dossier, + 'poste': dossier.poste, + 'date_debut': ( + dossier.date_debut + if dossier.date_debut and dossier.date_debut.year == annee + else None + ), + 'date_fin': ( + dossier.date_fin + if dossier.date_fin and dossier.date_fin.year == annee + else None + ), + 'jours': jours, + 'devise': devise, + 'valeur_point': dossier.valeur_point, + 'valeur_point_devise': dossier.valeur_point_devise, + 'regime_travail': dossier.regime_travail, + 'local_expatrie': { + 'local': 'L', 'expat': 'E' + }.get(dossier.statut_residence), + 'salaire_bstg': remuns_par_type[2], + 'salaire_bstg_eur': ( + (remuns_par_type[2] * taux_change).quantize(TWOPLACES) + ), + 'salaire_theorique': salaire_theorique, + 'traitements': traitements, + 'total_traitements': sum(traitements), + 'indemnites': indemnites, + 'total_indemnites': sum(indemnites), + 'primes': primes, + 'total_primes': sum(primes), + 'charges': charges, + 'total_charges': sum(charges), + 'masse_salariale': masse_salariale, + 'masse_salariale_eur': masse_salariale_eur, + }) - annee = forms.CharField( - initial=date.today().year, - widget=forms.Select( - choices=((a, a) for a in reversed(CHOICE_ANNEES)) + # Récupérer les postes actifs pour déterminer le nombre de jours + # vacants. + postes = list( + rh.Poste.objects.actifs(annee=annee) + .select_related( + 'devise_max', 'valeur_point_max', 'classement_max' ) - ) - - region = forms.CharField( - widget=forms.Select(choices=[('', '')] + - [(i.id, i) for i in ref.Region.objects.all()] - ) + .extra( + tables=['rh_tauxchange'], + where=[ + 'rh_tauxchange.annee = %s', + 'rh_tauxchange.devise = rh_devise.id' + ], + params=[annee], + select={ + 'taux_change': 'rh_tauxchange.taux' + } ) - - implantation = forms.CharField( - widget=forms.Select(choices=[('', '')] + - [(i.id, i) for i in ref.Implantation.objects.all()] - ) + ) + postes_par_id = dict((poste.id, poste) for poste in postes) + jours_vacants_date = dict( + (poste.id, max(today, poste.date_debut or today)) + for poste in postes + ) + jours_vacants = defaultdict(lambda: 0) + for dossier in rh.Dossier.objects.actifs(annee=annee) \ + .order_by('date_debut'): + if dossier.poste_id not in jours_vacants_date: + continue + derniere_date = jours_vacants_date[dossier.poste_id] + if dossier.date_debut is not None: + jours_vacants[dossier.poste_id] += max(( + dossier.date_debut - derniere_date + ).days - 1, 0) + jours_vacants_date[dossier.poste_id] = max( + min(dossier.date_fin or fin_annee, fin_annee), + derniere_date ) - #date_debut = forms.DateField(widget=adminwidgets.AdminDateWidget) - #date_fin = forms.DateField(widget=adminwidgets.AdminDateWidget) - - form = RechercheTemporelle(request.GET) - get_filtre = [ - (k, v) for k, v in request.GET.items() - if k not in ('date_debut', 'date_fin', 'implantation') - ] - query_string = urllib.urlencode(get_filtre) - - date_debut = None - date_fin = None - if request.GET.get('annee', None): - date_debut = "01-01-%s" % request.GET.get('annee', None) - date_fin = "31-12-%s" % request.GET.get('annee', None) - - implantation = request.GET.get('implantation') - region = request.GET.get('region') + # Ajouter les lignes des postes vacants au rapport + for poste_id, jours in jours_vacants.iteritems(): + if jours == 0: + continue + poste = postes_par_id[poste_id] + if poste.valeur_point_max and poste.classement_max \ + and poste.classement_max.coefficient and poste.regime_travail: + salaire_theorique = (Decimal(str( + poste.valeur_point_max.valeur * + poste.classement_max.coefficient + )) * poste.regime_travail / 100).quantize(TWOPLACES) + else: + salaire_theorique = None - custom_filter = {} - if implantation: - custom_filter['dossier__poste__implantation'] = implantation - if region: - custom_filter['dossier__poste__implantation__region'] = region + local_expatrie = '/'.join( + (['L'] if poste.local else []) + + (['E'] if poste.expatrie else []) + ) - c = { - 'title': 'Rapport de masse salariale', - 'form': form, - 'headers': [], - 'query_string': query_string, - } - if date_debut or date_fin: - masse = MasseSalariale(date_debut, date_fin, custom_filter, - request.GET.get('ne_pas_grouper', False)) - if masse.rapport: - if request.GET.get('ods'): - for h in ( - h for h in masse.headers if 'background-color' in h[2] - ): - del h[2]['background-color'] - masse.ods() - output = StringIO.StringIO() - masse.doc.save(output) - output.seek(0) - - response = HttpResponse( - FileWrapper(output), - content_type=( - 'application/vnd.oasis.opendocument.spreadsheet' - ) - ) - response['Content-Disposition'] = \ - 'attachment; filename=Masse Salariale %s.ods' % \ - masse.annee - return response - else: - c['rapport'] = masse.rapport - c['header_keys'] = [h[0] for h in masse.headers] - #on enleve le background pour le header - for h in ( - h for h in masse.headers if 'background-color' in h[2] - ): - h[2]['background'] = 'none' - h = SortHeaders(request, masse.headers, order_field_type="ot", - not_sortable=c['header_keys'], order_field="o") - c['headers'] = list(h.headers()) - for key, nom, opts in masse.headers: - c['headers'] - c['total'] = masse.grand_totaux[0] - c['total_euro'] = masse.grand_totaux[1] - c['colspan'] = len(c['header_keys']) - 1 - get_filtre.append(('ods', True)) - query_string = urllib.urlencode(get_filtre) - c['url_ods'] = "%s?%s" % ( - reverse('rhr_masse_salariale'), query_string) - - return render(request, 'rh/rapports/masse_salariale.html', c) + salaire = ( + poste.salaire_max * jours / jours_annee * + poste.regime_travail / 100 + ).quantize(TWOPLACES) + indemnites = ( + poste.indemn_max * jours / jours_annee * + poste.regime_travail / 100 + ).quantize(TWOPLACES) + charges = ( + poste.autre_max * jours / jours_annee * + poste.regime_travail / 100 + ).quantize(TWOPLACES) + masse_salariale = salaire + indemnites + charges + masse_salariale_eur = ( + masse_salariale * Decimal(str(poste.taux_change)) + ).quantize(TWOPLACES) + masse_salariale_totale += masse_salariale_eur + + lignes.append({ + 'poste': poste, + 'regime_travail': poste.regime_travail, + 'local_expatrie': local_expatrie, + 'jours': jours, + 'devise': poste.devise_max and poste.devise_max.code, + 'valeur_point': ( + poste.valeur_point_max and poste.valeur_point_max.valeur + ), + 'valeur_point_devise': ( + poste.valeur_point_max and \ + poste.valeur_point_max.devise.code + ), + 'salaire_theorique': salaire_theorique, + 'salaire_de_base': salaire, + 'total_indemnites': indemnites, + 'total_charges': charges, + 'masse_salariale': masse_salariale, + 'masse_salariale_eur': masse_salariale_eur + }) + if 'ods' in request.GET: + doc = ods.masse_salariale( + lignes=lignes, + annee=annee, + titres_traitements=titres_traitements, + titres_indemnites=titres_indemnites, + titres_primes=titres_primes, + titres_charges=titres_charges, + masse_salariale_totale=masse_salariale_totale + ) + response = HttpResponse( + mimetype='vnd.oasis.opendocument.spreadsheet' + ) + response['Content-Disposition'] = 'filename=masse-salariale.ods' + doc.write(response) + return response + else: + return render(request, 'rh/rapports/masse_salariale.html', { + 'form': form, + 'titres_traitements': titres_traitements, + 'titres_indemnites': titres_indemnites, + 'titres_primes': titres_primes, + 'titres_charges': titres_charges, + 'masse_salariale_totale': masse_salariale_totale, + 'lignes': lignes, + 'annee': annee + }) + return render(request, 'rh/rapports/masse_salariale.html', { + 'form': form + }) @login_required diff --git a/project/settings.py b/project/settings.py index 5d2527f..6be7ea5 100644 --- a/project/settings.py +++ b/project/settings.py @@ -27,6 +27,8 @@ SESSION_EXPIRE_AT_BROWSER_CLOSE = True SHORT_DATE_FORMAT = 'd-m-Y' LANGUAGE_CODE = 'fr-ca' +USE_L10N = True +USE_THOUSAND_SEPARATOR = True # Absolute path to the directory that holds media. # Example: "/home/media/media.lawrence.com/" @@ -79,11 +81,12 @@ INSTALLED_APPS = ( 'admin_tools.theming', 'admin_tools.menu', 'admin_tools.dashboard', + 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', + 'django.contrib.humanize', 'django.contrib.messages', 'django.contrib.sessions', - 'django.contrib.admin', 'django.contrib.staticfiles', 'django_qbe', 'ajax_select', diff --git a/versions.cfg b/versions.cfg index b4a5bfb..cd52e59 100644 --- a/versions.cfg +++ b/versions.cfg @@ -62,3 +62,6 @@ django-picklefield = 0.2.1 # Added by Buildout Versions at 2012-06-08 11:15:52.545562 auf.django.emploi = 1.2dev + +# Added by Buildout Versions at 2012-06-15 14:24:42.911205 +odsgen = 0.1 -- 1.7.10.4