Browse Source

Disponibiliza relatórios de custos no menu de relatórios. Gertiq #183393

dependabot/pip/requirements/djangorestframework-3.15.2 3.0.86
Sesóstris Vieira 8 months ago
parent
commit
fdf6f5d487
  1. 2
      sigi/apps/casas/views.py
  2. 234
      sigi/apps/eventos/admin.py
  3. 10
      sigi/apps/eventos/admin_urls.py
  4. 375
      sigi/apps/eventos/templates/admin/eventos/custos_eventos_report.html
  5. 101
      sigi/apps/eventos/templates/admin/eventos/custos_eventos_report_pdf.html
  6. 263
      sigi/apps/eventos/templates/admin/eventos/custos_eventos_report_snippet.html
  7. 149
      sigi/apps/eventos/templates/admin/eventos/custos_servidor_report.html
  8. 98
      sigi/apps/eventos/templates/admin/eventos/custos_servidor_report_pdf.html
  9. 36
      sigi/apps/eventos/templates/admin/eventos/custos_servidor_report_snippet.html
  10. 350
      sigi/apps/eventos/views.py
  11. 4
      sigi/apps/utils/views.py
  12. 4
      sigi/menu_conf.yaml

2
sigi/apps/casas/views.py

@ -434,7 +434,7 @@ class CnpjErradoReport(
orgaos.append(orgao)
return orgaos
def get_dataset(self):
def get_dataset(self, context):
return (
[
{f: getattr(o, f) for f in self.list_fields}

234
sigi/apps/eventos/admin.py

@ -1,5 +1,4 @@
import datetime
import pandas as pd
import time
from admin_auto_filters.filters import AutocompleteFilter
from moodle import Moodle
@ -12,9 +11,6 @@ from django.db.models import (
Q,
Sum,
Avg,
Min,
Max,
Prefetch,
Case,
When,
)
@ -54,7 +50,10 @@ from sigi.apps.eventos.models import (
ParticipantesEvento,
)
from sigi.apps.eventos.forms import EventoAdminForm, SelecionaModeloForm
from sigi.apps.servidores.models import Servidor
from sigi.apps.eventos.views import (
context_custos_eventos,
context_custos_servidor,
)
from sigi.apps.utils import abreviatura
from sigi.apps.utils.filters import DateRangeFilter
from sigi.apps.utils.mixins import (
@ -1481,172 +1480,23 @@ class EventoAdmin(AsciifyQParameter, CartExportReportMixin, admin.ModelAdmin):
)
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"),
output_field=my_decimal_field,
),
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
]
data_inicio = (
context = context_custos_eventos(self.get_queryset(request))
context["data_inicio"] = (
self.get_queryset(request)
.order_by("data_inicio")
.first()
.data_inicio
)
data_fim = (
context["data_fim"] = (
self.get_queryset(request)
.order_by("data_termino")
.last()
.data_termino
)
context = {
"eventos": eventos.order_by("data_inicio"),
"resumo": resumo,
"custos_regiao": custos_regiao,
"title": _("Custos por eventos"),
"data_inicio": data_inicio,
"data_fim": data_fim,
}
return WeasyTemplateResponse(
filename=f"custos_eventos-{timezone.localdate()}.pdf",
request=request,
template="admin/eventos/custos_eventos_report.html",
template="admin/eventos/custos_eventos_report_pdf.html",
context=context,
content_type="application/pdf",
)
@ -1654,83 +1504,25 @@ class EventoAdmin(AsciifyQParameter, CartExportReportMixin, admin.ModelAdmin):
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
),
)
)
data_inicio = (
context = context_custos_servidor(self.get_queryset(request))
context["data_inicio"] = (
self.get_queryset(request)
.order_by("data_inicio")
.first()
.data_inicio
)
data_fim = (
context["data_fim"] = (
self.get_queryset(request)
.exclude(data_termino=None)
.order_by("data_termino")
.last()
.data_termino
)
context = {
"servidores": servidores.order_by("nome_completo"),
"totais": totais,
"title": _("Custos por servidor"),
"data_inicio": data_inicio,
"data_fim": data_fim,
}
return WeasyTemplateResponse(
filename=f"custos_servidor-{timezone.localdate()}.pdf",
request=request,
template="admin/eventos/custos_servidor_report.html",
template="admin/eventos/custos_servidor_report_pdf.html",
context=context,
content_type="application/pdf",
)

10
sigi/apps/eventos/admin_urls.py

@ -17,4 +17,14 @@ urlpatterns = [
views.solicitacoes_por_periodo,
name="eventos_solicitacoesporperiodo",
),
path(
"custoseventos/",
views.CustosEventosReport.as_view(),
name="eventos_custoseventos",
),
path(
"custosservidor/",
views.CustosServidorReport.as_view(),
name="eventos_custosservidor",
),
]

375
sigi/apps/eventos/templates/admin/eventos/custos_eventos_report.html

@ -1,357 +1,22 @@
{% extends 'pdf/base_report.html' %}
{% load static i18n sigi_tags %}
{% block page_size %}A4 landscape{% endblock page_size %}
{% block page_margin %}3cm 1cm 2cm 1cm{% endblock page_margin %};
{% 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;
}
.timestamp-container {
width: 100%;
margin: 24px 10px;
border-left: 5px solid #ee6e73;
font-size: 1.3em;
}
.timestamp-row {
display: flex;
flex-wrap: wrap;
margin-bottom: 6px;
}
.timestamp-col {
position: relative;
padding-left: 15px;
padding-right: 15px;
}
.timestamp-label {
flex: 0 0 12%;
max-width: 12%;
font-weight: bold;
}
{% extends 'utils/report/report.html' %}
{% load i18n %}
{% block extrastyle %}
{{ block.super }}
<style type="text/css">
.sessao-resumo {
display: flex;
flex-wrap: wrap;
gap: 0.5em;
}
.card-resumo {
flex-grow: 1;
}
</style>
{% endblock %}
{% block main_content %}
<div class="timestamp-container">
<div class="timestamp-row">
<div class="timestamp-col timestamp-label">
{% trans "Data inicial" %}:
</div>
<div class="timestamp-col timestamp-data">
{{ data_inicio|date:"SHORT_DATE_FORMAT" }}<br/>
</div>
</div>
<div class="timestamp-row">
<div class="timestamp-col timestamp-label">
{% trans "Data final" %}:
</div>
<div class="timestamp-col timestamp-data">
{{ data_fim|date:"SHORT_DATE_FORMAT" }}
</div>
</div>
</div>
<table repeat="2">
<thead>
<tr>
<th rowspan="2">{% trans "Início / término" %}</th>
<th rowspan="2">{% trans "SIGAD" %}</th>
<th rowspan="2">{% trans "Evento" %}</th>
<th rowspan="2">{% trans "Casa anfitriã" %}</th>
<th rowspan="2">{% trans "Duração do evento (dias)" %}</th>
<th rowspan="2">{% trans "Total de participantes" %}</th>
<th colspan="7">{% trans "Equipe" %}</th>
<th colspan="3">{% trans "Custos" %}</th>
</tr>
<tr>
<th>{% trans "Nome" %}</th>
<th>{% trans "Função" %}</th>
<th>{% trans "Qtde de diárias" %}</th>
<th>{% trans "Valor total diárias" %}</th>
<th>{% trans "Valor total passagens" %}</th>
<th>{% trans "Emissão das passagens" %}</th>
<th>{% trans "Antecedência das passagens (dias)" %}</th>
<th>{% trans "Custo total" %}</th>
<th>{% trans "Custo médio participante" %}</th>
<th>{% trans "Custo médio equipe" %}</th>
</thead>
<tbody>
{% for evento in eventos %}
{% with equipe_count=evento.equipe_ext|length|default:1 %}
<tr class="{% cycle "" "even-row" as row_class %}">
<td rowspan="{{ equipe_count }}" class="center-align">
{% blocktranslate with inicio=evento.data_inicio|date:"SHORT_DATE_FORMAT" termino=evento.data_termino|date:"SHORT_DATE_FORMAT" %}
{{ inicio }} a {{ termino }}
{% endblocktranslate %}
</td>
<td rowspan="{{ equipe_count }}">{{ evento.num_processo }}</td>
<td rowspan="{{ equipe_count }}">
{% blocktranslate with nome=evento.nome turma=evento.turma %}
{{ nome }} - turma {{ turma }}
{% endblocktranslate %}
</td>
<td rowspan="{{ equipe_count }}">{{ evento.casa_anfitria }}</td>
<td rowspan="{{ equipe_count }}" class="right-align">{{ evento.duracao_dias|default:"-" }}</td>
<td rowspan="{{ equipe_count }}" class="right-align">{{ evento.total_participantes|default:"-" }}</td>
{% for membro in evento.equipe_ext %}
{% if not forloop.first %}<tr class="{{ row_class }}">{% endif %}
<td>{{ membro.membro.get_apelido }}</td>
<td>{{ membro.funcao }}</td>
<td class="right-align">{{ membro.qtde_diarias|floatformat:2|default:"-" }}</td>
<td class="right-align">{{ membro.total_diarias|floatformat:2|default:"-" }}</td>
<td class="right-align">{{ membro.total_passagens|floatformat:2|default:"-" }}</td>
<td>{{ membro.emissao_passagens|default:"-" }}</td>
<td class="right-align">{{ membro.antecedencia|default:"-" }}</td>
{% if forloop.first %}
<td rowspan="{{ equipe_count }}" class="right-align">{{ evento.custo_total|floatformat:2|default:"-" }}</td>
<td rowspan="{{ equipe_count }}" class="right-align">{{ evento.custo_medio_participante|floatformat:2|default:"-" }}</td>
<td rowspan="{{ equipe_count }}"class="right-align">{{ evento.custo_medio_membro|floatformat:2|default:"-" }}</td>
{% endif %}
</tr>
{% empty %}
<td colspan="7" class="center-align">{% trans "Equipe não definida" %}</td>
<td class="right-align">{{ evento.custo_total|floatformat:2|default:"-" }}</td>
<td class="right-align">{{ evento.custo_medio_participante|floatformat:2|default:"-" }}</td>
<td class="right-align">{{ evento.custo_medio_membro|floatformat:2|default:"-" }}</td>
</tr>
{% endfor %}
{% endwith %}
{% endfor %}
</tbody>
</table>
{# Resumo do relatório #}
<div class="sessao-resumo">
<div class="card-resumo">
<blockquote>{% trans "Dados gerais" %}</blockquote>
<table>
<tr>
<th class="index-cell">{% 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 %}</th>
<th class="label-resumo">{% trans "Quantidade de eventos" %}</th>
<td class="right-align">{{ resumo.qtde_oficinas|default:"-" }}</td>
</tr>
<tr>
<th class="index-cell">{% cycle letra %}</th>
<th class="label-resumo">{% trans "Total de participantes" %}</th>
<td class="right-align">{{ resumo.tot_participantes|default:"-" }}</td>
</tr>
<tr>
<th class="index-cell">{% cycle letra %}</th>
<th class="label-resumo">{% trans "Média de participantes por evento" %} [B / A]</th>
<td class="right-align">{{ resumo.media_participantes|default:"-" }}</td>
</tr>
<tr>
<th class="index-cell">{% cycle letra %}</th>
<th class="label-resumo">{% trans "Mínimo de participantes" %}</th>
<td class="right-align">{{ resumo.min_participantes|default:"-" }}</td>
</tr>
<tr>
<th class="index-cell">{% cycle letra %}</th>
<th class="label-resumo">{% trans "Máximo de participantes" %}</th>
<td class="right-align">{{ resumo.max_participantes|default:"-" }}</td>
</tr>
</table>
</div>
<div class="card-resumo">
<blockquote>{% trans "Equipes" %}</blockquote>
<table>
<tr>
<th class="index-cell">{% cycle letra %}</th>
<th class="label-resumo">{% trans "Total de servidores em missão" %}</th>
<td class="right-align">{{ resumo.tot_servidores|default:"-" }}</td>
</tr>
<tr>
<th class="index-cell">{% cycle letra %}</th>
<th class="label-resumo">{% trans "Tamanho médio das equipes [F / A]" %}</th>
<td class="right-align">{{ resumo.media_membros|default:"-" }}</td>
</tr>
<tr>
<th class="index-cell">{% cycle letra %}</th>
<th class="label-resumo">{% trans "Menor equipe" %}</th>
<td class="right-align">{{ resumo.min_membros|default:"-" }}</td>
</tr>
<tr>
<th class="index-cell">{% cycle letra %}</th>
<th class="label-resumo">{% trans "Maior equipe" %}</th>
<td class="right-align">{{ resumo.max_membros|default:"-" }}</td>
</tr>
</table>
</div>
<div class="card-resumo">
<blockquote>{% trans "Tempo" %}</blockquote>
<table>
<tr>
<th class="index-cell">{% cycle letra %}</th>
<th class="label-resumo">{% trans "Total de dias de evento" %}</th>
<td class="right-align">{{ resumo.tot_dias|default:"-" }}</td>
</tr>
<tr>
<th class="index-cell">{% cycle letra %}</th>
<th class="label-resumo">{% trans "Duração média dos eventos (dias) [J / A]" %}</th>
<td class="right-align">{{ resumo.media_dias|default:"-" }}</td>
</tr>
<tr>
<th class="index-cell">{% cycle letra %}</th>
<th class="label-resumo">{% trans "Total de diárias" %}</th>
<td class="right-align">{{ resumo.tot_diarias|default:"-" }}</td>
</tr>
<tr>
<th class="index-cell">{% cycle letra %}</th>
<th class="label-resumo">{% trans "Média de diárias por evento [L / A]" %}</th>
<td class="right-align">{{ resumo.media_diarias|default:"-" }}</td>
</tr>
<tr>
<th class="index-cell">{% cycle letra %}</th>
<th class="label-resumo">{% trans "Antecedência média na emissão de passagens" %}</th>
<td class="right-align">{{ resumo.media_antecedencia|floatformat:2|default:"-" }}</td>
</tr>
<tr>
<th class="index-cell">{% cycle letra %}</th>
<th class="label-resumo">{% trans "Menor antecedência" %}</th>
<td class="right-align">{{ resumo.min_antecedencia|default:"-" }}</td>
</tr>
<tr>
<th class="index-cell">{% cycle letra %}</th>
<th class="label-resumo">{% trans "Maior antecedência" %}</th>
<td class="right-align">{{ resumo.max_antecedencia|default:"-" }}</td>
</tr>
</table>
</div>
<div class="card-resumo">
<blockquote>{% trans "Custos" %}</blockquote>
<table>
<tr>
<th class="index-cell">{% cycle letra %}</th>
<th class="label-resumo">{% trans "Custo total" %}</th>
<td class="right-align">{{ resumo.tot_custo_total|floatformat:2|default:"-" }}</td>
</tr>
<tr>
<th class="index-cell">{% cycle letra %}</th>
<th class="label-resumo">{% trans "Total com diárias" %}</th>
<td class="right-align">{{ resumo.tot_custo_diarias|floatformat:2|default:"-" }}</td>
</tr>
<tr>
<th class="index-cell">{% cycle letra %}</th>
<th class="label-resumo">{% trans "Total com passagens" %}</th>
<td class="right-align">{{ resumo.tot_custo_passagens|floatformat:2|default:"-" }}</td>
</tr>
<tr>
<th class="index-cell">{% cycle letra %}</th>
<th class="label-resumo">{% trans "Custo médio dos eventos [Q / A]" %}</th>
<td class="right-align">{{ resumo.media_custo_total|floatformat:2|default:"-" }}</td>
</tr>
<tr>
<th class="index-cell">{% cycle letra %}</th>
<th class="label-resumo">{% trans "Custo médio de diárias por evento [R / A]" %}</th>
<td class="right-align">{{ resumo.media_custo_diarias|floatformat:2|default:"-" }}</td>
</tr>
<tr>
<th class="index-cell">{% cycle letra %}</th>
<th class="label-resumo">{% trans "Custo médio de passagens por evento [S / A]" %}</th>
<td class="right-align">{{ resumo.media_custo_passagens|floatformat:2|default:"-" }}</td>
</tr>
<tr>
<th class="index-cell">{% cycle letra %}</th>
<th class="label-resumo">{% trans "Custo médio por participante [Q / B]" %}</th>
<td class="right-align">{{ resumo.media_custo_participantes|floatformat:2|default:"-" }}</td>
</tr>
<tr>
<th class="index-cell">{% cycle letra %}</th>
<th class="label-resumo">{% trans "Gasto médio por membro da equipe" %}</th>
<td class="right-align">{{ resumo.media_custo_membro|floatformat:2|default:"-" }}</td>
</tr>
</table>
</div>
<div class="card-resumo full">
<blockquote>{% trans "Custos por região" %}</blockquote>
<table>
<thead>
<tr>
<th rowspan="2" class="center-align">{% trans "Região" %}</th>
<th colspan="4" class="center-align">{% trans "Custos com diárias" %}</th>
<th colspan="4" class="center-align">{% trans "Custos com passagens" %}</th>
<th colspan="4" class="center-align">{% trans "Custo total" %}</th>
</tr>
<tr>
<th class="right-align">{% trans "Mínimo" %}</th>
<th class="right-align">{% trans "Médio" %}</th>
<th class="right-align">{% trans "Máximo" %}</th>
<th class="right-align">{% trans "Total" %}</th>
<th class="right-align">{% trans "Mínimo" %}</th>
<th class="right-align">{% trans "Médio" %}</th>
<th class="right-align">{% trans "Máximo" %}</th>
<th class="right-align">{% trans "Total" %}</th>
<th class="right-align">{% trans "Mínimo" %}</th>
<th class="right-align">{% trans "Médio" %}</th>
<th class="right-align">{% trans "Máximo" %}</th>
<th class="right-align">{% trans "Total" %}</th>
</thead>
<tbody>
{% for data in custos_regiao %}
<tr>
<th>{{ data.nome }}</th>
<td class="right-align">{{ data.extrato.tot_diarias.min|floatformat:2|default:"-" }}</td>
<td class="right-align">{{ data.extrato.tot_diarias.mean|floatformat:2|default:"-" }}</td>
<td class="right-align">{{ data.extrato.tot_diarias.max|floatformat:2|default:"-" }}</td>
<td class="right-align">{{ data.extrato.tot_diarias.sum|floatformat:2|default:"-" }}</td>
<td class="right-align">{{ data.extrato.tot_passagens.min|floatformat:2|default:"-" }}</td>
<td class="right-align">{{ data.extrato.tot_passagens.mean|floatformat:2|default:"-" }}</td>
<td class="right-align">{{ data.extrato.tot_passagens.max|floatformat:2|default:"-" }}</td>
<td class="right-align">{{ data.extrato.tot_passagens.sum|floatformat:2|default:"-" }}</td>
<td class="right-align">{{ data.extrato.tot_custo.min|floatformat:2|default:"-" }}</td>
<td class="right-align">{{ data.extrato.tot_custo.mean|floatformat:2|default:"-" }}</td>
<td class="right-align">{{ data.extrato.tot_custo.max|floatformat:2|default:"-" }}</td>
<td class="right-align">{{ data.extrato.tot_custo.sum|floatformat:2|default:"-" }}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
{% endblock %}
{% block data %}
{% if eventos %}
{% include "admin/eventos/custos_eventos_report_snippet.html" %}
{% endif %}
{% endblock data %}

101
sigi/apps/eventos/templates/admin/eventos/custos_eventos_report_pdf.html

@ -0,0 +1,101 @@
{% extends 'pdf/base_report.html' %}
{% load static i18n sigi_tags %}
{% block page_size %}A4 landscape{% endblock page_size %}
{% block page_margin %}3cm 1cm 2cm 1cm{% endblock page_margin %};
{% 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;
}
.timestamp-container {
width: 100%;
margin: 24px 10px;
border-left: 5px solid #ee6e73;
font-size: 1.3em;
}
.timestamp-row {
display: flex;
flex-wrap: wrap;
margin-bottom: 6px;
}
.timestamp-col {
position: relative;
padding-left: 15px;
padding-right: 15px;
}
.timestamp-label {
flex: 0 0 12%;
max-width: 12%;
font-weight: bold;
}
{% endblock %}
{% block main_content %}
<div class="timestamp-container">
<div class="timestamp-row">
<div class="timestamp-col timestamp-label">
{% trans "Data inicial" %}:
</div>
<div class="timestamp-col timestamp-data">
{{ data_inicio|date:"SHORT_DATE_FORMAT" }}<br/>
</div>
</div>
<div class="timestamp-row">
<div class="timestamp-col timestamp-label">
{% trans "Data final" %}:
</div>
<div class="timestamp-col timestamp-data">
{{ data_fim|date:"SHORT_DATE_FORMAT" }}
</div>
</div>
</div>
{% include "admin/eventos/custos_eventos_report_snippet.html" %}
{% endblock %}

263
sigi/apps/eventos/templates/admin/eventos/custos_eventos_report_snippet.html

@ -0,0 +1,263 @@
{% load static i18n sigi_tags %}
<table repeat="2">
<thead>
<tr>
<th rowspan="3">{% trans "Início / término" %}</th>
<th rowspan="3">{% trans "SIGAD" %}</th>
<th rowspan="3">{% trans "Evento" %}</th>
<th rowspan="3">{% trans "Casa anfitriã" %}</th>
<th rowspan="3">{% trans "Dur. (dias)" %}</th>
<th rowspan="3">{% trans "Tot part." %}</th>
<th colspan="7">{% trans "Equipe" %}</th>
<th colspan="3" rowspan="2">{% trans "Custo" %}</th>
</tr>
<tr>
<th rowspan="2">{% trans "Nome" %}</th>
<th rowspan="2">{% trans "Função" %}</th>
<th colspan="2">{% trans "Diárias" %}</th>
<th colspan="3">{% trans "Passagens" %}</th>
</tr>
<tr>
<th>{% trans "Qtde." %}</th>
<th>{% trans "Valor total" %}</th>
<th>{% trans "Valor total" %}</th>
<th>{% trans "Emissão" %}</th>
<th>{% trans "Antec. (dias)" %}</th>
<th>{% trans "Total" %}</th>
<th>{% trans "Médio partic." %}</th>
<th>{% trans "Médio equipe" %}</th>
</thead>
<tbody>
{% for evento in eventos %}
{% with equipe_count=evento.equipe_ext|length|default:1 %}
<tr class="{% cycle "" "even-row" as row_class %}">
<td rowspan="{{ equipe_count }}" class="center-align">
{% blocktranslate with inicio=evento.data_inicio|date:"SHORT_DATE_FORMAT" termino=evento.data_termino|date:"SHORT_DATE_FORMAT" %}
{{ inicio }} a {{ termino }}
{% endblocktranslate %}
</td>
<td rowspan="{{ equipe_count }}">{{ evento.num_processo }}</td>
<td rowspan="{{ equipe_count }}">
{% blocktranslate with nome=evento.nome turma=evento.turma %}
{{ nome }} - turma {{ turma }}
{% endblocktranslate %}
</td>
<td rowspan="{{ equipe_count }}">{{ evento.casa_anfitria|default:"" }}</td>
<td rowspan="{{ equipe_count }}" class="right-align">{{ evento.duracao_dias|default:"-" }}</td>
<td rowspan="{{ equipe_count }}" class="right-align">{{ evento.total_participantes|default:"-" }}</td>
{% for membro in evento.equipe_ext %}
{% if not forloop.first %}<tr class="{{ row_class }}">{% endif %}
<td>{{ membro.membro.get_apelido }}</td>
<td>{{ membro.funcao }}</td>
<td class="right-align">{{ membro.qtde_diarias|floatformat:2|default:"-" }}</td>
<td class="right-align">{{ membro.total_diarias|floatformat:2|default:"-" }}</td>
<td class="right-align">{{ membro.total_passagens|floatformat:2|default:"-" }}</td>
<td>{{ membro.emissao_passagens|default:"-" }}</td>
<td class="right-align">{{ membro.antecedencia|default:"-" }}</td>
{% if forloop.first %}
<td rowspan="{{ equipe_count }}" class="right-align">{{ evento.custo_total|floatformat:2|default:"-" }}</td>
<td rowspan="{{ equipe_count }}" class="right-align">{{ evento.custo_medio_participante|floatformat:2|default:"-" }}</td>
<td rowspan="{{ equipe_count }}"class="right-align">{{ evento.custo_medio_membro|floatformat:2|default:"-" }}</td>
{% endif %}
</tr>
{% empty %}
<td colspan="7" class="center-align">{% trans "Equipe não definida" %}</td>
<td class="right-align">{{ evento.custo_total|floatformat:2|default:"-" }}</td>
<td class="right-align">{{ evento.custo_medio_participante|floatformat:2|default:"-" }}</td>
<td class="right-align">{{ evento.custo_medio_membro|floatformat:2|default:"-" }}</td>
</tr>
{% endfor %}
{% endwith %}
{% endfor %}
</tbody>
</table>
{# Resumo do relatório #}
<div class="sessao-resumo">
<div class="card-panel card-resumo">
<blockquote class="card-title">{% trans "Dados gerais" %}</blockquote>
<table>
<tr>
<th class="index-cell">{% 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 %}</th>
<th class="label-resumo">{% trans "Quantidade de eventos" %}</th>
<td class="right-align">{{ resumo.qtde_oficinas|default:"-" }}</td>
</tr>
<tr>
<th class="index-cell">{% cycle letra %}</th>
<th class="label-resumo">{% trans "Total de participantes" %}</th>
<td class="right-align">{{ resumo.tot_participantes|default:"-" }}</td>
</tr>
<tr>
<th class="index-cell">{% cycle letra %}</th>
<th class="label-resumo">{% trans "Média de participantes por evento" %} [B / A]</th>
<td class="right-align">{{ resumo.media_participantes|default:"-" }}</td>
</tr>
<tr>
<th class="index-cell">{% cycle letra %}</th>
<th class="label-resumo">{% trans "Mínimo de participantes" %}</th>
<td class="right-align">{{ resumo.min_participantes|default:"-" }}</td>
</tr>
<tr>
<th class="index-cell">{% cycle letra %}</th>
<th class="label-resumo">{% trans "Máximo de participantes" %}</th>
<td class="right-align">{{ resumo.max_participantes|default:"-" }}</td>
</tr>
</table>
</div>
<div class="card-panel card-resumo">
<blockquote class="card-title">{% trans "Equipes" %}</blockquote>
<table>
<tr>
<th class="index-cell">{% cycle letra %}</th>
<th class="label-resumo">{% trans "Total de servidores em missão" %}</th>
<td class="right-align">{{ resumo.tot_servidores|default:"-" }}</td>
</tr>
<tr>
<th class="index-cell">{% cycle letra %}</th>
<th class="label-resumo">{% trans "Tamanho médio das equipes [F / A]" %}</th>
<td class="right-align">{{ resumo.media_membros|default:"-" }}</td>
</tr>
<tr>
<th class="index-cell">{% cycle letra %}</th>
<th class="label-resumo">{% trans "Menor equipe" %}</th>
<td class="right-align">{{ resumo.min_membros|default:"-" }}</td>
</tr>
<tr>
<th class="index-cell">{% cycle letra %}</th>
<th class="label-resumo">{% trans "Maior equipe" %}</th>
<td class="right-align">{{ resumo.max_membros|default:"-" }}</td>
</tr>
</table>
</div>
<div class="card-panel card-resumo">
<blockquote class="card-title">{% trans "Tempo" %}</blockquote>
<table>
<tr>
<th class="index-cell">{% cycle letra %}</th>
<th class="label-resumo">{% trans "Total de dias de evento" %}</th>
<td class="right-align">{{ resumo.tot_dias|default:"-" }}</td>
</tr>
<tr>
<th class="index-cell">{% cycle letra %}</th>
<th class="label-resumo">{% trans "Duração média dos eventos (dias) [J / A]" %}</th>
<td class="right-align">{{ resumo.media_dias|default:"-" }}</td>
</tr>
<tr>
<th class="index-cell">{% cycle letra %}</th>
<th class="label-resumo">{% trans "Total de diárias" %}</th>
<td class="right-align">{{ resumo.tot_diarias|default:"-" }}</td>
</tr>
<tr>
<th class="index-cell">{% cycle letra %}</th>
<th class="label-resumo">{% trans "Média de diárias por evento [L / A]" %}</th>
<td class="right-align">{{ resumo.media_diarias|default:"-" }}</td>
</tr>
<tr>
<th class="index-cell">{% cycle letra %}</th>
<th class="label-resumo">{% trans "Antecedência média na emissão de passagens" %}</th>
<td class="right-align">{{ resumo.media_antecedencia|floatformat:2|default:"-" }}</td>
</tr>
<tr>
<th class="index-cell">{% cycle letra %}</th>
<th class="label-resumo">{% trans "Menor antecedência" %}</th>
<td class="right-align">{{ resumo.min_antecedencia|default:"-" }}</td>
</tr>
<tr>
<th class="index-cell">{% cycle letra %}</th>
<th class="label-resumo">{% trans "Maior antecedência" %}</th>
<td class="right-align">{{ resumo.max_antecedencia|default:"-" }}</td>
</tr>
</table>
</div>
<div class="card-panel card-resumo">
<blockquote class="card-title">{% trans "Custos" %}</blockquote>
<table>
<tr>
<th class="index-cell">{% cycle letra %}</th>
<th class="label-resumo">{% trans "Custo total" %}</th>
<td class="right-align">{{ resumo.tot_custo_total|floatformat:2|default:"-" }}</td>
</tr>
<tr>
<th class="index-cell">{% cycle letra %}</th>
<th class="label-resumo">{% trans "Total com diárias" %}</th>
<td class="right-align">{{ resumo.tot_custo_diarias|floatformat:2|default:"-" }}</td>
</tr>
<tr>
<th class="index-cell">{% cycle letra %}</th>
<th class="label-resumo">{% trans "Total com passagens" %}</th>
<td class="right-align">{{ resumo.tot_custo_passagens|floatformat:2|default:"-" }}</td>
</tr>
<tr>
<th class="index-cell">{% cycle letra %}</th>
<th class="label-resumo">{% trans "Custo médio dos eventos [Q / A]" %}</th>
<td class="right-align">{{ resumo.media_custo_total|floatformat:2|default:"-" }}</td>
</tr>
<tr>
<th class="index-cell">{% cycle letra %}</th>
<th class="label-resumo">{% trans "Custo médio de diárias por evento [R / A]" %}</th>
<td class="right-align">{{ resumo.media_custo_diarias|floatformat:2|default:"-" }}</td>
</tr>
<tr>
<th class="index-cell">{% cycle letra %}</th>
<th class="label-resumo">{% trans "Custo médio de passagens por evento [S / A]" %}</th>
<td class="right-align">{{ resumo.media_custo_passagens|floatformat:2|default:"-" }}</td>
</tr>
<tr>
<th class="index-cell">{% cycle letra %}</th>
<th class="label-resumo">{% trans "Custo médio por participante [Q / B]" %}</th>
<td class="right-align">{{ resumo.media_custo_participantes|floatformat:2|default:"-" }}</td>
</tr>
<tr>
<th class="index-cell">{% cycle letra %}</th>
<th class="label-resumo">{% trans "Gasto médio por membro da equipe" %}</th>
<td class="right-align">{{ resumo.media_custo_membro|floatformat:2|default:"-" }}</td>
</tr>
</table>
</div>
<div class="card-panel card-resumo full">
<blockquote class="card-title">{% trans "Custos por região" %}</blockquote>
<table>
<thead>
<tr>
<th rowspan="2" class="center-align">{% trans "Região" %}</th>
<th colspan="4" class="center-align">{% trans "Custos com diárias" %}</th>
<th colspan="4" class="center-align">{% trans "Custos com passagens" %}</th>
<th colspan="4" class="center-align">{% trans "Custo total" %}</th>
</tr>
<tr>
<th class="right-align">{% trans "Mínimo" %}</th>
<th class="right-align">{% trans "Médio" %}</th>
<th class="right-align">{% trans "Máximo" %}</th>
<th class="right-align">{% trans "Total" %}</th>
<th class="right-align">{% trans "Mínimo" %}</th>
<th class="right-align">{% trans "Médio" %}</th>
<th class="right-align">{% trans "Máximo" %}</th>
<th class="right-align">{% trans "Total" %}</th>
<th class="right-align">{% trans "Mínimo" %}</th>
<th class="right-align">{% trans "Médio" %}</th>
<th class="right-align">{% trans "Máximo" %}</th>
<th class="right-align">{% trans "Total" %}</th>
</thead>
<tbody>
{% for data in custos_regiao %}
<tr>
<th>{{ data.nome }}</th>
<td class="right-align">{{ data.extrato.tot_diarias.min|floatformat:2|default:"-" }}</td>
<td class="right-align">{{ data.extrato.tot_diarias.mean|floatformat:2|default:"-" }}</td>
<td class="right-align">{{ data.extrato.tot_diarias.max|floatformat:2|default:"-" }}</td>
<td class="right-align">{{ data.extrato.tot_diarias.sum|floatformat:2|default:"-" }}</td>
<td class="right-align">{{ data.extrato.tot_passagens.min|floatformat:2|default:"-" }}</td>
<td class="right-align">{{ data.extrato.tot_passagens.mean|floatformat:2|default:"-" }}</td>
<td class="right-align">{{ data.extrato.tot_passagens.max|floatformat:2|default:"-" }}</td>
<td class="right-align">{{ data.extrato.tot_passagens.sum|floatformat:2|default:"-" }}</td>
<td class="right-align">{{ data.extrato.tot_custo.min|floatformat:2|default:"-" }}</td>
<td class="right-align">{{ data.extrato.tot_custo.mean|floatformat:2|default:"-" }}</td>
<td class="right-align">{{ data.extrato.tot_custo.max|floatformat:2|default:"-" }}</td>
<td class="right-align">{{ data.extrato.tot_custo.sum|floatformat:2|default:"-" }}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>

149
sigi/apps/eventos/templates/admin/eventos/custos_servidor_report.html

@ -1,133 +1,28 @@
{% extends 'pdf/base_report.html' %}
{% load static i18n sigi_tags %}
{% extends 'utils/report/report.html' %}
{% load i18n %}
{% block page_size %}A4 landscape{% endblock page_size %}
{% block page_margin %}3cm 1cm 2cm 1cm{% endblock page_margin %};
{% 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;
}
.timestamp-container {
width: 100%;
margin: 24px 10px;
border-left: 5px solid #ee6e73;
font-size: 1.3em;
}
.timestamp-row {
display: flex;
flex-wrap: wrap;
margin-bottom: 6px;
}
.timestamp-col {
position: relative;
padding-left: 15px;
padding-right: 15px;
}
.timestamp-label {
flex: 0 0 12%;
max-width: 12%;
font-weight: bold;
}
{% block extrastyle %}
{{ block.super }}
<style type="text/css">
.sessao-resumo {
display: flex;
flex-wrap: wrap;
gap: 0.5em;
}
.card-resumo {
flex-grow: 1;
}
</style>
{% endblock %}
{% block main_content %}
<div class="timestamp-container">
<div class="timestamp-row">
<div class="timestamp-col timestamp-label">
{% trans "Data inicial" %}:
</div>
<div class="timestamp-col timestamp-data">
{{ data_inicio|date:"SHORT_DATE_FORMAT" }}<br/>
</div>
</div>
<div class="timestamp-row">
<div class="timestamp-col timestamp-label">
{% trans "Data final" %}:
</div>
<div class="timestamp-col timestamp-data">
{{ data_fim|date:"SHORT_DATE_FORMAT" }}
{% block data %}
{% if not servidores is None %}
<div class="row">
<div class="col s12">
<div class="card-panel">
{% include "admin/eventos/custos_servidor_report_snippet.html" %}
</div>
</div>
</div>
<table repeat="2">
<thead>
<tr>
<th>{% trans "Membro da equipe" %}</th>
<th>{% trans "Qtde eventos" %}</th>
<th>{% trans "Qtde diárias" %}</th>
<th>{% trans "Valor médio diária" %}</th>
<th>{% trans "Total diárias" %}</th>
<th>{% trans "Total passagens" %}</th>
<th>{% trans "Total" %}</th>
</tr>
</thead>
<tbody>
{% for servidor in servidores %}
<tr>
<td>{{ servidor.nome_completo }}</td>
<td class="right-align">{{ servidor.qtde_eventos|default:"-" }}</td>
<td class="right-align">{{ servidor.qtde_diarias|floatformat:2|default:"-" }}</td>
<td class="right-align">{{ servidor.media_diarias|floatformat:2|default:"-" }}</td>
<td class="right-align">{{ servidor.total_diarias|floatformat:2|default:"-" }}</td>
<td class="right-align">{{ servidor.total_passagens|floatformat:2|default:"-" }}</td>
<td class="right-align">{{ servidor.total_custo|floatformat:2|default:"-" }}</td>
</tr>
{% endfor %}
<tr>
<th class="right-align">{% trans "Totais" %}</th>
<th class="right-align">{{ totais.qtde_eventos|default:"-" }}</th>
<th class="right-align">{{ totais.qtde_diarias|floatformat:2|default:"-" }}</th>
<th class="right-align">{{ totais.media_diarias|floatformat:2|default:"-" }}</th>
<th class="right-align">{{ totais.total_diarias|floatformat:2|default:"-" }}</th>
<th class="right-align">{{ totais.total_passagens|floatformat:2|default:"-" }}</th>
<th class="right-align">{{ totais.total_custo|floatformat:2|default:"-" }}</th>
</tr>
</tbody>
</table>
{% endblock %}
{% endif %}
{% endblock data %}

98
sigi/apps/eventos/templates/admin/eventos/custos_servidor_report_pdf.html

@ -0,0 +1,98 @@
{% extends 'pdf/base_report.html' %}
{% load i18n %}
{% block page_size %}A4 landscape{% endblock page_size %}
{% block page_margin %}3cm 1cm 2cm 1cm{% endblock page_margin %};
{% 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;
}
.timestamp-container {
width: 100%;
margin: 24px 10px;
border-left: 5px solid #ee6e73;
font-size: 1.3em;
}
.timestamp-row {
display: flex;
flex-wrap: wrap;
margin-bottom: 6px;
}
.timestamp-col {
position: relative;
padding-left: 15px;
padding-right: 15px;
}
.timestamp-label {
flex: 0 0 12%;
max-width: 12%;
font-weight: bold;
}
{% endblock %}
{% block main_content %}
<div class="timestamp-container">
<div class="timestamp-row">
<div class="timestamp-col timestamp-label">
{% trans "Data inicial" %}:
</div>
<div class="timestamp-col timestamp-data">
{{ data_inicio|date:"SHORT_DATE_FORMAT" }}<br/>
</div>
</div>
<div class="timestamp-row">
<div class="timestamp-col timestamp-label">
{% trans "Data final" %}:
</div>
<div class="timestamp-col timestamp-data">
{{ data_fim|date:"SHORT_DATE_FORMAT" }}
</div>
</div>
</div>
{% include 'admin/eventos/custos_servidor_report_snippet.html' %}
{% endblock %}

36
sigi/apps/eventos/templates/admin/eventos/custos_servidor_report_snippet.html

@ -0,0 +1,36 @@
{% load i18n %}
<table class="striped" repeat="2">
<thead>
<tr>
<th>{% trans "Membro da equipe" %}</th>
<th>{% trans "Qtde eventos" %}</th>
<th>{% trans "Qtde diárias" %}</th>
<th>{% trans "Valor médio diária" %}</th>
<th>{% trans "Total diárias" %}</th>
<th>{% trans "Total passagens" %}</th>
<th>{% trans "Total" %}</th>
</tr>
</thead>
<tbody>
{% for servidor in servidores.itertuples %}
<tr>
<td>{{ servidor.nome_completo }}</td>
<td class="right-align">{{ servidor.qtde_eventos|default:"-" }}</td>
<td class="right-align">{{ servidor.qtde_diarias|floatformat:2|default:"-" }}</td>
<td class="right-align">{{ servidor.media_diarias|floatformat:2|default:"-" }}</td>
<td class="right-align">{{ servidor.total_diarias|floatformat:2|default:"-" }}</td>
<td class="right-align">{{ servidor.total_passagens|floatformat:2|default:"-" }}</td>
<td class="right-align">{{ servidor.total_custo|floatformat:2|default:"-" }}</td>
</tr>
{% endfor %}
<tr>
<th class="right-align">{% trans "Totais" %}</th>
<th class="right-align">{{ totais.qtde_eventos|default:"-" }}</th>
<th class="right-align">{{ totais.qtde_diarias|floatformat:2|default:"-" }}</th>
<th class="right-align">{{ totais.media_diarias|floatformat:2|default:"-" }}</th>
<th class="right-align">{{ totais.total_diarias|floatformat:2|default:"-" }}</th>
<th class="right-align">{{ totais.total_passagens|floatformat:2|default:"-" }}</th>
<th class="right-align">{{ totais.total_custo|floatformat:2|default:"-" }}</th>
</tr>
</tbody>
</table>

350
sigi/apps/eventos/views.py

@ -11,16 +11,21 @@ from django.contrib import messages
from django.contrib.admin.views.decorators import staff_member_required
from django.contrib.auth.decorators import login_required
from django.contrib.auth.mixins import LoginRequiredMixin, UserPassesTestMixin
from django.db import models
from django.db.models import (
Avg,
Case,
Count,
Sum,
Q,
F,
Max,
Min,
OuterRef,
Prefetch,
Q,
Subquery,
Case,
When,
Sum,
Value,
When,
)
from django.http import HttpResponse
from django.shortcuts import render
@ -51,6 +56,7 @@ from sigi.apps.eventos.serializers import (
EventoSerializer,
EventoListSerializer,
)
from sigi.apps.servidores.models import Servidor
from sigi.apps.utils.views import ReportListView
@ -89,7 +95,7 @@ class AlunosPorUfReportView(
queryset = queryset.filter(evento__virtual=False)
return queryset
def get_dataset(self):
def get_dataset(self, context):
queryset = self.get_queryset()
fieldnames = [
"evento__nome",
@ -1006,3 +1012,337 @@ class ApiEventoRetrieve(ApiEventoAbstract, generics.RetrieveAPIView):
"""
pass
class CustosEventosReport(
LoginRequiredMixin, UserPassesTestMixin, ReportListView
):
title = _("Custos por eventos")
template_name = "admin/eventos/custos_eventos_report.html"
template_name_pdf = "admin/eventos/custos_eventos_report_pdf.html"
filter_form = EventosPorUfForm
queryset = Evento.objects.filter(status=Evento.STATUS_REALIZADO)
list_fields = [
"nome",
"data_inicio",
"data_termino",
"turma",
"descricao",
"virtual",
"solicitante",
"num_processo",
"casa_anfitria__nome",
"casa_anfitria__municipio__nome",
"casa_anfitria__municipio__uf__sigla",
"duracao_dias",
"qtde_diarias",
"vlr_tot_diarias",
"custo_total",
"custo_medio_participante",
"custo_medio_membro",
"tot_membros",
]
def test_func(self):
return self.request.user.is_staff
def filter_queryset(self, queryset):
form = self.get_filter_form_instance()
if form.is_valid():
data_inicio = form.cleaned_data.get("data_inicio")
data_fim = form.cleaned_data.get("data_fim")
categorias = form.cleaned_data.get(
"categoria", [c[0] for c in TipoEvento.CATEGORIA_CHOICES]
)
modo = form.cleaned_data.get("virtual", ["V", "P"])
queryset = queryset.filter(
status=Evento.STATUS_REALIZADO,
data_inicio__gte=data_inicio,
data_termino__lte=data_fim,
tipo_evento__categoria__in=categorias,
)
if len(modo) == 1:
if "V" in modo:
queryset = queryset.filter(virtual=True)
else:
queryset = queryset.filter(virtual=False)
else:
queryset = queryset.none()
return queryset
def get_context_data(self, **kwargs):
queryset = self.get_queryset()
form = self.get_filter_form_instance()
if queryset.exists():
context = context_custos_eventos(queryset)
form.is_valid()
context["data_inicio"] = form.cleaned_data["data_inicio"]
context["data_fim"] = form.cleaned_data["data_fim"]
else:
context = {}
context["form"] = form
return context
def get_dataset(self, context):
dataset = context["eventos"]
return dataset.values(*self.list_fields), self.list_fields
class CustosServidorReport(
LoginRequiredMixin, UserPassesTestMixin, ReportListView
):
title = _("Custos por servidor")
template_name = "admin/eventos/custos_servidor_report.html"
template_name_pdf = "admin/eventos/custos_servidor_report_pdf.html"
filter_form = EventosPorUfForm
queryset = Evento.objects.filter(status=Evento.STATUS_REALIZADO)
def test_func(self):
return self.request.user.is_staff
def filter_queryset(self, queryset):
form = self.get_filter_form_instance()
if form.is_valid():
data_inicio = form.cleaned_data.get("data_inicio")
data_fim = form.cleaned_data.get("data_fim")
categorias = form.cleaned_data.get(
"categoria", [c[0] for c in TipoEvento.CATEGORIA_CHOICES]
)
modo = form.cleaned_data.get("virtual", ["V", "P"])
queryset = queryset.filter(
status=Evento.STATUS_REALIZADO,
data_inicio__gte=data_inicio,
data_termino__lte=data_fim,
tipo_evento__categoria__in=categorias,
)
if len(modo) == 1:
if "V" in modo:
queryset = queryset.filter(virtual=True)
else:
queryset = queryset.filter(virtual=False)
else:
queryset = queryset.none()
return queryset
def get_context_data(self, **kwargs):
queryset = self.get_queryset()
form = self.get_filter_form_instance()
if queryset.exists():
context = context_custos_servidor(queryset)
form.is_valid()
context["data_inicio"] = form.cleaned_data["data_inicio"]
context["data_fim"] = form.cleaned_data["data_fim"]
else:
context = {}
context["form"] = form
return context
def render_to_response(self, context, **response_kwargs):
if self._is_csv():
dataset = context["servidores"]
response = HttpResponse(content_type="text/csv")
response["Content-Disposition"] = (
f'attachment; filename="{self.get_filename()}.csv"'
)
dataset.to_csv(response, index=False, encoding="utf8")
return response
return super().render_to_response(context, **response_kwargs)
def context_custos_eventos(queryset):
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=models.functions.ExtractDay(
F("evento__data_inicio") - F("emissao_passagens")
),
)
eventos = queryset.annotate(
duracao_dias=(
models.functions.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"),
output_field=my_decimal_field,
),
vlr_tot_passagens=Sum("equipe__total_passagens"),
custo_total=F("vlr_tot_diarias") + F("vlr_tot_passagens"),
custo_medio_participante=models.functions.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=models.functions.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=models.functions.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=models.functions.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=models.functions.Cast(
1.0 * F("tot_dias") / F("qtde_oficinas"),
output_field=my_decimal_field,
),
tot_diarias=Sum("qtde_diarias"),
media_diarias=models.functions.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=models.functions.Cast(
F("tot_custo_total") / F("qtde_oficinas"),
output_field=my_decimal_field,
),
media_custo_diarias=models.functions.Cast(
F("tot_custo_diarias") / F("qtde_oficinas"),
output_field=my_decimal_field,
),
media_custo_passagens=models.functions.Cast(
F("tot_custo_passagens") / F("qtde_oficinas"),
output_field=my_decimal_field,
),
media_custo_participantes=models.functions.Cast(
F("tot_custo_total") / F("tot_participantes"),
output_field=my_decimal_field,
),
media_custo_membro=models.functions.Cast(
F("tot_custo_total") / Sum("tot_membros"),
output_field=my_decimal_field,
),
)
resumo.update(
eventos.aggregate(
media_antecedencia=Avg(
models.functions.ExtractDay(
F("data_inicio") - F("equipe__emissao_passagens")
)
),
min_antecedencia=Min(
models.functions.ExtractDay(
F("data_inicio") - F("equipe__emissao_passagens")
)
),
max_antecedencia=Max(
models.functions.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 = (
queryset.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
]
return {
"eventos": eventos.order_by("data_inicio"),
"resumo": resumo,
"custos_regiao": custos_regiao,
"title": _("Custos por eventos"),
}
def context_custos_servidor(queryset):
equipe_qs = Equipe.objects.filter(evento__in=queryset)
f_total_diarias = F("equipe_evento__qtde_diarias") * F(
"equipe_evento__valor_diaria"
)
servidores = (
(
Servidor.objects.distinct()
.filter(equipe_evento__evento__in=queryset)
.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"),
total_diarias=Sum(f_total_diarias),
total_passagens=Sum("equipe_evento__total_passagens"),
total_custo=Sum(
F("equipe_evento__total_passagens") + f_total_diarias
),
)
)
.order_by("nome_completo")
.values(
"nome_completo",
"qtde_eventos",
"qtde_diarias",
"total_diarias",
"total_passagens",
"total_custo",
)
)
servidores = pd.DataFrame(servidores)
totais = servidores[
[
"qtde_eventos",
"qtde_diarias",
"total_diarias",
"total_passagens",
"total_custo",
]
].sum()
servidores["media_diarias"] = (
servidores["total_diarias"] / servidores["qtde_diarias"]
)
totais["media_diarias"] = totais["total_diarias"] / totais["qtde_diarias"]
return {
"servidores": servidores.fillna(0),
"totais": totais.fillna(0),
"title": _("Custos por servidor"),
}

4
sigi/apps/utils/views.py

@ -85,7 +85,7 @@ class ReportListView(ListView):
def render_to_response(self, context, **response_kwargs):
if self._is_csv():
dataset, fieldnames = self.get_dataset()
dataset, fieldnames = self.get_dataset(context)
response = HttpResponse(content_type="text/csv")
response["Content-Disposition"] = (
f'attachment; filename="{self.get_filename()}.csv"'
@ -119,7 +119,7 @@ class ReportListView(ListView):
form = self.filter_form(self.request.GET)
return form
def get_dataset(self):
def get_dataset(self, context):
return (
self.get_queryset().values(*self.list_fields),
self.list_fields,

4
sigi/menu_conf.yaml

@ -40,6 +40,10 @@ main_menu:
view_name: eventos_eventosporuf
- title: Alunos por UF
view_name: eventos_alunosporuf
- title: Custos por evento
view_name: eventos_custoseventos
- title: Custos por servidor
view_name: eventos_custosservidor
- title: Solicitações de eventos por período
view_name: eventos_solicitacoesporperiodo
- title: Calendário de eventos

Loading…
Cancel
Save