diff --git a/sapl/relatorios/forms.py b/sapl/relatorios/forms.py
index 59eb2d741..a7f42e8e5 100644
--- a/sapl/relatorios/forms.py
+++ b/sapl/relatorios/forms.py
@@ -3,6 +3,7 @@ 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 sapl.audiencia.models import AudienciaPublica
@@ -10,10 +11,10 @@ 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
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 +69,68 @@ class RelatorioDocumentosAcessoriosFilterSet(django_filters.FilterSet):
)
+class RelatorioVotacoesNominaisFilterSet(django_filters.FilterSet):
+
+ @property
+ def qs(self):
+ parent = super(RelatorioVotacoesNominaisFilterSet, self).qs
+ return parent.distinct().order_by('-votacao_id', 'parlamentar')
+
+ class Meta(FilterOverridesMetaMixin):
+ model = VotoParlamentar
+ fields = ['data_hora']
+
+ def __init__(self, *args, **kwargs):
+ super(
+ RelatorioVotacoesNominaisFilterSet, self
+ ).__init__(*args, **kwargs)
+
+ self.filters['data_hora'].label = 'Período (Data Inicial - Data Final)'
+
+ tipo_materia = '''
'
+
+ numero = ''''''
+
+ ano = '''Ano da Matéria
+
--------- '''
+ for ano_materia in choice_anos_com_materias():
+ ano += '' + str(ano_materia[1]) + ' '
+
+ ano += '
'
+
+ row0= HTML('' + tipo_materia + numero + ano + '
')
+
+ row1 = to_row([('data_hora', 12)])
+
+ buttons = FormActions(
+ *[
+ HTML('''
+
+
+ Gerar relatório PDF
+
+ ''')
+ ],
+ 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..5244ea3d6 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,9 @@ 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..4c5eaab42 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, VotacoesMultiFormatOutputMixin
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, VotacoesMultiFormatOutputMixin, FilterView):
+ model = VotoParlamentar
+ filterset_class = RelatorioVotacoesNominaisFilterSet
+ template_name = 'relatorios/RelatorioVotacoesNominais_filter.html'
+ relatorio = relatorio_votacao_nominal
+
+ fields_base_report = [
+ 'votacao_id', 'votacao', 'parlamentar__nome_parlamentar', 'voto'
+ ]
+
+ fields_report = {
+ 'csv': fields_base_report,
+ 'xlsx': fields_base_report,
+ 'json': fields_base_report,
+ }
+
+ def get_context_data(self, **kwargs):
+ context = super(
+ RelatorioVotacoesNominaisView, self
+ ).get_context_data(**kwargs)
+
+ context['title'] = _('Votações Nominais')
+
+ if not self.filterset.form.is_valid():
+ return context
+
+ query_dict = self.request.GET.copy()
+ context['filter_url'] = ('&' + query_dict.urlencode()) if len(query_dict) > 0 else ''
+ context['show_results'] = show_results_filter_set(query_dict)
+
+ data_inicial = self.request.GET['data_hora_0']
+ data_final = self.request.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'] = (
+ data_inicial + ' - ' + data_final
+ )
+
+ if self.request.GET['tipo_materia'] or self.request.GET['numero'] or self.request.GET['ano']:
+ object_list = context['object_list']
+ if self.request.GET['tipo_materia']:
+ tipo_id = self.request.GET['tipo_materia']
+ context['tipo_materia'] = TipoMateriaLegislativa.objects.get(id=tipo_id)
+ object_list = object_list.filter(
+ Q(ordem__materia__tipo_id=tipo_id) |
+ Q(expediente__materia__tipo_id=tipo_id))
+ if self.request.GET['numero']:
+ numero = self.request.GET['numero']
+ context['numero'] = numero
+ object_list = object_list.filter(
+ Q(ordem__materia__numero=numero) |
+ Q(expediente__materia__numero=numero))
+ if self.request.GET['ano']:
+ ano = self.request.GET['ano']
+ context['ano'] = ano
+ object_list = object_list.filter(
+ Q(ordem__materia__ano=ano) |
+ Q(expediente__materia__ano=ano))
+ context['object_list'] = object_list
+
+
+ if not 'format' in query_dict:
+ context['qtde_votacoes'] = context['object_list'].distinct('votacao_id').count()
+
+ 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..d20ba930c 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.
@@ -696,7 +699,7 @@ class VotoParlamentar(models.Model): # RegistroVotacaoParlamentar
class Meta:
verbose_name = _('Registro de Votação de Parlamentar')
verbose_name_plural = _('Registros de Votações de Parlamentares')
- ordering = ('id',)
+ ordering = ('parlamentar',)
def __str__(self):
return _('Votação: %(votacao)s - Parlamentar: %(parlamentar)s') % {
diff --git a/sapl/templates/materia/materialegislativa_filter.html b/sapl/templates/materia/materialegislativa_filter.html
index 0c5064806..cddba92e4 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 %}
+
{% endif %}
{% if m.tramitacao_set.first.data_tramitacao %}
Data da última Tramitação: {{m.tramitacao_set.first.data_tramitacao}}
@@ -215,6 +243,14 @@
{% 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..36561fa7e
--- /dev/null
+++ b/sapl/templates/relatorios/RelatorioVotacoesNominais_filter.html
@@ -0,0 +1,79 @@
+{% 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 %}
+
+
+
+
+ Dados da Votação / Matéria
+ Parlamentar - Voto
+
+
+ {% for voto in object_list %}
+ {% ifchanged voto.votacao_id %}
+ {% if not forloop.first %}
+
+ {% endif %}
+
+ {% if voto.ordem %}
+ {{ voto.ordem.materia }} - {{ voto.ordem.materia.ementa }}
+ Momento da Votação: Ordem do Dia -
+ {{ voto.ordem.sessao_plenaria }}
+ Data da Votação: {{ voto.data_hora|date:"d/m/Y" }}
+ Resultado: {{ voto.ordem.resultado }}
+ {% else %}
+ {{ voto.expediente.materia }} - {{ voto.expediente.materia.ementa }}
+ Momento da Votação: Expediente -
+ {{ voto.expediente.sessao_plenaria }}
+ Data da Votação: {{ voto.data_hora|date:"d/m/Y" }}
+ Resultado: {{ voto.expediente.resultado }}
+
+ {% endif %}
+
+ {% endifchanged %}
+
+ {{ voto.parlamentar }} - {{ voto.voto }}
+
+ {% endfor %}
+
+
+ {% else %}
+ Nenhuma votação encontrada com esses parâmetros.
+ {% endif %}
+ {% endif %}
+ {% include "paginacao.html" %}
+{% 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..cb0605b20
--- /dev/null
+++ b/sapl/templates/relatorios/relatorio_votacao_nominal.html
@@ -0,0 +1,49 @@
+{% 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 %}
+ 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 voto in object_list %}
+ {% ifchanged voto.votacao_id %}
+ {% if not forloop.first %}{% endif %}
+
+ {% if voto.ordem %}
+ Matéria: {{ voto.ordem.materia }} - {{ voto.ordem.materia.ementa }}
+ Momento da Votação: Ordem do Dia - {{ voto.ordem.sessao_plenaria }}
+ Data da Votação: {{ voto.data_hora|date:"d/m/Y" }}
+ Resultado: {{ voto.ordem.resultado }}
+ {% else %}
+ Matéria: {{ voto.expediente.materia }} - {{ voto.expediente.materia.ementa }}
+ Momento da Votação: Expediente - {{ voto.expediente.sessao_plenaria }}
+ Data da Votação: {{ voto.data_hora|date:"d/m/Y" }}
+ Resultado: {{ voto.expediente.resultado }}
+ {% endif %}
+
+
+ {% endifchanged %}
+ {{ voto.parlamentar }} - {{ voto.voto }}
+ {% 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 %}
diff --git a/sapl/utils.py b/sapl/utils.py
index cddeb1f49..6c7213b0e 100644
--- a/sapl/utils.py
+++ b/sapl/utils.py
@@ -1707,3 +1707,125 @@ class PautaMultiFormatOutputMixin(MultiFormatOutputMixin):
output.close()
return response
+
+
+class VotacoesMultiFormatOutputMixin(MultiFormatOutputMixin):
+
+ def render_to_csv(self, context):
+ response = HttpResponse(content_type='text/csv')
+ response['Content-Disposition'] = f'attachment; filename="sapl_{self.request.resolver_match.url_name}.csv"'
+ response['Cache-Control'] = 'no-cache'
+ response['Pragma'] = 'no-cache'
+ response['Expires'] = 0
+ writer = csv.writer(response, delimiter=";",
+ quoting=csv.QUOTE_NONNUMERIC)
+
+ object_list = context['object_list']
+
+ data = [[list(self._headers(self.fields_report['csv']))], ]
+ for obj in object_list:
+ wr = list(self._write_row(obj, self.fields_report['csv']))
+ if wr[0] != data[-1][0][0]:
+ data.append([wr])
+ else:
+ data[-1].append(wr)
+
+ for mri, multirows in enumerate(data):
+ if len(multirows) == 1:
+ writer.writerow(multirows[0])
+ else:
+ for v in multirows:
+ writer.writerow(v)
+
+ return response
+
+ def render_to_xlsx(self, context):
+
+ object_list = context['object_list']
+
+ data = [[list(self._headers(self.fields_report['xlsx']))], ]
+ row = 0
+ for obj in object_list:
+ wr = list(self._write_row(obj, self.fields_report['xlsx']))
+ if wr[0] != data[-1][0][0]:
+ data.append([wr])
+ else:
+ data[-1].append(wr)
+
+ output = io.BytesIO()
+ wb = Workbook(output, {'in_memory': True})
+
+ ws = wb.add_worksheet()
+
+ for mri, multirows in enumerate(data):
+ if len(multirows) == 1:
+ for rc, cell in enumerate(multirows[0]):
+ ws.write(row, rc, cell)
+ row += 1
+ else:
+ for v in multirows:
+ for rc, cell in enumerate(v):
+ try:
+ ws.write(row, rc, cell)
+ except TypeError:
+ ws.write(row, rc, str(cell))
+ row += 1
+ ws.autofit()
+ wb.close()
+
+ output.seek(0)
+
+ response = HttpResponse(output.read(
+ ), content_type="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet")
+ response['Content-Disposition'] = f'attachment; filename="sapl_{self.request.resolver_match.url_name}.xlsx"'
+ response['Cache-Control'] = 'no-cache'
+ response['Pragma'] = 'no-cache'
+ response['Expires'] = 0
+
+ output.close()
+
+ return response
+
+ def render_to_json(self, context):
+
+ object_list = context['object_list']
+
+ data = []
+ for obj in object_list:
+ wr = list(self._write_row(obj, self.fields_report['json']))
+
+ wr[1] = str(wr[1])
+ if not data:
+ data.append([wr])
+ continue
+
+ if wr[0] != data[-1][0][0]:
+ data.append([wr])
+ else:
+ data[-1].append(wr)
+
+ fields_report = list(map(lambda i, j: (i, j), self.fields_report['json'], self._headers(self.fields_report['json'])))
+ fields_report[2] = ('parlamentar_voto', 'Voto por Parlamentar')
+ fields_report_data = []
+ for f in fields_report:
+ fields_report_data.append(f[0])
+ for mri, multirows in enumerate(data):
+ parlamentar_voto = []
+ for ri, cols in enumerate(multirows):
+ parlamentar_voto.append([cols[2], cols[3]])
+ data[mri] = dict(
+ map(lambda i, j: (i, j), fields_report_data, [multirows[0][0], multirows[0][1], parlamentar_voto]))
+
+ json_metadata = {
+ 'headers': dict(fields_report[:-1]),
+ 'results': data
+ }
+
+ response = JsonResponse(json_metadata)
+ response['Content-Disposition'] = f'attachment; filename="sapl_{self.request.resolver_match.url_name}.json"'
+ response['Cache-Control'] = 'no-cache'
+ response['Pragma'] = 'no-cache'
+ response['Expires'] = 0
+
+ return response
+