Browse Source

Feat(Materia): Adicionar download PDF unificado e melhorar layout dos botões

Adiciona opção de baixar matéria + acessórios mesclados em um único PDF,
além do ZIP já existente. Reorganiza botões da página de detalhe com
flex-wrap responsivo e cores distintas para cada ação.
pull/3858/head
rangelbruno 7 months ago
committed by rangelbruno
parent
commit
ba4e7704a1
  1. 4
      sapl/materia/urls.py
  2. 77
      sapl/materia/views.py
  3. 19
      sapl/static/js/materia_pesquisa_download_pdfs.js
  4. 27
      sapl/templates/materia/materialegislativa_detail.html

4
sapl/materia/urls.py

@ -29,7 +29,7 @@ from sapl.materia.views import (AcompanhamentoConfirmarView,
RetornarProposicao,
MateriaPesquisaSimplesView,
DespachoInicialMultiCreateView,
get_zip_docacessorios, get_pdf_docacessorios, get_zip_completo,
get_zip_docacessorios, get_pdf_docacessorios, get_zip_completo, get_pdf_completo,
configEtiquetaMateriaLegislativaCrud,
PesquisarStatusTramitacaoView, HistoricoProposicaoView)
from sapl.materia.onlyoffice_views import (onlyoffice_config, onlyoffice_download,
@ -149,6 +149,8 @@ urlpatterns_materia = [
name='merge_docacessorios'),
url(r'^materia/zip-completo/(?P<pk>\d+)$', get_zip_completo,
name='zip_completo_materia'),
url(r'^materia/pdf-completo/(?P<pk>\d+)$', get_pdf_completo,
name='pdf_completo_materia'),
# OnlyOffice endpoints para Matéria Legislativa
url(r'^materia/(?P<pk>\d+)/onlyoffice/editor$', materia_onlyoffice_editor,

77
sapl/materia/views.py

@ -3343,6 +3343,83 @@ def get_zip_completo(request, pk):
return response
def create_pdf_completo(materia):
"""
Creates a unified in memory PDF with materia PDF + all documentos acessórios
"""
logger = logging.getLogger(__name__)
pdf_files = []
# PDF da matéria: prefere pdf_assinado, fallback texto_original
if materia.pdf_assinado:
pdf_files.append(os.path.join(MEDIA_ROOT, str(materia.pdf_assinado)))
elif materia.texto_original:
f = os.path.join(MEDIA_ROOT, str(materia.texto_original))
if f.lower().endswith('pdf'):
pdf_files.append(f)
# Documentos acessórios: prefere pdf_assinado, fallback arquivo (somente PDF)
for doc in materia.documentoacessorio_set.all():
if doc.pdf_assinado:
pdf_files.append(os.path.join(MEDIA_ROOT, str(doc.pdf_assinado)))
elif doc.arquivo:
f = os.path.join(MEDIA_ROOT, str(doc.arquivo))
if f.lower().endswith('pdf'):
pdf_files.append(f)
# Filtra apenas arquivos que existem no disco
pdf_files = [f for f in pdf_files if os.path.exists(f)]
if not pdf_files:
raise FileNotFoundError(
"Nenhum arquivo PDF encontrado para a matéria.")
logger.info(
"Gerando PDF completo da matéria com {} arquivos: {}".format(
len(pdf_files), pdf_files))
merger = PdfFileMerger(strict=False)
for f in pdf_files:
merger.append(fileobj=f)
data = BytesIO()
merger.write(data)
merger.close()
external_name = "mat_{}_{}_completo.pdf".format(
materia.numero, materia.ano)
return external_name, data.getvalue()
def get_pdf_completo(request, pk):
logger = logging.getLogger(__name__)
username = 'Usuário anônimo' if request.user.is_anonymous else request.user.username
materia = get_object_or_404(MateriaLegislativa, pk=pk)
try:
external_name, data = create_pdf_completo(materia)
logger.info(
"user= {}. Gerou o PDF completo da matéria {}".format(username, pk))
except FileNotFoundError:
logger.error("user= {}.Arquivos PDF não encontrados para matéria {}".format(username, pk))
msg = _('Não há arquivos PDF disponíveis para download nesta matéria.')
messages.add_message(request, messages.ERROR, msg)
return redirect(reverse('sapl.materia:materialegislativa_detail',
kwargs={'pk': pk}))
except Exception as e:
logger.error("user={}. Erro ao criar PDF completo da matéria {}: {}"
.format(username, pk, str(e)))
msg = _('Um erro inesperado ocorreu. Entre em contato com o suporte do SGVP.')
messages.add_message(request, messages.ERROR, msg)
return redirect(reverse('sapl.materia:materialegislativa_detail',
kwargs={'pk': pk}))
response = HttpResponse(data, content_type='application/pdf')
response['Content-Disposition'] = ('attachment; filename="%s"'
% external_name)
return response
def create_pdf_docacessorios(materia):
"""
Creates a unified in memory PDF file

19
sapl/static/js/materia_pesquisa_download_pdfs.js

@ -167,17 +167,28 @@
strong.parentNode.insertBefore(document.createTextNode(' '), strong.nextSibling);
strong.parentNode.insertBefore(btn, strong.nextSibling);
// Botão "Baixar Todos" (ZIP com matéria + acessórios)
// Botões "Baixar Todos" quando há documentos acessórios
const materiaPk = link.getAttribute('data-materia-pk');
if (materiaPk) {
// Botão PDF unificado
const pdfBtn = document.createElement('a');
pdfBtn.setAttribute('data-sapl-download-all-btn', '1');
pdfBtn.className = 'btn btn-sm btn-info ml-1';
pdfBtn.title = 'Baixar todos em PDF único';
pdfBtn.href = '/materia/pdf-completo/' + materiaPk;
pdfBtn.innerHTML = '<i class="fas fa-file-pdf"></i>';
strong.parentNode.insertBefore(document.createTextNode(' '), btn.nextSibling);
strong.parentNode.insertBefore(pdfBtn, btn.nextSibling.nextSibling);
// Botão ZIP
const zipBtn = document.createElement('a');
zipBtn.setAttribute('data-sapl-download-all-btn', '1');
zipBtn.className = 'btn btn-sm btn-info ml-1';
zipBtn.title = 'Baixar todos os documentos (ZIP)';
zipBtn.title = 'Baixar todos em ZIP';
zipBtn.href = '/materia/zip-completo/' + materiaPk;
zipBtn.innerHTML = '<i class="fas fa-file-archive"></i>';
strong.parentNode.insertBefore(document.createTextNode(' '), btn.nextSibling);
strong.parentNode.insertBefore(zipBtn, btn.nextSibling.nextSibling);
strong.parentNode.insertBefore(document.createTextNode(' '), pdfBtn.nextSibling);
strong.parentNode.insertBefore(zipBtn, pdfBtn.nextSibling.nextSibling);
}
});
}

27
sapl/templates/materia/materialegislativa_detail.html

@ -3,38 +3,37 @@
{% block sub_actions %}
{{ block.super }}
<div class="d-flex flex-wrap align-items-center mt-1" style="gap: 0.35rem;">
{% if object.em_tramitacao and mail_service_configured and google_recaptcha_configured %}
<div class="actions btn-group btn-group-sm" role="group">
<a href="{% url 'sapl.materia:acompanhar_materia' object.id %}" class="btn btn-outline-primary">Acompanhar Matéria</a>
</div>
<a href="{% url 'sapl.materia:acompanhar_materia' object.id %}" class="btn btn-sm btn-outline-primary">
<i class="fa fa-bell"></i> {% trans "Acompanhar Matéria" %}
</a>
{% endif %}
{% if is_autor and not object.numero_protocolo or can_edit_materia %}
<div class="actions btn-group btn-group-sm" role="group">
<a class="btn btn-primary" href="{% url 'sapl.materia:materia_onlyoffice_editor' object.pk %}">
<a class="btn btn-sm btn-primary" href="{% url 'sapl.materia:materia_onlyoffice_editor' object.pk %}">
<i class="fa fa-file-word-o"></i> {% trans "Editar com OnlyOffice" %}
</a>
</div>
{% endif %}
{% if object.numero_protocolo and object.texto_original %}
<div class="actions btn-group btn-group-sm" role="group">
{% if object.pdf_assinado %}
<a class="btn btn-success" href="{% url 'sapl.materia:materia_pdf_assinado' object.pk %}" target="_blank">
<a class="btn btn-sm btn-success" href="{% url 'sapl.materia:materia_pdf_assinado' object.pk %}" target="_blank">
<i class="fa fa-check-circle"></i> {% trans "Ver PDF Assinado" %}
</a>
{% elif is_autor %}
<button type="button" class="btn btn-warning" data-toggle="modal" data-target="#assinaturaModal">
<button type="button" class="btn btn-sm btn-warning" data-toggle="modal" data-target="#assinaturaModal">
<i class="fa fa-certificate"></i> {% trans "Assinar PDF Digitalmente" %}
</button>
{% endif %}
</div>
{% endif %}
{% if object.documentoacessorio_set.all.exists %}
<div class="actions btn-group btn-group-sm" role="group">
<a class="btn btn-info" href="{% url 'sapl.materia:zip_completo_materia' object.pk %}">
<i class="fa fa-file-archive-o"></i> {% trans "Baixar Todos os Documentos" %}
<a class="btn btn-sm btn-danger" href="{% url 'sapl.materia:pdf_completo_materia' object.pk %}" title="{% trans 'Mescla matéria e acessórios em um único PDF' %}">
<i class="fa fa-file-pdf-o"></i> {% trans "Todos em PDF" %}
</a>
<a class="btn btn-sm btn-secondary" href="{% url 'sapl.materia:zip_completo_materia' object.pk %}" title="{% trans 'Baixa matéria e acessórios em arquivos separados (ZIP)' %}">
<i class="fa fa-file-archive-o"></i> {% trans "Todos em ZIP" %}
</a>
</div>
{% endif %}
</div>
{% endblock sub_actions %}
{% block editions %}

Loading…
Cancel
Save