Browse Source

Como cliente, quero que documentos não assinados tenham filtro na tela de Minhas Matérias [AB#1322]

pull/3858/head
KemuelAlves 4 months ago
parent
commit
e9dd39d664
  1. 50
      sapl/materia/forms.py
  2. 5
      sapl/materia/urls.py
  3. 51
      sapl/materia/views.py
  4. 22
      sapl/static/js/materia_pesquisa_download_pdfs.js
  5. 35
      sapl/templates/materia/materialegislativa_filter.html
  6. 119
      sapl/templates/materia/materias_pendentes_assinatura_list.html
  7. 2
      sapl/templates/navbar.yaml

50
sapl/materia/forms.py

@ -979,12 +979,26 @@ class AnexadaForm(ModelForm):
fields = ['tipo', 'numero', 'ano', 'data_anexacao', 'data_desanexacao'] fields = ['tipo', 'numero', 'ano', 'data_anexacao', 'data_desanexacao']
CHOICE_STATUS_ASSINATURA = [
('', _('Todas')),
('pendente', _('⚠ Pendente de Assinatura')),
('assinada', _('✔ Assinada')),
]
class MateriaLegislativaFilterSet(django_filters.FilterSet): class MateriaLegislativaFilterSet(django_filters.FilterSet):
ano = django_filters.ChoiceFilter(required=False, ano = django_filters.ChoiceFilter(required=False,
label='Ano da Matéria', label='Ano da Matéria',
choices=choice_anos_com_materias) choices=choice_anos_com_materias)
status_assinatura = django_filters.ChoiceFilter(
required=False,
label=_('Status de Assinatura'),
choices=CHOICE_STATUS_ASSINATURA,
method='filter_status_assinatura'
)
autoria__autor = django_filters.CharFilter(widget=forms.HiddenInput()) autoria__autor = django_filters.CharFilter(widget=forms.HiddenInput())
autoria__primeiro_autor = django_filters.BooleanFilter( autoria__primeiro_autor = django_filters.BooleanFilter(
@ -1074,6 +1088,17 @@ class MateriaLegislativaFilterSet(django_filters.FilterSet):
self.filters['o'].label = _('Ordenação') self.filters['o'].label = _('Ordenação')
self.form.fields['tipo_listagem'] = self.tipo_listagem self.form.fields['tipo_listagem'] = self.tipo_listagem
row_assinatura = to_row([
(HTML('''
<div class="alert alert-warning d-flex align-items-center py-2 mb-0"
style="border-left: 4px solid #f0ad4e; background:#fffbf0;">
<span class="mr-2" style="font-size:1.2em;"></span>
<strong class="mr-2">Assinatura Digital:</strong>
</div>
'''), 3),
('status_assinatura', 4),
])
row1 = to_row( row1 = to_row(
[('tipo', 5), ('ementa', 7)]) [('tipo', 5), ('ementa', 7)])
row2 = to_row( row2 = to_row(
@ -1131,6 +1156,7 @@ class MateriaLegislativaFilterSet(django_filters.FilterSet):
HTML(autor_label), HTML(autor_label),
HTML(autor_modal), HTML(autor_modal),
row4, row4,
row_assinatura,
), ),
Button('btn_pesquisa_avancada', 'Pesquisa Avançada >>>', Button('btn_pesquisa_avancada', 'Pesquisa Avançada >>>',
css_id='btn_pesquisa_avancada_id', css_id='btn_pesquisa_avancada_id',
@ -1158,6 +1184,30 @@ class MateriaLegislativaFilterSet(django_filters.FilterSet):
) )
) )
def filter_status_assinatura(self, queryset, name, value):
if value == 'pendente':
# Tem texto original mas não tem PDF assinado
return queryset.filter(
texto_original__isnull=False
).exclude(
texto_original=''
).filter(
pdf_assinado__isnull=True
) | queryset.filter(
texto_original__isnull=False
).exclude(
texto_original=''
).filter(
pdf_assinado=''
)
elif value == 'assinada':
return queryset.exclude(
pdf_assinado__isnull=True
).exclude(
pdf_assinado=''
)
return queryset
@property @property
def qs(self): def qs(self):
qs = qs_override_django_filter(self) qs = qs_override_django_filter(self)

5
sapl/materia/urls.py

@ -16,6 +16,7 @@ from sapl.materia.views import (AcompanhamentoConfirmarView,
LegislacaoCitadaCrud, MateriaAssuntoCrud, LegislacaoCitadaCrud, MateriaAssuntoCrud,
MateriaLegislativaCrud, MateriaLegislativaCrud,
MateriaLegislativaPesquisaView, MateriaTaView, MateriaLegislativaPesquisaView, MateriaTaView,
MateriasPendentesAssinaturaView,
NumeracaoCrud, OrgaoCrud, OrigemCrud, NumeracaoCrud, OrgaoCrud, OrigemCrud,
PrimeiraTramitacaoEmLoteView, ProposicaoCrud, PrimeiraTramitacaoEmLoteView, ProposicaoCrud,
ProposicaoDevolvida, ProposicaoPendente, ProposicaoDevolvida, ProposicaoPendente,
@ -122,6 +123,10 @@ urlpatterns_materia = [
url(r'^materia/pesquisar-materia$', url(r'^materia/pesquisar-materia$',
MateriaLegislativaPesquisaView.as_view(), name='pesquisar_materia'), MateriaLegislativaPesquisaView.as_view(), name='pesquisar_materia'),
url(r'^materia/pendentes-assinatura$',
MateriasPendentesAssinaturaView.as_view(), name='materias_pendentes_assinatura'),
url(r'^materia/(?P<pk>\d+)/acompanhar-materia/$', url(r'^materia/(?P<pk>\d+)/acompanhar-materia/$',
AcompanhamentoMateriaView.as_view(), name='acompanhar_materia'), AcompanhamentoMateriaView.as_view(), name='acompanhar_materia'),
url(r'^materia/(?P<pk>\d+)/acompanhar-confirmar$', url(r'^materia/(?P<pk>\d+)/acompanhar-confirmar$',

51
sapl/materia/views.py

@ -15,7 +15,7 @@ from crispy_forms.layout import Div, HTML, Submit
from django.conf import settings from django.conf import settings
from django.contrib import messages from django.contrib import messages
from django.contrib.auth.decorators import permission_required from django.contrib.auth.decorators import permission_required
from django.contrib.auth.mixins import PermissionRequiredMixin from django.contrib.auth.mixins import LoginRequiredMixin, PermissionRequiredMixin
from django.contrib.contenttypes.models import ContentType from django.contrib.contenttypes.models import ContentType
from django.core.exceptions import ObjectDoesNotExist, MultipleObjectsReturned, ValidationError from django.core.exceptions import ObjectDoesNotExist, MultipleObjectsReturned, ValidationError
from django.db.models import Max, Q from django.db.models import Max, Q
@ -38,7 +38,7 @@ from django.utils.decorators import method_decorator
import sapl import sapl
from sapl.base.email_utils import do_envia_email_confirmacao from sapl.base.email_utils import do_envia_email_confirmacao
from sapl.base.models import Autor, CasaLegislativa, AppConfig as BaseAppConfig from sapl.base.models import Autor, CasaLegislativa, AppConfig as BaseAppConfig, OperadorAutor
from sapl.comissoes.models import Participacao from sapl.comissoes.models import Participacao
from sapl.compilacao.models import STATUS_TA_IMMUTABLE_RESTRICT, STATUS_TA_PRIVATE from sapl.compilacao.models import STATUS_TA_IMMUTABLE_RESTRICT, STATUS_TA_PRIVATE
from sapl.compilacao.views import IntegracaoTaView from sapl.compilacao.views import IntegracaoTaView
@ -641,6 +641,53 @@ class ProposicaoPendenteSetor(PermissionRequiredMixin, ListView):
return context return context
class MateriasPendentesAssinaturaView(LoginRequiredMixin, ListView):
template_name = 'materia/materias_pendentes_assinatura_list.html'
model = MateriaLegislativa
paginate_by = 20
login_url = '/login/'
def get_autor(self):
try:
return OperadorAutor.objects.get(user=self.request.user).autor
except OperadorAutor.DoesNotExist:
return None
def get_queryset(self):
autor = self.get_autor()
qs = MateriaLegislativa.objects.filter(
texto_original__isnull=False
).exclude(
texto_original=''
).filter(
Q(pdf_assinado__isnull=True) | Q(pdf_assinado='')
)
if autor:
qs = qs.filter(autoria__autor=autor)
return qs.order_by('-data_apresentacao', '-id').distinct()
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
autor = self.get_autor()
context['autor'] = autor
paginator = context['paginator']
page_obj = context['page_obj']
context['page_range'] = make_pagination(page_obj.number, paginator.num_pages)
context['total'] = paginator.count
# URL para pesquisar todas filtrando por autor + pendente
if autor:
context['url_pesquisa_completa'] = (
reverse('sapl.materia:pesquisar_materia')
+ f'?autoria__autor={autor.pk}&status_assinatura=pendente'
)
else:
context['url_pesquisa_completa'] = (
reverse('sapl.materia:pesquisar_materia')
+ '?status_assinatura=pendente'
)
return context
class RevisarProposicaoSetor(PermissionRequiredMixin, UpdateView): class RevisarProposicaoSetor(PermissionRequiredMixin, UpdateView):
template_name = "materia/revisar_proposicao_setor.html" template_name = "materia/revisar_proposicao_setor.html"
model = Proposicao model = Proposicao

22
sapl/static/js/materia_pesquisa_download_pdfs.js

@ -208,5 +208,27 @@
downloadAllPDFs(); downloadAllPDFs();
}); });
} }
// ── Filtro rápido de assinatura ──────────────────────────────────
const selectAssinatura = document.getElementById('select-status-assinatura');
if (selectAssinatura) {
selectAssinatura.addEventListener('change', function () {
const status = this.value;
const url = new URL(window.location.href);
const params = url.searchParams;
// Volta sempre para a página 1
params.delete('page');
if (status === '') {
params.delete('status_assinatura');
} else {
params.set('status_assinatura', status);
}
window.location.href = url.pathname + '?' + params.toString();
});
}
// ─────────────────────────────────────────────────────────────────
}); });
})(); })();

