@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