Merge branch 'masse-salariale' into dev
authorEric Mc Sween <eric.mcsween@auf.org>
Fri, 15 Jun 2012 19:34:23 +0000 (15:34 -0400)
committerEric Mc Sween <eric.mcsween@auf.org>
Fri, 15 Jun 2012 19:34:23 +0000 (15:34 -0400)
Conflicts:
project/rh/views.py

1  2 
project/rh/models.py
project/rh/views.py
project/settings.py

Simple merge
@@@ -135,177 -130,309 +130,375 @@@ def rapports_contrat(request)
  
  @login_required
  @drh_or_admin_required
 +def rapports_employes_sans_contrat(request):
 +    """
 +    Employé sans contrat = a un Dossier qui n'a pas de Contrat associé
 +    Employé avec contrat échu = a un Dossier avec un Contrat se terminant hier
 +    au plus tard... (aujourd'hui = ok, pas date de fin = illimité donc ok)
 +    """
 +    lookup_params = get_lookup_params(request)
 +
 +    # contrats échus
 +    contrats_echus = rh.Contrat.objects.filter(
 +            date_fin__lt=date.today()
 +        ).filter(
 +            **lookup_params
 +        )
 +
 +    # dossiers en cours sans contrat ou contrats échus
 +    dossiers = rh.Dossier.objects.filter(
 +            Q(date_debut=None) | Q(date_debut__lte=date.today()),
 +        ).filter(
 +            Q(date_fin=None) | Q(date_fin__gte=date.today()),
 +        ).exclude(
 +            date_debut__gt=date.today()
 +        ).filter(
 +            # sans contrat | contrat échu
 +            Q(rh_contrats=None) | Q(rh_contrats__in=contrats_echus)
-         ).distinct()   
-     
++        ).distinct()
++
 +    # employés sans contrat ou contrats échus
 +    employes = rh.Employe.objects.filter(rh_dossiers__in=dossiers)  \
 +        .distinct().count()
-         
++
 +    # tri
 +    if 'o' in request.GET:
 +        dossiers = dossiers.order_by(
 +            ('-' if request.GET.get('ot') == "desc" else '') + request.GET['o']
 +        )
 +
 +    # affichage
 +    headers = [
 +        ("employe__id", u"#"),
 +        ("employe__nom", u"Employé"),
 +        ("dossier", u"Dossier : Poste"),
 +        ("rh_contrats__type_contrat", u"Contrat"),
 +        ("rh_contrats__date_debut", u"Début contrat"),
 +        ("rh_contrats__date_fin", u"Fin contrat"),
 +        ("statut_residence", u"Statut"),
 +        ("poste__implantation__region__code", u"Région"),
 +        ("poste__implantation__nom", u"Implantation"),
 +    ]
 +    h = SortHeaders(
 +        request, headers, order_field_type="ot", order_field="o",
 +        not_sortable=('dossier',)
 +    )
 +
 +    c = {
 +        'title': u'Rapport des employés sans contrat ou contrat échu',
 +        'employes': employes,
 +        'dossiers': dossiers,
 +        'headers': list(h.headers()),
 +    }
 +
 +    return render(request, 'rh/rapports/employes_sans_contrat.html', c)
 +
 +
 +@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:
+                 if remun.type.nature_remuneration == u'Accessoire':
+                     remuns_par_type[remun.type_id] += montant_remun(remun)
+                 else:
+                     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
@@@ -25,7 -25,10 +25,9 @@@ SHORT_DATE_FORMAT = 'd-m-Y
  SESSION_SAVE_EVERY_REQUEST = True
  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/"