From 6c4dbd1a5e7840f6e331d21156cc9505aa5b63c6 Mon Sep 17 00:00:00 2001 From: cristian-longhi Date: Tue, 2 Sep 2025 12:58:06 -0300 Subject: [PATCH] =?UTF-8?q?Ajustes=20solicitados=20-=20Relat=C3=B3rio=20de?= =?UTF-8?q?=20Vota=C3=A7=C3=B5es=20Nominais=20(#3785)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Implementa Relatório de Votações Nominais * Alterações solicitadas - Relatório de Votações Nominais * Apply suggestions from code review Todas as sugestões de alteração acatadas. Co-authored-by: Edward <9326037+edwardoliveira@users.noreply.github.com> * Update views.py Conforme observação sobre o retorno da QuerySet, escolha da opção 2 - colocar o qs dentro dos if's. Também houve a alteração na view, utilizando diretamente a classe genérica MultiFormatOutputMixin. Com a refatoração efetuada, não foi necessário definir uma especificação da mesma. --------- Co-authored-by: root Co-authored-by: Edward <9326037+edwardoliveira@users.noreply.github.com> --- sapl/relatorios/forms.py | 71 +++++++++++++++- sapl/relatorios/urls.py | 8 +- sapl/relatorios/views.py | 78 +++++++++++++++++- sapl/sessao/models.py | 13 +-- .../materia/materialegislativa_filter.html | 56 +++++++++++-- .../RelatorioVotacoesNominais_filter.html | 82 +++++++++++++++++++ .../relatorios/relatorio_votacao_nominal.html | 53 ++++++++++++ .../templates/relatorios/relatorios_list.html | 7 ++ 8 files changed, 351 insertions(+), 17 deletions(-) create mode 100644 sapl/templates/relatorios/RelatorioVotacoesNominais_filter.html create mode 100644 sapl/templates/relatorios/relatorio_votacao_nominal.html diff --git a/sapl/relatorios/forms.py b/sapl/relatorios/forms.py index 59eb2d741..cfbf369c1 100644 --- a/sapl/relatorios/forms.py +++ b/sapl/relatorios/forms.py @@ -3,17 +3,19 @@ from crispy_forms.bootstrap import (FormActions) from crispy_forms.layout import (HTML, Button, Fieldset, Layout, Submit) from django import forms +from django.forms import ModelChoiceField from django.utils.translation import ugettext_lazy as _ +from django.db.models import Q from sapl.audiencia.models import AudienciaPublica from sapl.base.models import Autor from sapl.comissoes.models import Reuniao from sapl.crispy_layout_mixin import SaplFormHelper, to_row, form_actions from sapl.materia.models import DocumentoAcessorio, MateriaLegislativa, MateriaEmTramitacao, UnidadeTramitacao, \ - StatusTramitacao + StatusTramitacao, TipoMateriaLegislativa from sapl.norma.models import NormaJuridica from sapl.protocoloadm.models import DocumentoAdministrativo -from sapl.sessao.models import SessaoPlenaria +from sapl.sessao.models import SessaoPlenaria, VotoParlamentar, RegistroVotacao from sapl.utils import FilterOverridesMetaMixin, choice_anos_com_normas, qs_override_django_filter, \ choice_anos_com_materias, choice_tipos_normas, autor_label, autor_modal @@ -68,6 +70,71 @@ class RelatorioDocumentosAcessoriosFilterSet(django_filters.FilterSet): ) +class RelatorioVotacoesNominaisFilterSet(django_filters.FilterSet): + + tipo_id = django_filters.ModelChoiceFilter( + queryset=TipoMateriaLegislativa.objects.all(), + method='ordem_or_expediente', + label='Tipo de Matéria', + empty_label="---------" + ) + numero = django_filters.NumberFilter( + widget=forms.NumberInput(attrs={'class': 'form-control', 'step': 'any'}), + method='ordem_or_expediente', + label='Número' + ) + ano = django_filters.ChoiceFilter( + choices=list(choice_anos_com_materias()), + widget=forms.Select(attrs={'class': 'form-control'}), + method='ordem_or_expediente', + label='Ano da Matéria' + ) + + def ordem_or_expediente(self, queryset, name, value): + if value is None: + return queryset + value = getattr(value, "pk", value) + ordem_q = f"ordem__materia__{name}" + expediente_q = f"expediente__materia__{name}" + return queryset.filter(Q(**{ordem_q: val})|Q(**{expediente_q: val})) + return queryset + + class Meta(FilterOverridesMetaMixin): + model = RegistroVotacao + fields = ['data_hora'] + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + + self.filters['data_hora'].label = 'Período (Data Inicial - Data Final)' + + row0= to_row([('tipo_id', 6), ('numero', 3), ('ano', 3)]) + + row1 = to_row([('data_hora', 12)]) + + buttons = FormActions( + *[ + HTML(""" +
+ + +
+ """) + ], + Submit('pesquisar', _('Pesquisar'), css_class='float-right', + onclick='return true;'), + css_class='form-group row justify-content-between', + ) + + self.form.helper = SaplFormHelper() + self.form.helper.form_method = 'GET' + self.form.helper.layout = Layout( + Fieldset(_('Pesquisa'), + row0, row1, + buttons) + ) + + class RelatorioAtasFilterSet(django_filters.FilterSet): class Meta(FilterOverridesMetaMixin): model = SessaoPlenaria diff --git a/sapl/relatorios/urls.py b/sapl/relatorios/urls.py index b27bfc55a..235becfab 100644 --- a/sapl/relatorios/urls.py +++ b/sapl/relatorios/urls.py @@ -11,7 +11,7 @@ from .views import (relatorio_capa_processo, RelatorioMateriasTramitacaoView, RelatorioMateriaAnoAssuntoView, RelatorioHistoricoTramitacaoView, RelatorioDataFimPrazoTramitacaoView, RelatorioPresencaSessaoView, RelatorioAtasView, RelatorioReuniaoView, RelatorioAudienciaView, RelatorioHistoricoTramitacaoAdmView, - RelatorioDocumentosAcessoriosView, RelatorioNormasPorAutorView) + RelatorioDocumentosAcessoriosView, RelatorioNormasPorAutorView, RelatorioVotacoesNominaisView) from ..base.views import EstatisticasAcessoNormas app_name = AppConfig.name @@ -95,6 +95,10 @@ urlpatterns = [ url(r'^sistema/relatorios/documentos_acessorios$', RelatorioDocumentosAcessoriosView.as_view(), name='relatorio_documentos_acessorios'), + url(r'^sistema/relatorios/votacoes_nominais$', + RelatorioVotacoesNominaisView.as_view(), + name='relatorio_votacoes_nominais'), url(r'^sistema/relatorios/normas-por-autor$', RelatorioNormasPorAutorView.as_view(), name='normas_por_autor'), -] \ No newline at end of file +] + diff --git a/sapl/relatorios/views.py b/sapl/relatorios/views.py index 99a299abb..b96557e16 100755 --- a/sapl/relatorios/views.py +++ b/sapl/relatorios/views.py @@ -31,7 +31,8 @@ from sapl.relatorios.forms import RelatorioNormasPorAutorFilterSet, RelatorioHis RelatorioNormasVigenciaFilterSet, RelatorioNormasMesFilterSet, RelatorioMateriasPorAutorFilterSet, \ RelatorioMateriasPorAnoAutorTipoFilterSet, RelatorioMateriasTramitacaoFilterSet, RelatorioAudienciaFilterSet, \ RelatorioReuniaoFilterSet, RelatorioDataFimPrazoTramitacaoFilterSet, RelatorioHistoricoTramitacaoFilterSet, \ - RelatorioPresencaSessaoFilterSet, RelatorioAtasFilterSet, RelatorioDocumentosAcessoriosFilterSet + RelatorioPresencaSessaoFilterSet, RelatorioAtasFilterSet, RelatorioDocumentosAcessoriosFilterSet, \ + RelatorioVotacoesNominaisFilterSet from sapl.sessao.models import (ExpedienteMateria, ExpedienteSessao, IntegranteMesa, JustificativaAusencia, Orador, OradorExpediente, @@ -50,7 +51,7 @@ from sapl.sessao.views import (get_identificacao_basica, get_mesa_diretora, from sapl.settings import MEDIA_URL from sapl.settings import STATIC_ROOT from sapl.utils import LISTA_DE_UFS, TrocaTag, filiacao_data, create_barcode, show_results_filter_set, \ - num_materias_por_tipo, parlamentares_ativos + num_materias_por_tipo, parlamentares_ativos, MultiFormatOutputMixin from .templates import (pdf_capa_processo_gerar, pdf_documento_administrativo_gerar, pdf_espelho_gerar, pdf_etiqueta_protocolo_gerar, pdf_materia_gerar, @@ -1560,6 +1561,10 @@ def relatorio_documento_acessorio(obj, request, context): return cria_relatorio(request, context, 'relatorios/relatorio_documento_acessorio.html') +def relatorio_votacao_nominal(obj, request, context): + return cria_relatorio(request, context, 'relatorios/relatorio_votacao_nominal.html') + + def relatorio_normas_por_autor(obj, request, context): return cria_relatorio(request, context, 'relatorios/relatorio_normas_por_autor.html') @@ -1880,6 +1885,75 @@ class RelatorioDocumentosAcessoriosView(RelatorioMixin, FilterView): return context +class RelatorioVotacoesNominaisView(RelatorioMixin, MultiFormatOutputMixin, FilterView): + model = VotoParlamentar + filterset_class = RelatorioVotacoesNominaisFilterSet + template_name = 'relatorios/RelatorioVotacoesNominais_filter.html' + relatorio = relatorio_votacao_nominal + paginate_by = 20 + + export_fields = [ + 'votacao_id', 'votacao', 'parlamentar__nome_parlamentar', 'voto' + ] + + def get_queryset(self): + query_params = Q(ordem__tipo_votacao=2)|Q(expediente__tipo_votacao=2) + if 'format' in self.request.GET: + order_fields = ['-votacao_id', 'parlamentar'] + qs = VotoParlamentar.objects.filter(query_params).order_by(*order_fields) + else: + order_fields = ['-id'] + qs = RegistroVotacao.objects.filter(query_params).order_by(*order_fields) + return qs + + def get_context_data(self, **kwargs): + context = super().get_context_data(**kwargs) + + context['title'] = _('Votações Nominais') + + if not self.filterset.form.is_valid(): + return context + + query_dict = self.request.GET.copy() + if 'page' in query_dict: + del query_dict['page'] + context['filter_url'] = f"&{query_dict.urlencode()}" if query_dict else '' + context['show_results'] = show_results_filter_set(query_dict) + + data_inicial = self.request.GET.get('data_hora_0', '') + data_final = self.request.GET.get('data_hora_1', '') + if not data_inicial: + data_inicial = "Data Inicial não definida" + if not data_final: + data_final = "Data Final não definida" + context['periodo'] = f"{data_inicial} - {data_final}" + + tipo_id = self.request.GET.get('tipo_id') + numero = self.request.GET.get('numero') + ano = self.request.GET.get('ano') + + if tipo_id: + context['tipo_materia'] = TipoMateriaLegislativa.objects.get(id=tipo_id) + if numero: + context['numero'] = int(numero) + if ano: + context['ano'] = ano + + if 'relatorio' not in self.request.GET: + paginator = context['paginator'] + page_obj = context['page_obj'] + + context['page_range'] = make_pagination( + page_obj.number, paginator.num_pages) + + context['qtde_votacoes'] = paginator.count + else: + self.paginate_by = None + context['qtde_votacoes'] = len(context['object_list']) + + return context + + class RelatorioAtasView(RelatorioMixin, FilterView): model = SessaoPlenaria filterset_class = RelatorioAtasFilterSet diff --git a/sapl/sessao/models.py b/sapl/sessao/models.py index 55fe581a1..613068afe 100644 --- a/sapl/sessao/models.py +++ b/sapl/sessao/models.py @@ -641,11 +641,14 @@ class RegistroVotacao(models.Model): ordering = ('id',) def __str__(self): - return _('Ordem: %(ordem)s - Votação: %(votacao)s - ' - 'Matéria: %(materia)s') % { - 'ordem': self.ordem, - 'votacao': self.tipo_resultado_votacao, - 'materia': self.materia} + if self.ordem: + return _('Ordem: %(ordem)s - Votação: %(votacao)s') % { + 'ordem': self.ordem, + 'votacao': self.tipo_resultado_votacao} + else: + return _('Expediente: %(expediente)s - Votação: %(votacao)s') % { + 'expediente': self.expediente, + 'votacao': self.tipo_resultado_votacao} def clean(self): """Exatamente um dos campos ordem ou expediente deve estar preenchido. diff --git a/sapl/templates/materia/materialegislativa_filter.html b/sapl/templates/materia/materialegislativa_filter.html index 0c5064806..f74c5d5ab 100644 --- a/sapl/templates/materia/materialegislativa_filter.html +++ b/sapl/templates/materia/materialegislativa_filter.html @@ -108,19 +108,47 @@ Resultado:  {{m|resultado_votacao}}
{% endif %} {% if m.registrovotacao_set.exists %} +
+
Data Votação: +
+
{% for rv in m.registrovotacao_set.all %} {% if rv.ordem %} - - {{ rv.ordem.sessao_plenaria.data_inicio }} - + {{ rv.ordem.sessao_plenaria.data_inicio }} + {% if rv.ordem.tipo_votacao == 2 %} + - + Votação Nominal >>> + + + {% endif %} {% elif rv.expediente %} - - {{ rv.expediente.sessao_plenaria.data_inicio }} - + {{ rv.expediente.sessao_plenaria.data_inicio }} + {% if rv.expediente.tipo_votacao == 2 %} + - + Votação Nominal >>> + + + {% endif %} {% endif %}
{% endfor %} +
+
{% endif %} {% if m.tramitacao_set.first.data_tramitacao %} Data da última Tramitação:  {{m.tramitacao_set.first.data_tramitacao}}
@@ -215,6 +243,11 @@ {% block extra_js %} {% endblock extra_js %} diff --git a/sapl/templates/relatorios/RelatorioVotacoesNominais_filter.html b/sapl/templates/relatorios/RelatorioVotacoesNominais_filter.html new file mode 100644 index 000000000..72d6f0212 --- /dev/null +++ b/sapl/templates/relatorios/RelatorioVotacoesNominais_filter.html @@ -0,0 +1,82 @@ +{% extends "crud/list.html" %} +{% load i18n %} +{% load crispy_forms_tags %} + +{% block base_content %} + {% if not show_results %} + {% crispy filter.form %} + {% else %} +
+ {% with 'sapl.relatorios:relatorio_votacoes_nominais' as url_reverse %} + {% include "crud/format_options.html" %} + {% endwith %} +
+ + +

+ PARÂMETROS DE PESQUISA
+ {% if tipo_materia %} +  Tipo de Matéria: {{ tipo_materia }}
+ {% endif %} + {% if numero %} +  Número: {{ numero }}
+ {% endif %} + {% if ano %} +  Ano: {{ ano }}
+ {% endif %} +  Período: {{ periodo }}

+ + {% if object_list %} + + {% if qtde_votacoes > 1 %} +

Foram encontradas {{qtde_votacoes}} votações.


+ {% elif qtde_votacoes == 1 %} +

Foi encontrada {{qtde_votacoes}} votação.


+ {% endif %} + + + + + + + + + + {% for rv in object_list %} + + + + + {% endfor %} + +
Dados da Votação / MatériaParlamentar - Voto
+ {% if rv.ordem %} + {{ rv.ordem.materia }} - {{ rv.ordem.materia.ementa }}
+ Momento da Votação: Ordem do Dia - + {{ rv.ordem.sessao_plenaria }}
+ Data da Votação: {{ rv.data_hora|date:"d/m/Y" }}
+ Resultado: {{ rv.ordem.resultado }} + {% else %} + {{ rv.expediente.materia }} - {{ rv.expediente.materia.ementa }}
+ Momento da Votação: Expediente - + {{ rv.expediente.sessao_plenaria }}
+ Data da Votação: {{ rv.data_hora|date:"d/m/Y" }}
+ Resultado: {{ rv.expediente.resultado }} + {% endif %} +
+ {% for voto in rv.votoparlamentar_set.all|dictsort:"parlamentar.nome_parlamentar" %} + + {{ voto.parlamentar }} - {{ voto.voto }} +
+ {% endfor %} +
+ {% else %} +

Nenhuma votação encontrada com esses parâmetros.



+ {% endif %} + {% endif %} + {% if filter_url %} + {% include "paginacao.html" %} + {% endif %} +{% endblock base_content %} diff --git a/sapl/templates/relatorios/relatorio_votacao_nominal.html b/sapl/templates/relatorios/relatorio_votacao_nominal.html new file mode 100644 index 000000000..72773a4e8 --- /dev/null +++ b/sapl/templates/relatorios/relatorio_votacao_nominal.html @@ -0,0 +1,53 @@ +{% extends "relatorios/base_relatorio.html" %} +{% load i18n %} +{% load common_tags %} +{% load static %} + +{% block content %} +

Votações Nominais

+ + PARÂMETROS DE PESQUISA:
+ {% if tipo_materia %} + Tipo de matéria: {{ tipo_materia }}
+ {% endif %} + {% if numero %} + Número: {{ numero }}
+ {% endif %} + {% if ano %} + Ano: {{ ano }}
+ {% endif %} + Período: {{ periodo }}
+
+ + {% if object_list %} + {% if qtde_votacoes > 1 %} +

Foram encontradas {{qtde_votacoes}} votações.

+ {% elif qtde_votacoes == 1 %} +

Foi encontrada {{qtde_votacoes}} votação.

+ {% endif %} + + {% for rv in object_list %} +
+ {% if rv.ordem %} + Matéria: {{ rv.ordem.materia }} - {{ rv.ordem.materia.ementa }}
+ Momento da Votação: Ordem do Dia - {{ rv.ordem.sessao_plenaria }}
+ Data da Votação: {{ rv.data_hora|date:"d/m/Y" }}
+ Resultado: {{ rv.ordem.resultado }}

+ {% else %} + Matéria: {{ rv.expediente.materia }} - {{ rv.expediente.materia.ementa }}
+ Momento da Votação: Expediente - {{ rv.expediente.sessao_plenaria }}
+ Data da Votação: {{ rv.data_hora|date:"d/m/Y" }}
+ Resultado: {{ rv.expediente.resultado }}

+ {% endif %} +
+ + {% endfor %} + {% else %} +

Nenhuma votação encontrada com esses parâmetros.



+ {% endif %} + +{% endblock content %} diff --git a/sapl/templates/relatorios/relatorios_list.html b/sapl/templates/relatorios/relatorios_list.html index 38346fa21..eec0843d0 100644 --- a/sapl/templates/relatorios/relatorios_list.html +++ b/sapl/templates/relatorios/relatorios_list.html @@ -127,6 +127,13 @@ Listagem e totalização de normas por autor, com filtros para tipo e período. {% endif %} + {% url 'sapl.relatorios:relatorio_votacoes_nominais' as relatorio_votacoes_nominais %} + {% if request|is_report_visible:relatorio_votacoes_nominais %} + + Votações Nominais + Votações Nominais em Expedientes e Ordens do Dia por data. + + {% endif %}