From 74996add63c512b311b10bddcf6b2b29da3af970 Mon Sep 17 00:00:00 2001 From: Gustavo Date: Thu, 11 Jun 2026 17:15:15 -0300 Subject: [PATCH] Feat(Materia/Assinatura): tela "Assinar Despachos em Lote" para Presidente da Mesa Adiciona pagina onde o Presidente da Mesa Diretora pode visualizar e assinar em lote, com um unico certificado A1, TODOS os documentos acessorios do tipo "Despacho" ainda pendentes de assinatura digital (filtro frouxo: tipo__descricao__icontains "despacho"). Reuso integral do backend ja existente: - docacessorio_assinar_lote() em views_assinatura.py ja aceita PKs de documentos de multiplas materias num unico POST. - Modal de 4 passos (selecao -> certificado -> previa -> progresso -> resumo) extraido de documentoacessorio_list.html para partial reusavel e incluido nos dois templates (refactor mecanico, HTML/JS byte-a-byte identico). Acesso: restrito ao grupo SGVP_GROUP_PRESIDENTE_MESA (ou superuser para depuracao); demais usuarios recebem 403. Atalhos pra tela aparecem so quando usuario esta no grupo: - "Pesquisar Materia Legislativa" (botao na linha de filtro de assinatura) - "Materias Pendentes de Assinatura" (botao no bloco de acoes) Arquivos: - sapl/materia/views.py: ListView DespachosPendentesLoteView nova + flag is_presidente_mesa nos contextos das views existentes. - sapl/materia/urls.py: rota /materia/despachos-pendentes-lote. - sapl/templates/materia/assinatura_doc_lote_modal.html (novo): partial com modal completo. - sapl/templates/materia/despachos_pendentes_lote_list.html (novo): tela do presidente. - sapl/templates/materia/documentoacessorio_list.html: substitui modal embedded por include do partial. - sapl/templates/materia/materialegislativa_filter.html: atalho. - sapl/templates/materia/materias_pendentes_assinatura_list.html: atalho. --- sapl/materia/urls.py | 4 + sapl/materia/views.py | 90 ++++ .../materia/assinatura_doc_lote_modal.html | 420 ++++++++++++++++++ .../despachos_pendentes_lote_list.html | 136 ++++++ .../materia/documentoacessorio_list.html | 405 +---------------- .../materia/materialegislativa_filter.html | 10 + .../materias_pendentes_assinatura_list.html | 7 + 7 files changed, 668 insertions(+), 404 deletions(-) create mode 100644 sapl/templates/materia/assinatura_doc_lote_modal.html create mode 100644 sapl/templates/materia/despachos_pendentes_lote_list.html diff --git a/sapl/materia/urls.py b/sapl/materia/urls.py index 273318d42..729071963 100644 --- a/sapl/materia/urls.py +++ b/sapl/materia/urls.py @@ -18,6 +18,7 @@ from sapl.materia.views import (AcompanhamentoConfirmarView, MateriaLegislativaCrud, MateriaLegislativaPesquisaView, MateriaTaView, MateriasPendentesAssinaturaView, + DespachosPendentesLoteView, NumeracaoCrud, OrgaoCrud, OrigemCrud, PrimeiraTramitacaoEmLoteView, ProposicaoCrud, ProposicaoDevolvida, ProposicaoPendente, @@ -131,6 +132,9 @@ urlpatterns_materia = [ url(r'^materia/pendentes-assinatura$', MateriasPendentesAssinaturaView.as_view(), name='materias_pendentes_assinatura'), + url(r'^materia/despachos-pendentes-lote$', + DespachosPendentesLoteView.as_view(), name='despachos_pendentes_lote'), + url(r'^materia/(?P\d+)/acompanhar-materia/$', AcompanhamentoMateriaView.as_view(), name='acompanhar_materia'), url(r'^materia/(?P\d+)/acompanhar-confirmar$', diff --git a/sapl/materia/views.py b/sapl/materia/views.py index cd7ada455..bd4f7b3f4 100644 --- a/sapl/materia/views.py +++ b/sapl/materia/views.py @@ -685,6 +685,88 @@ class MateriasPendentesAssinaturaView(LoginRequiredMixin, ListView): reverse('sapl.materia:pesquisar_materia') + '?status_assinatura=pendente' ) + # Flag pra mostrar atalho "Assinar Despachos em Lote" só pra + # Presidente da Mesa Diretora (feature dedicada — assina em lote + # todos os DocumentoAcessorio do tipo Despacho ainda pendentes) + from sapl.rules import SGVP_GROUP_PRESIDENTE_MESA + u = self.request.user + context['is_presidente_mesa'] = u.is_authenticated and ( + u.is_superuser or + u.groups.filter(name=SGVP_GROUP_PRESIDENTE_MESA).exists() + ) + return context + + +class DespachosPendentesLoteView(LoginRequiredMixin, ListView): + """ + Lista todos os Documentos Acessórios do tipo "Despacho" que ainda + não têm assinatura digital (pdf_assinado vazio), para que o + Presidente da Mesa Diretora possa assiná-los em lote. + + Reusa o backend `docacessorio_assinar_lote` (views_assinatura.py), + que já aceita PKs de documentos de múltiplas matérias. Reusa também + o modal de assinatura em lote já existente em + `documentoacessorio_list.html` (copiado no template desta view). + + Acesso restrito ao grupo `Presidente da Mesa Diretora` (ou + superuser, para depuração). Demais usuários recebem 403. + """ + template_name = 'materia/despachos_pendentes_lote_list.html' + model = DocumentoAcessorio + paginate_by = 50 + login_url = '/login/' + + def _is_presidente(self): + from sapl.rules import SGVP_GROUP_PRESIDENTE_MESA + u = self.request.user + return u.is_superuser or u.groups.filter( + name=SGVP_GROUP_PRESIDENTE_MESA + ).exists() + + def dispatch(self, request, *args, **kwargs): + # LoginRequiredMixin já trata anônimo + if request.user.is_authenticated and not self._is_presidente(): + from django.http import HttpResponseForbidden + return HttpResponseForbidden( + 'Acesso restrito ao grupo "Presidente da Mesa Diretora".' + ) + return super().dispatch(request, *args, **kwargs) + + def get_queryset(self): + # Filtro frouxo por nome do tipo — pega "Despacho", "Despacho do + # Presidente", "Despacho Inicial", etc. Decisão de produto. + return DocumentoAcessorio.objects.filter( + tipo__descricao__icontains='despacho' + ).filter( + Q(pdf_assinado__isnull=True) | Q(pdf_assinado='') + ).select_related('materia', 'materia__tipo', 'tipo').order_by( + '-data', '-id' + ) + + def get_context_data(self, **kwargs): + context = super().get_context_data(**kwargs) + # `docs_pendentes_lote` no MESMO formato que o modal de + # documentoacessorio_list.html espera (id + descricao). Aqui a + # descrição inclui referência à matéria pra o presidente + # conseguir identificar de qual matéria é o despacho. + docs = list(context['object_list']) + context['docs_pendentes_lote'] = [ + { + 'id': d.pk, + 'descricao': ( + f'{d.nome} — {d.materia.tipo.sigla} ' + f'{d.materia.numero}/{d.materia.ano} ' + f'({d.tipo}) — {d.data}' + ), + } + for d in docs + ] + paginator = context['paginator'] + page_obj = context['page_obj'] + context['page_range'] = make_pagination( + page_obj.number, paginator.num_pages + ) + context['total'] = paginator.count return context @@ -2846,6 +2928,14 @@ class MateriaLegislativaPesquisaView(MultiFormatOutputMixin, FilterView): else: context['materias_pendentes_lote'] = [] + # Flag pra mostrar atalho "Assinar Despachos em Lote" só pra + # Presidente da Mesa Diretora (vê DespachosPendentesLoteView) + from sapl.rules import SGVP_GROUP_PRESIDENTE_MESA + u = self.request.user + context['is_presidente_mesa'] = u.is_authenticated and ( + u.is_superuser or + u.groups.filter(name=SGVP_GROUP_PRESIDENTE_MESA).exists() + ) return context diff --git a/sapl/templates/materia/assinatura_doc_lote_modal.html b/sapl/templates/materia/assinatura_doc_lote_modal.html new file mode 100644 index 000000000..c3ab03bab --- /dev/null +++ b/sapl/templates/materia/assinatura_doc_lote_modal.html @@ -0,0 +1,420 @@ +{% comment %} +Partial reusavel: Modal de Assinatura Digital em Lote de Documentos +Acessorios. Espera no contexto a variavel `docs_pendentes_lote` +(lista de dicts com chaves `id` e `descricao`). O caller deve +envolver com if docs_pendentes_lote include endif e renderizar em +algum lugar um botao com id="btn-assinar-doc-lote" para disparar o +modal. + +Backend: POST para sapl.materia:docacessorio_assinar_lote +(views_assinatura.py::docacessorio_assinar_lote). + +Usado por: + - sapl/templates/materia/documentoacessorio_list.html + (assinar docs acessorios pendentes de UMA materia) + - sapl/templates/materia/despachos_pendentes_lote_list.html + (assinar despachos pendentes de TODAS as materias - Presidente da Mesa) +{% endcomment %} + + + diff --git a/sapl/templates/materia/despachos_pendentes_lote_list.html b/sapl/templates/materia/despachos_pendentes_lote_list.html new file mode 100644 index 000000000..39248f81b --- /dev/null +++ b/sapl/templates/materia/despachos_pendentes_lote_list.html @@ -0,0 +1,136 @@ +{% extends "crud/detail.html" %} +{% load i18n %} +{% load static %} + +{% block actions %} + +{% endblock %} + +{% block detail_content %} + +
+
+

+ + {% trans "Despachos Pendentes de Assinatura" %} + +

+ + + {% trans "Acesso exclusivo do" %} {% trans "Presidente da Mesa Diretora" %} + +
+
+ {% if total == 0 %} + + {% trans "Nenhum despacho pendente" %} + + {% elif total == 1 %} + + 1 {% trans "despacho pendente" %} + + {% else %} + + {{ total }} {% trans "despachos pendentes" %} + + {% endif %} +
+
+ +{% if not object_list %} + +{% else %} + + + + + + + + + + + + + + + + {% for d in object_list %} + + + + + + + + + {% endfor %} + +
{% trans "Matéria" %}{% trans "Nome do Despacho" %}{% trans "Tipo" %}{% trans "Autor" %}{% trans "Data" %}{% trans "Ações" %}
+ + {{ d.materia.tipo.sigla }} {{ d.materia.numero }}/{{ d.materia.ano }} + + + + {{ d.nome }} + + + {{ d.tipo }} + + {% if d.autor %} + {{ d.autor }} + {% else %} + + {% endif %} + + {% if d.data %} + {{ d.data|date:"d/m/Y" }} + {% else %} + + {% endif %} + + {% if d.arquivo %} + + + + {% else %} + + {% endif %} +
+ + {% include "paginacao.html" %} + + {# Modal de assinatura em lote (partial reusado de documentoacessorio_list.html) #} + {% include "materia/assinatura_doc_lote_modal.html" %} + +{% endif %} + +{% endblock detail_content %} diff --git a/sapl/templates/materia/documentoacessorio_list.html b/sapl/templates/materia/documentoacessorio_list.html index 69ac34a2b..34fb28247 100644 --- a/sapl/templates/materia/documentoacessorio_list.html +++ b/sapl/templates/materia/documentoacessorio_list.html @@ -182,409 +182,6 @@ {% endif %} {% if docs_pendentes_lote %} -{# ── Modal de Assinatura em Lote – Documentos Acessórios ─────────────── #} - - - + {% include "materia/assinatura_doc_lote_modal.html" %} {% endif %} {% endblock %} diff --git a/sapl/templates/materia/materialegislativa_filter.html b/sapl/templates/materia/materialegislativa_filter.html index 0c1a3ffb9..e87d550b0 100644 --- a/sapl/templates/materia/materialegislativa_filter.html +++ b/sapl/templates/materia/materialegislativa_filter.html @@ -144,6 +144,16 @@ {{ materias_pendentes_lote|length }} {% endif %} + + {# Assinar Despachos em Lote — atalho exclusivo do Presidente da Mesa Diretora #} + {% if is_presidente_mesa %} + + + Assinar Despachos em Lote + + {% endif %} {# ── /Linha 2 ────────────────────────────────────────────────────── #} diff --git a/sapl/templates/materia/materias_pendentes_assinatura_list.html b/sapl/templates/materia/materias_pendentes_assinatura_list.html index 49d653fa7..37da83d6a 100644 --- a/sapl/templates/materia/materias_pendentes_assinatura_list.html +++ b/sapl/templates/materia/materias_pendentes_assinatura_list.html @@ -4,6 +4,13 @@ {% block actions %}