35
sapl/templates/materia/materialegislativa_filter.html

@ -19,6 +19,23 @@
</div> </div>
{% endif %} {% endif %}
{# ── Filtro rápido de assinatura ─────────────────────────────────── #}
<div class="d-inline-flex align-items-center ml-2" style="vertical-align:middle;">
<label class="mb-0 mr-1 font-weight-bold text-secondary" style="white-space:nowrap;font-size:.85rem;">
<i class="fas fa-signature mr-1"></i> Assinatura:
</label>
{% with status_atual=request.GET.status_assinatura %}
<select id="select-status-assinatura"
class="custom-select custom-select-sm"
style="min-width:210px;">
<option value="" {% if not status_atual %}selected{% endif %}>Todas as matérias</option>
<option value="pendente" {% if status_atual == 'pendente' %}selected{% endif %}>⚠ Pendente de Assinatura</option>
<option value="assinada" {% if status_atual == 'assinada' %}selected{% endif %}>✔ Assinada</option>
</select>
{% endwith %}
</div>
{# ─────────────────────────────────────────────────────────────────── #}
<div class="actions btn-group float-right pb-4" role="group"> <div class="actions btn-group float-right pb-4" role="group">
{% switch "SOLR_SWITCH" %} {% switch "SOLR_SWITCH" %}
<a href="{% url 'sapl.base:haystack_search' %}" class="btn btn-outline-primary"> <a href="{% url 'sapl.base:haystack_search' %}" class="btn btn-outline-primary">
@ -37,6 +54,7 @@
{% endblock %} {% endblock %}
{% block detail_content %} {% block detail_content %}
{% if not show_results %} {% if not show_results %}
{% crispy filter.form %} {% crispy filter.form %}
{% endif %} {% endif %}
@ -215,6 +233,19 @@
Texto Original Texto Original
</a> </a>
</strong> </strong>
&nbsp;
{% if m.pdf_assinado %}
<a href="{% url 'sapl.materia:materia_pdf_assinado' m.pk %}" target="_blank"
class="badge badge-success"
title="PDF assinado disponível — clique para baixar">
<i class="fas fa-signature"></i> Assinado{% if m.assinado_em %} em {{ m.assinado_em|date:"d/m/Y" }}{% endif %}
</a>
{% elif m.texto_original %}
<span class="badge badge-warning text-dark"
title="Documento ainda não possui assinatura digital">
<i class="fas fa-clock"></i> Pendente de Assinatura
</span>
{% endif %}
</br> </br>
{% endif %} {% endif %}
{% if m.texto_articulado.exists %}<strong><a href="{% url 'sapl.materia:materia_ta' m.id%}">Texto Articulado</a></strong></br>{% endif %} {% if m.texto_articulado.exists %}<strong><a href="{% url 'sapl.materia:materia_ta' m.id%}">Texto Articulado</a></strong></br>{% endif %}
@ -256,7 +287,7 @@
{% block extra_js %} {% block extra_js %}
<script src="{% static 'js/materia_pesquisa_download_pdfs.js' %}"></script> <script src="{% static 'js/materia_pesquisa_download_pdfs.js' %}"></script>
<script type="text/javascript" > <script type="text/javascript">
$( document ).ready(function() { $( document ).ready(function() {
$('.link_votacao_nominal').on('click', function(event) { $('.link_votacao_nominal').on('click', function(event) {
event.preventDefault(); event.preventDefault();
@ -265,7 +296,6 @@
function pesquisaAvancada(){ function pesquisaAvancada(){
$('.pesquisa_avancada').toggle(); $('.pesquisa_avancada').toggle();
var id_btn = "#btn_pesquisa_avancada_id"; var id_btn = "#btn_pesquisa_avancada_id";
if ($(id_btn).val().endsWith('>>>')){ if ($(id_btn).val().endsWith('>>>')){
$(id_btn).val($(id_btn).val().replace('>>>', '<<<')) $(id_btn).val($(id_btn).val().replace('>>>', '<<<'))
@ -276,7 +306,6 @@
function votacaoNominal(id){ function votacaoNominal(id){
$('#div_' + id).toggle(); $('#div_' + id).toggle();
var id_link = "#link_votacao_nominal_" + id; var id_link = "#link_votacao_nominal_" + id;
if ($(id_link).text().indexOf('>>>') > -1){ if ($(id_link).text().indexOf('>>>') > -1){
$(id_link).text($(id_link).text().replace('>>>', '<<<')) $(id_link).text($(id_link).text().replace('>>>', '<<<'))

119
sapl/templates/materia/materias_pendentes_assinatura_list.html

@ -0,0 +1,119 @@
{% extends "crud/detail.html" %}
{% load i18n %}
{% load static %}
{% block actions %}
<div class="actions btn-group float-right pb-4" role="group">
<a href="{{ url_pesquisa_completa }}" class="btn btn-outline-secondary">
<i class="fas fa-search"></i> {% trans "Ver na Pesquisa Completa" %}
</a>
<a href="{% url 'sapl.materia:pesquisar_materia' %}" class="btn btn-outline-primary">
<i class="fas fa-list"></i> {% trans "Pesquisar Matérias" %}
</a>
</div>
{% endblock %}
{% block detail_content %}
<div class="d-flex align-items-center justify-content-between mb-3 flex-wrap" style="gap:.75rem;">
<div>
<h4 class="mb-0">
<span class="badge badge-warning text-dark px-3 py-2" style="font-size:1rem;">
<i class="fas fa-clock mr-1"></i> Matérias Pendentes de Assinatura
</span>
</h4>
{% if autor %}
<small class="text-muted mt-1 d-block">
<i class="fas fa-user mr-1"></i> Autor: <strong>{{ autor }}</strong>
</small>
{% endif %}
</div>
<div>
{% if total == 0 %}
<span class="badge badge-success px-3 py-2" style="font-size:.95rem;">
<i class="fas fa-check-circle mr-1"></i> Nenhuma pendência
</span>
{% elif total == 1 %}
<span class="badge badge-warning text-dark px-3 py-2" style="font-size:.95rem;">
1 matéria pendente
</span>
{% else %}
<span class="badge badge-warning text-dark px-3 py-2" style="font-size:.95rem;">
{{ total }} matérias pendentes
</span>
{% endif %}
</div>
</div>
{% if not object_list %}
<div class="alert alert-success d-flex align-items-center" role="alert">
<i class="fas fa-check-circle fa-2x mr-3"></i>
<div>
<strong>Tudo em dia!</strong><br>
Não há matérias com documento pendente de assinatura digital.
</div>
</div>
{% else %}
<div class="alert alert-warning d-flex align-items-center mb-3" role="alert"
style="border-left: 5px solid #f0ad4e;">
<i class="fas fa-exclamation-triangle fa-lg mr-2"></i>
As matérias abaixo possuem <strong>&nbsp;Texto Original&nbsp;</strong> mas ainda
<strong>&nbsp;não foram assinadas digitalmente</strong>.
</div>
<table class="table table-hover table-bordered">
<thead class="thead-light">
<tr>
<th style="width:160px;">Matéria</th>
<th>Ementa</th>
<th style="width:130px;">Apresentação</th>
<th style="width:130px;">Situação</th>
<th style="width:110px;" class="text-center">Ações</th>
</tr>
</thead>
<tbody>
{% for m in object_list %}
<tr>
<td>
<a href="{% url 'sapl.materia:materialegislativa_detail' m.pk %}"
class="font-weight-bold">
{{ m.tipo.sigla }} {{ m.numero }}/{{ m.ano }}
</a>
<br>
<small class="text-muted">{{ m.tipo }}</small>
</td>
<td>
<span title="{{ m.ementa }}">
{{ m.ementa|truncatechars:120 }}
</span>
</td>
<td class="text-center">
{% if m.data_apresentacao %}
{{ m.data_apresentacao|date:"d/m/Y" }}
{% else %}
<span class="text-muted"></span>
{% endif %}
</td>
<td class="text-center">
{% if m.em_tramitacao %}
<span class="badge badge-info">Em Tramitação</span>
{% else %}
<span class="badge badge-secondary">Arquivada</span>
{% endif %}
</td>
<td class="text-center">
<a href="{% url 'sapl.materia:materialegislativa_detail' m.pk %}"
class="btn btn-sm btn-outline-primary"
title="Abrir matéria para assinar">
<i class="fas fa-signature"></i> Assinar
</a>
</td>
</tr>
{% endfor %}
</tbody>
</table>
{% include "paginacao.html" %}
{% endif %}
{% endblock detail_content %}

2
sapl/templates/navbar.yaml

@ -49,6 +49,8 @@
check_permission: materia.can_access_impressos {% comment %} FIXME transformar para checagens de menu_[funcionalidade]{% endcomment%} check_permission: materia.can_access_impressos {% comment %} FIXME transformar para checagens de menu_[funcionalidade]{% endcomment%}
- title: {% trans 'Matérias Legislativas' %} - title: {% trans 'Matérias Legislativas' %}
url: sapl.materia:pesquisar_materia url: sapl.materia:pesquisar_materia
- title: {% trans 'Matérias Pendentes de Assinatura' %}
url: sapl.materia:materias_pendentes_assinatura
- title: {% trans 'Pautas das Sessões' %} - title: {% trans 'Pautas das Sessões' %}
url: sapl.sessao:pauta_sessao url: sapl.sessao:pauta_sessao
- title: {% trans 'Proposições' %} - title: {% trans 'Proposições' %}

Loading…
Cancel
Save