From 0daa7433ec0dcc8a6595250521094115ca3b68a5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ses=C3=B3stris=20Vieira?= Date: Mon, 20 Nov 2023 17:09:35 -0300 Subject: [PATCH] =?UTF-8?q?C=C3=B4mputo=20de=20custos=20de=20oficinas.=20G?= =?UTF-8?q?ertiq=20#168279?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../espacos/snippets/agenda_cal.html | 2 - sigi/apps/eventos/admin.py | 317 +++++++++++++++++- ..._passagens_equipe_qtde_diarias_and_more.py | 50 +++ sigi/apps/eventos/models.py | 20 ++ .../admin/eventos/custos_eventos_report.html | 316 +++++++++++++++++ .../admin/eventos/custos_servidor_report.html | 285 ++++++++++++++++ .../admin/eventos/evento/change_form.html | 8 + .../admin/eventos/evento/custos_report.html | 163 +++++++++ sigi/apps/utils/templatetags/sigi_tags.py | 4 +- sigi/templates/pdf/base_report.html | 5 +- 10 files changed, 1163 insertions(+), 7 deletions(-) create mode 100644 sigi/apps/eventos/migrations/0054_equipe_emissao_passagens_equipe_qtde_diarias_and_more.py create mode 100644 sigi/apps/eventos/templates/admin/eventos/custos_eventos_report.html create mode 100644 sigi/apps/eventos/templates/admin/eventos/custos_servidor_report.html create mode 100644 sigi/apps/eventos/templates/admin/eventos/evento/custos_report.html diff --git a/sigi/apps/espacos/templates/espacos/snippets/agenda_cal.html b/sigi/apps/espacos/templates/espacos/snippets/agenda_cal.html index 5418f46..e56c3a8 100644 --- a/sigi/apps/espacos/templates/espacos/snippets/agenda_cal.html +++ b/sigi/apps/espacos/templates/espacos/snippets/agenda_cal.html @@ -25,7 +25,6 @@ {% for espaco, reservas in semana.reservas.items %} {{ espaco.sigla }} - {% setvar 0 as last_pos %} {% for reserva, tupla in reservas %} {% for x in ""|ljust:tupla.0|make_list %}{% endfor %} @@ -40,7 +39,6 @@ {% if forloop.last %} {% for x in ""|ljust:tupla.2|make_list %}{% endfor %} {% endif %} - {% setvar last_pos|sum:tupla.1 as last_pos %} {% empty %} {% for x in "1234567"|make_list %}{% endfor %} {% endfor %} diff --git a/sigi/apps/eventos/admin.py b/sigi/apps/eventos/admin.py index 3a94fd3..0020814 100644 --- a/sigi/apps/eventos/admin.py +++ b/sigi/apps/eventos/admin.py @@ -1,9 +1,24 @@ import datetime +import pandas as pd import time from moodle import Moodle from typing import Any from django.db import models -from django.db.models import F, OuterRef, Subquery, Count, Q +from django.db.models import ( + F, + OuterRef, + Subquery, + Count, + Q, + Sum, + Avg, + Min, + Max, + Prefetch, + Case, + When, +) +from django.db.models.functions import ExtractDay, Cast from django.conf import settings from django.contrib import admin, messages from django.core.exceptions import ValidationError @@ -38,10 +53,12 @@ from sigi.apps.eventos.models import ( Anexo, ) from sigi.apps.eventos.forms import EventoAdminForm, SelecionaModeloForm +from sigi.apps.servidores.models import Servidor from sigi.apps.utils import abreviatura from sigi.apps.utils.filters import DateRangeFilter from sigi.apps.utils.mixins import ( CartExportMixin, + CartExportReportMixin, LabeledResourse, ValueLabeledResource, ) @@ -833,7 +850,7 @@ class ModeloDeclaracaoAdmin(admin.ModelAdmin): @admin.register(Evento) -class EventoAdmin(CartExportMixin, admin.ModelAdmin): +class EventoAdmin(CartExportReportMixin, admin.ModelAdmin): form = EventoAdminForm resource_class = EventoResource fieldsets = ( @@ -951,6 +968,7 @@ class EventoAdmin(CartExportMixin, admin.ModelAdmin): CronogramaInline, ) save_as = True + reports = ["custos_eventos_report", "custos_servidor_report"] @admin.display(description=_("banner")) def get_banner(self, obj): @@ -1071,6 +1089,11 @@ class EventoAdmin(CartExportMixin, admin.ModelAdmin): self.admin_site.admin_view(self.update_participantes), name="%s_%s_updateparticipantes" % model_info, ), + path( + "/custos/", + self.admin_site.admin_view(self.custos_report), + name="%s_%s_custos" % model_info, + ), ] return my_urls + urls @@ -1283,6 +1306,296 @@ class EventoAdmin(CartExportMixin, admin.ModelAdmin): content_type="application/pdf", ) + def custos_report(self, request, object_id): + evento = get_object_or_404(Evento, id=object_id) + change_url = ( + reverse( + "admin:%s_%s_change" % self.get_model_info(), args=[object_id] + ) + + "?" + + self.get_preserved_filters(request) + ) + + if not evento.equipe_set.filter( + Q(valor_diaria__gte=0) | Q(total_passagens__gte=0) + ).exists(): + self.message_user( + request, + _("Não há valores de diárias e passagens para este evento"), + messages.ERROR, + ) + return redirect(change_url) + + membros = Equipe.objects.filter(evento=evento).annotate( + antecedencia_passagens=( + ExtractDay(F("evento__data_inicio") - F("emissao_passagens")) + ), + total_diarias=(F("qtde_diarias") * F("valor_diaria")), + total_gasto=(F("total_diarias") + F("total_passagens")), + ) + my_decimal_field = models.DecimalField(max_digits=14, decimal_places=2) + total_equipe = membros.aggregate( + num_membros=Count("membro_id", distinct=True), + tot_qtde_diarias=Sum("qtde_diarias"), + media_qtde_diarias=Cast( + F("tot_qtde_diarias") + / 1.0 + / F("num_membros"), # divide por 1.0 para forçar float + output_field=my_decimal_field, + ), + tot_valor_diarias=Sum("total_diarias"), + media_diarias=Cast( + F("tot_valor_diarias") / F("tot_qtde_diarias"), + output_field=my_decimal_field, + ), + tot_passagens=Sum("total_passagens"), + media_passagens=Cast( + F("tot_passagens") / F("num_membros"), + output_field=my_decimal_field, + ), + tot_gastos=Sum("total_gasto"), + media_antecedencia=Avg("antecedencia_passagens"), + ) + + context = { + "evento": evento, + "membros": membros, + "total_equipe": total_equipe, + } + + return WeasyTemplateResponse( + filename=f"custos{evento.nome.replace(' ','')}-{timezone.localdate()}.pdf", + request=request, + template="admin/eventos/evento/custos_report.html", + context=context, + content_type="application/pdf", + ) + + def custos_eventos_report(self, request): + my_decimal_field = models.DecimalField(max_digits=14, decimal_places=2) + equipe_qs = Equipe.objects.annotate( + total_diarias=(F("qtde_diarias") * F("valor_diaria")), + antecedencia=ExtractDay( + F("evento__data_inicio") - F("emissao_passagens") + ), + ) + eventos = ( + self.get_queryset(request) + .annotate( + duracao_dias=( + ExtractDay(F("data_termino") - F("data_inicio")) + 1 + ), + qtde_diarias=Sum("equipe__qtde_diarias"), + vlr_tot_diarias=Sum( + F("equipe__qtde_diarias") * F("equipe__valor_diaria") + ), + vlr_tot_passagens=Sum("equipe__total_passagens"), + custo_total=F("vlr_tot_diarias") + F("vlr_tot_passagens"), + custo_medio_participante=Cast( + Case( + When(total_participantes__lte=0, then=0), + default=F("custo_total") / F("total_participantes"), + output_field=my_decimal_field, + ), + output_field=my_decimal_field, + ), + custo_medio_membro=Cast( + F("custo_total") / Count("equipe__membro"), + output_field=my_decimal_field, + ), + tot_membros=Count("equipe"), + ) + .prefetch_related( + Prefetch( + "equipe_set", queryset=equipe_qs, to_attr="equipe_ext" + ) + ) + ) + resumo = eventos.aggregate( + qtde_oficinas=Count("id"), + tot_participantes=Sum("total_participantes"), + media_participantes=Cast( + 1.0 * F("tot_participantes") / F("qtde_oficinas"), + output_field=my_decimal_field, + ), + min_participantes=Min("total_participantes"), + max_participantes=Max("total_participantes"), + tot_servidores=Sum("tot_membros"), + media_membros=Cast( + 1.0 * Sum("tot_membros") / F("qtde_oficinas"), + output_field=my_decimal_field, + ), + min_membros=Min("tot_membros"), + max_membros=Max("tot_membros"), + tot_dias=Sum("duracao_dias"), + media_dias=Cast( + 1.0 * F("tot_dias") / F("qtde_oficinas"), + output_field=my_decimal_field, + ), + tot_diarias=Sum("qtde_diarias"), + media_diarias=Cast( + 1.0 * F("tot_diarias") / F("qtde_oficinas"), + output_field=my_decimal_field, + ), + tot_custo_total=Sum("custo_total"), + tot_custo_diarias=Sum("vlr_tot_diarias"), + tot_custo_passagens=Sum("vlr_tot_passagens"), + media_custo_total=Cast( + F("tot_custo_total") / F("qtde_oficinas"), + output_field=my_decimal_field, + ), + media_custo_diarias=Cast( + F("tot_custo_diarias") / F("qtde_oficinas"), + output_field=my_decimal_field, + ), + media_custo_passagens=Cast( + F("tot_custo_passagens") / F("qtde_oficinas"), + output_field=my_decimal_field, + ), + media_custo_participantes=Cast( + F("tot_custo_total") / F("tot_participantes"), + output_field=my_decimal_field, + ), + media_custo_membro=Cast( + F("tot_custo_total") / Sum("tot_membros"), + output_field=my_decimal_field, + ), + ) + resumo.update( + eventos.aggregate( + media_antecedencia=Avg( + ExtractDay( + F("data_inicio") - F("equipe__emissao_passagens") + ) + ), + min_antecedencia=Min( + ExtractDay( + F("data_inicio") - F("equipe__emissao_passagens") + ) + ), + max_antecedencia=Max( + ExtractDay( + F("data_inicio") - F("equipe__emissao_passagens") + ) + ), + ) + ) + + f_valor_diarias = F("equipe__qtde_diarias") * F("equipe__valor_diaria") + f_custo_total = (f_valor_diarias) + F("equipe__total_passagens") + + extrato = ( + self.get_queryset(request) + .order_by("casa_anfitria__municipio__uf__regiao") + .annotate( + regiao=F("casa_anfitria__municipio__uf__regiao"), + tot_diarias=Sum(f_valor_diarias), + tot_passagens=Sum("equipe__total_passagens"), + tot_custo=Sum(f_custo_total), + ) + .values("regiao", "tot_diarias", "tot_passagens", "tot_custo") + ) + + df = ( + pd.DataFrame(extrato) + .set_index("regiao") + .groupby("regiao") + .aggregate(["sum", "min", "max", "mean"]) + .fillna(0) + ) + + custos_regiao = [ + { + "nome": nome, + "extrato": df.loc[sigla] if sigla in df.index else None, + } + for sigla, nome in UnidadeFederativa.REGIAO_CHOICES + ] + + context = { + "eventos": eventos, + "resumo": resumo, + "custos_regiao": custos_regiao, + "title": _("Custos por eventos"), + } + return WeasyTemplateResponse( + filename=f"custos_eventos-{timezone.localdate()}.pdf", + request=request, + template="admin/eventos/custos_eventos_report.html", + context=context, + content_type="application/pdf", + ) + + custos_eventos_report.title = _("Custos por eventos") + + def custos_servidor_report(self, request): + equipe_qs = Equipe.objects.filter( + evento__in=self.get_queryset(request) + ) + f_total_diarias = F("equipe_evento__qtde_diarias") * F( + "equipe_evento__valor_diaria" + ) + my_decimal_field = models.DecimalField(max_digits=14, decimal_places=2) + + servidores = ( + Servidor.objects.distinct() + .filter(equipe_evento__evento__in=self.get_queryset(request)) + .prefetch_related( + Prefetch( + "equipe_evento", queryset=equipe_qs, to_attr="equipe_ext" + ) + ) + .annotate( + qtde_eventos=Count("equipe_evento"), + qtde_diarias=Sum("equipe_evento__qtde_diarias"), + media_diarias=Cast( + Sum(f_total_diarias / F("equipe_evento__qtde_diarias")), + output_field=my_decimal_field, + ), + total_diarias=Sum(f_total_diarias), + total_passagens=Sum("equipe_evento__total_passagens"), + total_custo=Sum( + F("equipe_evento__total_passagens") + f_total_diarias + ), + ) + ) + totais = ( + Servidor.objects.distinct() + .filter(equipe_evento__evento__in=self.get_queryset(request)) + .prefetch_related( + Prefetch( + "equipe_evento", queryset=equipe_qs, to_attr="equipe_ext" + ) + ) + .aggregate( + qtde_eventos=Count("equipe_evento"), + qtde_diarias=Sum("equipe_evento__qtde_diarias"), + media_diarias=Cast( + Avg(f_total_diarias / F("equipe_evento__qtde_diarias")), + output_field=my_decimal_field, + ), + total_diarias=Sum(f_total_diarias), + total_passagens=Sum("equipe_evento__total_passagens"), + total_custo=Sum( + F("equipe_evento__total_passagens") + f_total_diarias + ), + ) + ) + context = { + "servidores": servidores, + "totais": totais, + "title": _("Custos por servidor"), + } + return WeasyTemplateResponse( + filename=f"custos_servidor-{timezone.localdate()}.pdf", + request=request, + template="admin/eventos/custos_servidor_report.html", + context=context, + content_type="application/pdf", + ) + + custos_servidor_report.title = _("Custos por servidor") + def create_course(self, request, object_id): evento = get_object_or_404(Evento, id=object_id) change_url = ( diff --git a/sigi/apps/eventos/migrations/0054_equipe_emissao_passagens_equipe_qtde_diarias_and_more.py b/sigi/apps/eventos/migrations/0054_equipe_emissao_passagens_equipe_qtde_diarias_and_more.py new file mode 100644 index 0000000..3354c1e --- /dev/null +++ b/sigi/apps/eventos/migrations/0054_equipe_emissao_passagens_equipe_qtde_diarias_and_more.py @@ -0,0 +1,50 @@ +# Generated by Django 4.2.4 on 2023-11-13 13:23 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + dependencies = [ + ("eventos", "0053_visita_anfitria_senado_oficina_remove_convite"), + ] + + operations = [ + migrations.AddField( + model_name="equipe", + name="emissao_passagens", + field=models.DateField( + blank=True, + null=True, + verbose_name="data de emissão das passagens", + ), + ), + migrations.AddField( + model_name="equipe", + name="qtde_diarias", + field=models.PositiveIntegerField( + blank=True, null=True, verbose_name="quantidade de diárias" + ), + ), + migrations.AddField( + model_name="equipe", + name="total_passagens", + field=models.DecimalField( + blank=True, + decimal_places=2, + max_digits=14, + null=True, + verbose_name="valor total das passagens", + ), + ), + migrations.AddField( + model_name="equipe", + name="valor_diaria", + field=models.DecimalField( + blank=True, + decimal_places=2, + max_digits=14, + null=True, + verbose_name="valor da diária (R$)", + ), + ), + ] diff --git a/sigi/apps/eventos/models.py b/sigi/apps/eventos/models.py index 0cf40de..e3d408c 100644 --- a/sigi/apps/eventos/models.py +++ b/sigi/apps/eventos/models.py @@ -757,6 +757,26 @@ class Equipe(models.Model): assina_oficio = models.BooleanField( _("Assina ofício de comparecimento"), default=False ) + qtde_diarias = models.PositiveIntegerField( + _("quantidade de diárias"), blank=True, null=True + ) + valor_diaria = models.DecimalField( + _("valor da diária (R$)"), + max_digits=14, + decimal_places=2, + blank=True, + null=True, + ) + emissao_passagens = models.DateField( + _("data de emissão das passagens"), blank=True, null=True + ) + total_passagens = models.DecimalField( + _("valor total das passagens"), + max_digits=14, + decimal_places=2, + blank=True, + null=True, + ) observacoes = models.TextField(_("Observações"), blank=True) class Meta: diff --git a/sigi/apps/eventos/templates/admin/eventos/custos_eventos_report.html b/sigi/apps/eventos/templates/admin/eventos/custos_eventos_report.html new file mode 100644 index 0000000..f872997 --- /dev/null +++ b/sigi/apps/eventos/templates/admin/eventos/custos_eventos_report.html @@ -0,0 +1,316 @@ +{% extends 'pdf/base_report.html' %} +{% load static i18n sigi_tags %} + +{% block page_size %}A4 landscape{% endblock page_size %} + +{% block extra_style %} + {{ block.super }} + aside { + margin-left: 8px; + font-size: 0.8em; + color: #666; + } + blockquote { + margin: 12px 0 12px; + padding-left: 1.5rem; + border-left: 5px solid #ee6e73; + font-size: 1.4em; + font-weight: bold; + } + tr:nth-child(even) { + background-color: initial; + } + .even-row { + background-color: #d2d2d2 !important; + } + + .sessao-resumo { + align-items: stretch; + display: flex; + flex-wrap: wrap; + width: 100%; + margin-top: 24px; + } + .card-resumo { + background-color: #eeeeef; + border-radius: 2px; + box-sizing: border-box; + margin: 6px; + flex-basis: 49%; + padding: 0 6px 6px 6px; + position: relative; + width: 100%; + } + .card-resumo.full { + flex-basis: 98%; + } + + .index-cell { + width: 2em; + text-align: center; + } + .label-resumo { + min-width: 30em; + } +{% endblock %} + +{% block main_content %} + + + + + + + + + + + + + + + + + + + + + + + + + + {% for evento in eventos %} + {% with equipe_count=evento.equipe_ext|length|default:1 %} + + + + + + + + {% for membro in evento.equipe_ext %} + {% if not forloop.first %}{% endif %} + + + + + + + + {% if forloop.first %} + + + + {% endif %} + + {% empty %} + + + + + + {% endfor %} + {% endwith %} + {% endfor %} + +
{% trans "Início / término" %}{% trans "SIGAD" %}{% trans "Evento" %}{% trans "Casa anfitriã" %}{% trans "Duração do evento (dias)" %}{% trans "Total de participantes" %}{% trans "Equipe" %}{% trans "Custos" %}
{% trans "Nome" %}{% trans "Função" %}{% trans "Qtde de diárias" %}{% trans "Valor total diárias" %}{% trans "Valor total passagens" %}{% trans "Emissão das passagens" %}{% trans "Antecedência das passagens (dias)" %}{% trans "Custo total" %}{% trans "Custo médio participante" %}{% trans "Custo médio equipe" %}
+ {% blocktranslate with inicio=evento.data_inicio|date:"SHORT_DATE_FORMAT" termino=evento.data_termino|date:"SHORT_DATE_FORMAT" %} + {{ inicio }} a {{ termino }} + {% endblocktranslate %} + {{ evento.num_processo }} + {% blocktranslate with nome=evento.nome turma=evento.turma %} + {{ nome }} - turma {{ turma }} + {% endblocktranslate %} + {{ evento.casa_anfitria }}{{ evento.duracao_dias|default:"-" }}{{ evento.total_participantes|default:"-" }}
{{ membro.membro.get_apelido }}{{ membro.funcao }}{{ membro.qtde_diarias|default:"-" }}{{ membro.total_diarias|default:"-" }}{{ membro.total_passagens|default:"-" }}{{ membro.emissao_passagens|default:"-" }}{{ membro.antecedencia|default:"-" }}{{ evento.custo_total|default:"-" }}{{ evento.custo_medio_participante|default:"-" }}{{ evento.custo_medio_membro|default:"-" }}
{% trans "Equipe não definida" %}{{ evento.custo_total|default:"-" }}{{ evento.custo_medio_participante|default:"-" }}{{ evento.custo_medio_membro|default:"-" }}
+ + {# Resumo do relatório #} + +
+
+
{% trans "Dados gerais" %}
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
{% cycle "A" "B" "C" "D" "E" "F" "G" "H" "I" "J" "K" "L" "M" "N" "O" "P" "Q" "R" "S" "T" "U" "V" "W" "X" "Y" "Z" as letra %}{% trans "Quantidade de eventos" %}{{ resumo.qtde_oficinas|default:"-" }}
{% cycle letra %}{% trans "Total de participantes" %}{{ resumo.tot_participantes|default:"-" }}
{% cycle letra %}{% trans "Média de participantes por evento" %} [B / A]{{ resumo.media_participantes|default:"-" }}
{% cycle letra %}{% trans "Mínimo de participantes" %}{{ resumo.min_participantes|default:"-" }}
{% cycle letra %}{% trans "Máximo de participantes" %}{{ resumo.max_participantes|default:"-" }}
+
+
+
{% trans "Equipes" %}
+ + + + + + + + + + + + + + + + + + + + + +
{% cycle letra %}{% trans "Total de servidores em missão" %}{{ resumo.tot_servidores|default:"-" }}
{% cycle letra %}{% trans "Tamanho médio das equipes [F / A]" %}{{ resumo.media_membros|default:"-" }}
{% cycle letra %}{% trans "Menor equipe" %}{{ resumo.min_membros|default:"-" }}
{% cycle letra %}{% trans "Maior equipe" %}{{ resumo.max_membros|default:"-" }}
+
+
+
{% trans "Tempo" %}
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
{% cycle letra %}{% trans "Total de dias de evento" %}{{ resumo.tot_dias|default:"-" }}
{% cycle letra %}{% trans "Duração média dos eventos (dias) [J / A]" %}{{ resumo.media_dias|default:"-" }}
{% cycle letra %}{% trans "Total de diárias" %}{{ resumo.tot_diarias|default:"-" }}
{% cycle letra %}{% trans "Média de diárias por evento [L / A]" %}{{ resumo.media_diarias|default:"-" }}
{% cycle letra %}{% trans "Antecedência média na emissão de passagens" %}{{ resumo.media_antecedencia|default:"-" }}
{% cycle letra %}{% trans "Menor antecedência" %}{{ resumo.min_antecedencia|default:"-" }}
{% cycle letra %}{% trans "Maior antecedência" %}{{ resumo.max_antecedencia|default:"-" }}
+
+
+
{% trans "Custos" %}
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
{% cycle letra %}{% trans "Custo total" %}{{ resumo.tot_custo_total|default:"-" }}
{% cycle letra %}{% trans "Total com diárias" %}{{ resumo.tot_custo_diarias|default:"-" }}
{% cycle letra %}{% trans "Total com passagens" %}{{ resumo.tot_custo_passagens|default:"-" }}
{% cycle letra %}{% trans "Custo médio dos eventos [Q / A]" %}{{ resumo.media_custo_total|default:"-" }}
{% cycle letra %}{% trans "Custo médio de diárias por evento [R / A]" %}{{ resumo.media_custo_diarias|default:"-" }}
{% cycle letra %}{% trans "Custo médio de passagens por evento [S / A]" %}{{ resumo.media_custo_passagens|default:"-" }}
{% cycle letra %}{% trans "Custo médio por participante [Q / B]" %}{{ resumo.media_custo_participantes|default:"-" }}
{% cycle letra %}{% trans "Gasto médio por membro da equipe" %}{{ resumo.media_custo_membro|default:"-" }}
+
+
+
{% trans "Custos por região" %}
+ + + + + + + + + + + + + + + + + + + + + + + + {% for data in custos_regiao %} + + + + + + + + + + + + + + + + {% endfor %} + +
{% trans "Região" %}{% trans "Custos com diárias" %}{% trans "Custos com passagens" %}{% trans "Custo total" %}
{% trans "Mínimo" %}{% trans "Médio" %}{% trans "Máximo" %}{% trans "Total" %}{% trans "Mínimo" %}{% trans "Médio" %}{% trans "Máximo" %}{% trans "Total" %}{% trans "Mínimo" %}{% trans "Médio" %}{% trans "Máximo" %}{% trans "Total" %}
{{ data.nome }}{{ data.extrato.tot_diarias.min|default:"-" }}{{ data.extrato.tot_diarias.mean|default:"-"|floatformat:2 }}{{ data.extrato.tot_diarias.max|default:"-" }}{{ data.extrato.tot_diarias.sum|default:"-" }}{{ data.extrato.tot_passagens.min|default:"-" }}{{ data.extrato.tot_passagens.mean|default:"-"|floatformat:2 }}{{ data.extrato.tot_passagens.max|default:"-" }}{{ data.extrato.tot_passagens.sum|default:"-" }}{{ data.extrato.tot_custo.min|default:"-" }}{{ data.extrato.tot_custo.mean|default:"-"|floatformat:2 }}{{ data.extrato.tot_custo.max|default:"-" }}{{ data.extrato.tot_custo.sum|default:"-" }}
+
+
+{% endblock %} \ No newline at end of file diff --git a/sigi/apps/eventos/templates/admin/eventos/custos_servidor_report.html b/sigi/apps/eventos/templates/admin/eventos/custos_servidor_report.html new file mode 100644 index 0000000..60b49ee --- /dev/null +++ b/sigi/apps/eventos/templates/admin/eventos/custos_servidor_report.html @@ -0,0 +1,285 @@ +{% extends 'pdf/base_report.html' %} +{% load static i18n sigi_tags %} + +{% block page_size %}A4 landscape{% endblock page_size %} + +{% block extra_style %} + {{ block.super }} + aside { + margin-left: 8px; + font-size: 0.8em; + color: #666; + } + blockquote { + margin: 12px 0 12px; + padding-left: 1.5rem; + border-left: 5px solid #ee6e73; + font-size: 1.4em; + font-weight: bold; + } + tr:nth-child(even) { + background-color: initial; + } + .even-row { + background-color: #d2d2d2 !important; + } + + .sessao-resumo { + align-items: stretch; + display: flex; + flex-wrap: wrap; + width: 100%; + margin-top: 24px; + } + .card-resumo { + background-color: #eeeeef; + border-radius: 2px; + box-sizing: border-box; + margin: 6px; + flex-basis: 49%; + padding: 0 6px 6px 6px; + position: relative; + width: 100%; + } + .card-resumo.full { + flex-basis: 98%; + } + + .index-cell { + width: 2em; + text-align: center; + } + .label-resumo { + min-width: 30em; + } +{% endblock %} + +{% block main_content %} + + + + + + + + + + + + + + + {% for servidor in servidores %} + + + + + + + + + + + {% endfor %} + + + + + + + + + + +
{% trans "ID saberes" %}{% trans "Membro da equipe" %}{% trans "Qtde eventos" %}{% trans "Qtde diárias" %}{% trans "Valor médio diária" %}{% trans "Total diárias" %}{% trans "Total passagens" %}{% trans "Total" %}
{{ servidor.moodle_id }}{{ servidor.nome_completo }}{{ servidor.qtde_eventos|default:"-" }}{{ servidor.qtde_diarias|default:"-" }}{{ servidor.media_diarias|default:"-" }}{{ servidor.total_diarias|default:"-" }}{{ servidor.total_passagens|default:"-" }}{{ servidor.total_custo|default:"-" }}
{% trans "Totais" %}{{ totais.qtde_eventos|default:"-" }}{{ totais.qtde_diarias|default:"-" }}{{ totais.media_diarias|default:"-" }}{{ totais.total_diarias|default:"-" }}{{ totais.total_passagens|default:"-" }}{{ totais.total_custo|default:"-" }}
+ + {# Resumo do relatório #} + +
+
+
{% trans "Dados gerais" %}
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
{% cycle "A" "B" "C" "D" "E" "F" "G" "H" "I" "J" "K" "L" "M" "N" "O" "P" "Q" "R" "S" "T" "U" "V" "W" "X" "Y" "Z" as letra %}{% trans "Quantidade de eventos" %}{{ resumo.qtde_oficinas|default:"-" }}
{% cycle letra %}{% trans "Total de participantes" %}{{ resumo.tot_participantes|default:"-" }}
{% cycle letra %}{% trans "Média de participantes por evento" %} [B / A]{{ resumo.media_participantes|default:"-" }}
{% cycle letra %}{% trans "Mínimo de participantes" %}{{ resumo.min_participantes|default:"-" }}
{% cycle letra %}{% trans "Máximo de participantes" %}{{ resumo.max_participantes|default:"-" }}
+
+
+
{% trans "Equipes" %}
+ + + + + + + + + + + + + + + + + + + + + +
{% cycle letra %}{% trans "Total de servidores em missão" %}{{ resumo.tot_servidores|default:"-" }}
{% cycle letra %}{% trans "Tamanho médio das equipes [F / A]" %}{{ resumo.media_membros|default:"-" }}
{% cycle letra %}{% trans "Menor equipe" %}{{ resumo.min_membros|default:"-" }}
{% cycle letra %}{% trans "Maior equipe" %}{{ resumo.max_membros|default:"-" }}
+
+
+
{% trans "Tempo" %}
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
{% cycle letra %}{% trans "Total de dias de evento" %}{{ resumo.tot_dias|default:"-" }}
{% cycle letra %}{% trans "Duração média dos eventos (dias) [J / A]" %}{{ resumo.media_dias|default:"-" }}
{% cycle letra %}{% trans "Total de diárias" %}{{ resumo.tot_diarias|default:"-" }}
{% cycle letra %}{% trans "Média de diárias por evento [L / A]" %}{{ resumo.media_diarias|default:"-" }}
{% cycle letra %}{% trans "Antecedência média na emissão de passagens" %}{{ resumo.media_antecedencia|default:"-" }}
{% cycle letra %}{% trans "Menor antecedência" %}{{ resumo.min_antecedencia|default:"-" }}
{% cycle letra %}{% trans "Maior antecedência" %}{{ resumo.max_antecedencia|default:"-" }}
+
+
+
{% trans "Custos" %}
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
{% cycle letra %}{% trans "Custo total" %}{{ resumo.tot_custo_total|default:"-" }}
{% cycle letra %}{% trans "Total com diárias" %}{{ resumo.tot_custo_diarias|default:"-" }}
{% cycle letra %}{% trans "Total com passagens" %}{{ resumo.tot_custo_passagens|default:"-" }}
{% cycle letra %}{% trans "Custo médio dos eventos [Q / A]" %}{{ resumo.media_custo_total|default:"-" }}
{% cycle letra %}{% trans "Custo médio de diárias por evento [R / A]" %}{{ resumo.media_custo_diarias|default:"-" }}
{% cycle letra %}{% trans "Custo médio de passagens por evento [S / A]" %}{{ resumo.media_custo_passagens|default:"-" }}
{% cycle letra %}{% trans "Custo médio por participante [Q / B]" %}{{ resumo.media_custo_participantes|default:"-" }}
{% cycle letra %}{% trans "Gasto médio por membro da equipe" %}{{ resumo.media_custo_membro|default:"-" }}
+
+
+
{% trans "Custos por região" %}
+ + + + + + + + + + + + + + + + + + + + + + + + {% for data in custos_regiao %} + + + + + + + + + + + + + + + + {% endfor %} + +
{% trans "Região" %}{% trans "Custos com diárias" %}{% trans "Custos com passagens" %}{% trans "Custo total" %}
{% trans "Mínimo" %}{% trans "Médio" %}{% trans "Máximo" %}{% trans "Total" %}{% trans "Mínimo" %}{% trans "Médio" %}{% trans "Máximo" %}{% trans "Total" %}{% trans "Mínimo" %}{% trans "Médio" %}{% trans "Máximo" %}{% trans "Total" %}
{{ data.nome }}{{ data.extrato.tot_diarias.min|default:"-" }}{{ data.extrato.tot_diarias.mean|default:"-"|floatformat:2 }}{{ data.extrato.tot_diarias.max|default:"-" }}{{ data.extrato.tot_diarias.sum|default:"-" }}{{ data.extrato.tot_passagens.min|default:"-" }}{{ data.extrato.tot_passagens.mean|default:"-"|floatformat:2 }}{{ data.extrato.tot_passagens.max|default:"-" }}{{ data.extrato.tot_passagens.sum|default:"-" }}{{ data.extrato.tot_custo.min|default:"-" }}{{ data.extrato.tot_custo.mean|default:"-"|floatformat:2 }}{{ data.extrato.tot_custo.max|default:"-" }}{{ data.extrato.tot_custo.sum|default:"-" }}
+
+
+{% endblock %} \ No newline at end of file diff --git a/sigi/apps/eventos/templates/admin/eventos/evento/change_form.html b/sigi/apps/eventos/templates/admin/eventos/evento/change_form.html index 1ca5700..fa850be 100644 --- a/sigi/apps/eventos/templates/admin/eventos/evento/change_form.html +++ b/sigi/apps/eventos/templates/admin/eventos/evento/change_form.html @@ -21,6 +21,14 @@ {% endif %} +
  • + {% url opts|admin_urlname:'custos' object_id|admin_urlquote as tool_url %} + + + {% trans "Custos" %} + +
  • +
  • {% url opts|admin_urlname:'declaracaoreport' object_id|admin_urlquote as tool_url %} diff --git a/sigi/apps/eventos/templates/admin/eventos/evento/custos_report.html b/sigi/apps/eventos/templates/admin/eventos/evento/custos_report.html new file mode 100644 index 0000000..0533503 --- /dev/null +++ b/sigi/apps/eventos/templates/admin/eventos/evento/custos_report.html @@ -0,0 +1,163 @@ +{% extends 'pdf/base_report.html' %} +{% load static i18n sigi_tags %} + +{% block extra_style %} + {{ block.super }} + aside { + margin-left: 8px; + font-size: 0.8em; + color: #666; + } + blockquote { + margin: 20px 0 5px; + padding-left: 1.5rem; + border-left: 5px solid #ee6e73; + font-size: 1.4em; + font-weight: bold; + } +{% endblock %} + +{% block main_content %} +

    {% trans 'Custos de realização do evento' %}

    +

    + {% blocktranslate with nome=evento.nome turma=evento.turma %} + {{ nome }} - turma {{ turma }} + {% endblocktranslate %} +

    + +
    {% trans "Identificação do evento" %}
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    {% trans 'Casa anfitriã' %}{{ evento.casa_anfitria }}
    {% trans 'Tipo' %} + {% blocktranslate with tipo_evento=evento.tipo_evento virtual=evento.virtual|yesno:"- VIRTUAL,," %} + {{ tipo_evento }} {{ virtual }} + {% endblocktranslate %} +
    {% trans 'Descrição' %}{{ evento.descricao }}
    {% trans 'Senador(a) solicitante' %}{{ evento.solicitante }}
    {% trans 'Data do pedido' %}{{ evento.data_pedido }}
    {% trans 'Data de recebimento na COPERI' %}{{ evento.data_recebido_coperi }}
    {% trans 'Período de realização' %} + {% blocktranslate with inicio=evento.data_inicio termino=evento.data_termino %} + de {{ inicio }} até {{ termino }} + {% endblocktranslate %} +
    + +
    {% trans "Equipe participante" %}
    + + + + + + + + + + + + + + + + + + {% for membro in membros %} + + + + + + + + + + + + {% endfor %} + + + + + + + + + +
    {% trans "Nome" %}{% trans "Função" %}{% trans "Diárias" %}{% trans "Passagens" %}{% trans "Total gasto (A + B)" %}
    {% trans "Quantidade" %}{% trans "Valor unitário" %}{% trans "Valor total (A)" %}{% trans "Valor total (B)" %}{% trans "Emissão" %}{% trans "Dias de antecedência" %}*
    {{ membro.membro.nome_completo }}{{ membro.funcao }}{{ membro.qtde_diarias|default:"-" }}{{ membro.valor_diaria|default:"-" }}{{ membro.total_diarias|default:"-" }}{{ membro.total_passagens|default:"-" }}{{ membro.emissao_passagens|default:"-" }}{{ membro.antecedencia_passagens|default:"-" }}{{ membro.total_gasto|default:"-" }}
    {% trans "Total de gastos" %}{{ total_equipe.tot_qtde_diarias|default:"-" }}-{{ total_equipe.tot_valor_diarias|default:"-" }}{{ total_equipe.tot_passagens|default:"-" }}--{{ total_equipe.tot_gastos|default:"-" }}
    + + +
    {% trans "Resumo dos custos" %}
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    A{% trans 'Número de membros da equipe' %}{{ total_equipe.num_membros|default:"-" }}
    B{% trans 'Quantidade de diárias da missão' %}{{ total_equipe.tot_qtde_diarias|default:"-" }}
    C{% trans 'Total gasto com diárias' %}{{ total_equipe.tot_valor_diarias|default:"-" }}
    D{% trans 'Total gasto com passagens' %}{{ total_equipe.tot_passagens|default:"-" }}
    E{% trans 'Custo total do evento [ C + D ]' %}{{ total_equipe.tot_gastos|default:"-" }}
    {% trans "Médias" %}
    F{% trans 'Média de diárias por membro [ B / A ]' %}{{ total_equipe.media_qtde_diarias|default:"-" }}
    G{% trans 'Valor médio das diárias da equipe [ C / B ]' %}{{ total_equipe.media_diarias|default:"-" }}
    H{% trans 'Valor médio das passagens da equipe [ D / A ]' %}{{ total_equipe.media_passagens|default:"-" }}
    I{% trans 'Antecedência média na aquisição das passagens, em dias/fração **' %}{{ total_equipe.media_antecedencia|default:"-" }}
    + +{% endblock %} \ No newline at end of file diff --git a/sigi/apps/utils/templatetags/sigi_tags.py b/sigi/apps/utils/templatetags/sigi_tags.py index fb96f65..87fb7ab 100644 --- a/sigi/apps/utils/templatetags/sigi_tags.py +++ b/sigi/apps/utils/templatetags/sigi_tags.py @@ -34,5 +34,5 @@ def sum(value, arg): @register.simple_tag -def setvar(val=None): - return val +def multiply(value, arg): + return value * arg diff --git a/sigi/templates/pdf/base_report.html b/sigi/templates/pdf/base_report.html index 3416d0c..b26954c 100644 --- a/sigi/templates/pdf/base_report.html +++ b/sigi/templates/pdf/base_report.html @@ -33,7 +33,7 @@ footer img { } table { - border: 2px white; + border: 2px solid white; width: 100%; } th,td { @@ -58,6 +58,9 @@ tr:nth-child(even) { .right-align { text-align: right; } +.bold { + font-weight: bold; +} ul { list-style-type: none; margin: 0px;