Browse Source

Feat (Assinatura digital): Como cliente, quero poder imprimir diversos documentos de uma única vez [AB#1325]

pull/3858/head
KemuelAlves 4 months ago
parent
commit
425686ce0d
  1. 207
      sapl/materia/onlyoffice_materia_views.py
  2. 10
      sapl/materia/urls.py
  3. 166
      sapl/templates/materia/assinatura_modal.html
  4. 8
      sapl/templates/materia/materialegislativa_detail.html
  5. 114
      sapl/templates/materia/materialegislativa_filter.html

207
sapl/materia/onlyoffice_materia_views.py

@ -821,3 +821,210 @@ def materia_gerar_pdf_assinatura(request, pk):
logger.error(f"Erro inesperado na geração de PDF: {e}") logger.error(f"Erro inesperado na geração de PDF: {e}")
messages.error(request, 'Erro inesperado ao gerar o PDF.') messages.error(request, 'Erro inesperado ao gerar o PDF.')
return redirect('sapl.materia:materialegislativa_detail', pk=pk) return redirect('sapl.materia:materialegislativa_detail', pk=pk)
# ============================================================
# Prévia de PDF para Documento Acessório
# ============================================================
# ============================================================
# Prévia de PDF para Documento Acessório
# ============================================================
@login_required
@require_http_methods(["GET"])
def materia_gerar_pdf_previa(request, pk):
"""
Gera o PDF da matéria para prévia antes da assinatura.
Diferente do materia_gerar_pdf_assinatura, não exige numero_protocolo.
Se for PDF, retorna direto. Caso seja DOCX, converte via OnlyOffice.
"""
import requests as http_requests
import xml.etree.ElementTree as ET
materia = get_object_or_404(MateriaLegislativa, pk=pk)
if not materia.texto_original:
return HttpResponse('Matéria não possui documento de texto original.', status=404)
file_name = materia.texto_original.name.lower()
if file_name.endswith('.pdf'):
try:
with open(materia.texto_original.path, 'rb') as f:
content = f.read()
response = HttpResponse(content, content_type='application/pdf')
response['Content-Disposition'] = (
f'inline; filename="Materia_{materia.tipo}_{materia.numero}_{materia.ano}.pdf"'
)
return response
except Exception as e:
logger.error(f"Erro ao ler PDF da materia {pk}: {e}")
return HttpResponse('Erro ao ler o arquivo PDF.', status=500)
download_url = build_onlyoffice_url(
request,
reverse('sapl.materia:materia_onlyoffice_download', kwargs={'pk': pk})
)
conversion_url = f'{settings.ONLYOFFICE_URL}/ConvertService.ashx'
conversion_data = {
"async": False,
"filetype": "docx",
"key": generate_file_key("materia_previa", pk, request.user.pk),
"outputtype": "pdf",
"title": f"Materia_{materia.tipo}_{materia.numero}_{materia.ano}.pdf",
"url": download_url,
}
if getattr(settings, 'ONLYOFFICE_JWT_ENABLED', False) and getattr(settings, 'ONLYOFFICE_JWT_SECRET', None):
import jwt
token = jwt.encode(conversion_data, settings.ONLYOFFICE_JWT_SECRET, algorithm='HS256')
conversion_data['token'] = token
try:
headers = {'Content-Type': 'application/json'}
if getattr(settings, 'ONLYOFFICE_JWT_ENABLED', False) and getattr(settings, 'ONLYOFFICE_JWT_SECRET', None):
import jwt
header_token = jwt.encode({"payload": conversion_data}, settings.ONLYOFFICE_JWT_SECRET, algorithm='HS256')
headers['Authorization'] = f'Bearer {header_token}'
conversion_response = http_requests.post(
conversion_url,
json=conversion_data,
headers=headers,
timeout=60
)
if conversion_response.status_code != 200:
return HttpResponse('Erro ao converter o documento.', status=500)
try:
root = ET.fromstring(conversion_response.text)
except ET.ParseError as e:
logger.error(f"Erro ao parsear resposta XML (materia previa): {e}")
return HttpResponse('Erro ao processar resposta do serviço.', status=500)
error_elem = root.find('Error')
if error_elem is not None:
return HttpResponse(f'Erro na conversão: {error_elem.text}', status=500)
file_url_elem = root.find('FileUrl')
if file_url_elem is None or not file_url_elem.text:
return HttpResponse('URL do PDF não retornada.', status=500)
pdf_response = http_requests.get(file_url_elem.text, timeout=60)
if pdf_response.status_code != 200:
return HttpResponse('Erro ao baixar PDF convertido.', status=500)
filename = f"Materia_{materia.tipo}_{materia.numero}_{materia.ano}.pdf"
filename = filename.replace(' ', '_').replace('/', '-')
response = HttpResponse(pdf_response.content, content_type='application/pdf')
response['Content-Disposition'] = f'inline; filename="{filename}"'
return response
except http_requests.exceptions.Timeout:
logger.error(f"Timeout na conversão OnlyOffice (materia previa pk={pk})")
return HttpResponse('Tempo limite excedido ao converter o documento.', status=504)
except http_requests.exceptions.ConnectionError:
logger.error(f"Erro de conexão com OnlyOffice (materia previa pk={pk})")
return HttpResponse('Não foi possível conectar ao serviço de conversão.', status=502)
except Exception as e:
logger.error(f"Erro inesperado na previa PDF (materia pk={pk}): {e}")
return HttpResponse('Erro inesperado ao gerar o PDF.', status=500)
@login_required
@require_http_methods(["GET"])
def docacessorio_gerar_pdf_previa(request, pk):
"""
Gera o PDF do documento acessório para prévia antes da assinatura.
Se for PDF, retorna direto. Caso seja DOCX, converte via OnlyOffice.
"""
import requests as http_requests
import xml.etree.ElementTree as ET
docacessorio = get_object_or_404(DocumentoAcessorio, pk=pk)
if not docacessorio.arquivo:
return HttpResponse('Documento sem arquivo.', status=404)
file_name = docacessorio.arquivo.name.lower()
if file_name.endswith('.pdf'):
try:
with open(docacessorio.arquivo.path, 'rb') as f:
content = f.read()
response = HttpResponse(content, content_type='application/pdf')
response['Content-Disposition'] = f'inline; filename="DocAcessorio_{pk}.pdf"'
return response
except Exception as e:
logger.error(f"Erro ao ler arquivo PDF do doc acessorio: {e}")
return HttpResponse('Erro ao ler o arquivo PDF.', status=500)
download_url = build_onlyoffice_url(
request,
reverse('sapl.materia:docacessorio_onlyoffice_download', kwargs={'pk': pk})
)
conversion_url = f'{settings.ONLYOFFICE_URL}/ConvertService.ashx'
conversion_data = {
"async": False,
"filetype": "docx",
"key": generate_file_key("docacessorio_pdf", pk, request.user.pk),
"outputtype": "pdf",
"title": f"DocAcessorio_{pk}.pdf",
"url": download_url,
}
if getattr(settings, 'ONLYOFFICE_JWT_ENABLED', False) and getattr(settings, 'ONLYOFFICE_JWT_SECRET', None):
import jwt
token = jwt.encode(conversion_data, settings.ONLYOFFICE_JWT_SECRET, algorithm='HS256')
conversion_data['token'] = token
try:
headers = {'Content-Type': 'application/json'}
if getattr(settings, 'ONLYOFFICE_JWT_ENABLED', False) and getattr(settings, 'ONLYOFFICE_JWT_SECRET', None):
import jwt
header_token = jwt.encode({"payload": conversion_data}, settings.ONLYOFFICE_JWT_SECRET, algorithm='HS256')
headers['Authorization'] = f'Bearer {header_token}'
conversion_response = http_requests.post(
conversion_url,
json=conversion_data,
headers=headers,
timeout=60
)
if conversion_response.status_code != 200:
return HttpResponse('Erro ao converter o documento.', status=500)
try:
root = ET.fromstring(conversion_response.text)
except ET.ParseError as e:
logger.error(f"Erro ao parsear resposta XML: {e}")
return HttpResponse('Erro ao processar resposta do serviço.', status=500)
error_elem = root.find('Error')
if error_elem is not None:
return HttpResponse(f'Erro na conversão: {error_elem.text}', status=500)
file_url_elem = root.find('FileUrl')
if file_url_elem is None or not file_url_elem.text:
return HttpResponse('URL do PDF não retornada.', status=500)
pdf_response = http_requests.get(file_url_elem.text, timeout=60)
if pdf_response.status_code != 200:
return HttpResponse('Erro ao baixar PDF convertido.', status=500)
response = HttpResponse(pdf_response.content, content_type='application/pdf')
response['Content-Disposition'] = f'inline; filename="DocAcessorio_{pk}.pdf"'
return response
except Exception as e:
logger.error(f"Erro inesperado na previa PDF (docacessorio): {e}")
return HttpResponse('Erro inesperado ao gerar o PDF.', status=500)

10
sapl/materia/urls.py

@ -47,7 +47,7 @@ from sapl.materia.onlyoffice_materia_views import (
docacessorio_onlyoffice_editor, docacessorio_onlyoffice_config, docacessorio_onlyoffice_editor, docacessorio_onlyoffice_config,
docacessorio_onlyoffice_download, docacessorio_onlyoffice_callback, docacessorio_onlyoffice_download, docacessorio_onlyoffice_callback,
docacessorio_check_doc, docacessorio_forcesave, docacessorio_check_doc, docacessorio_forcesave,
materia_gerar_pdf_assinatura materia_gerar_pdf_assinatura, materia_gerar_pdf_previa, docacessorio_gerar_pdf_previa
) )
from sapl.materia.views_assinatura import ( from sapl.materia.views_assinatura import (
materia_assinar_a1, materia_assinar_a3_preparar, materia_assinar_a3_finalizar, materia_assinar_a1, materia_assinar_a3_preparar, materia_assinar_a3_finalizar,
@ -180,6 +180,10 @@ urlpatterns_materia = [
url(r'^materia/(?P<pk>\d+)/pdf-assinatura$', materia_gerar_pdf_assinatura, url(r'^materia/(?P<pk>\d+)/pdf-assinatura$', materia_gerar_pdf_assinatura,
name='materia_pdf_assinatura'), name='materia_pdf_assinatura'),
# Prévia de PDF da Matéria antes da assinatura (sem restrição de protocolo)
url(r'^materia/(?P<pk>\d+)/pdf-previa$', materia_gerar_pdf_previa,
name='materia_pdf_previa'),
# Assinatura Digital de Matéria Legislativa # Assinatura Digital de Matéria Legislativa
url(r'^materia/(?P<pk>\d+)/assinar/a1/$', materia_assinar_a1, url(r'^materia/(?P<pk>\d+)/assinar/a1/$', materia_assinar_a1,
name='materia_assinar_a1'), name='materia_assinar_a1'),
@ -216,6 +220,10 @@ urlpatterns_materia = [
url(r'^materia/documentoacessorio/(?P<pk>\d+)/forcesave$', docacessorio_forcesave, url(r'^materia/documentoacessorio/(?P<pk>\d+)/forcesave$', docacessorio_forcesave,
name='docacessorio_forcesave'), name='docacessorio_forcesave'),
# Prévia de PDF do Documento Acessório antes da assinatura
url(r'^materia/documentoacessorio/(?P<pk>\d+)/pdf-previa$', docacessorio_gerar_pdf_previa,
name='docacessorio_pdf_previa'),
# Assinatura Digital de Documento Acessório # Assinatura Digital de Documento Acessório
url(r'^materia/documentoacessorio/(?P<pk>\d+)/assinar/a1/$', docacessorio_assinar_a1, url(r'^materia/documentoacessorio/(?P<pk>\d+)/assinar/a1/$', docacessorio_assinar_a1,
name='docacessorio_assinar_a1'), name='docacessorio_assinar_a1'),

166
sapl/templates/materia/assinatura_modal.html

@ -1,5 +1,52 @@
{% load i18n %} {% load i18n %}
<!-- Modal de Prévia do Documento -->
<div class="modal fade" id="previaDocumentoModal" tabindex="-1" role="dialog" aria-labelledby="previaDocumentoModalLabel" aria-hidden="true" style="z-index: 1060;">
<div class="modal-dialog modal-xl" role="document" style="max-width: 90vw;">
<div class="modal-content" style="height: 90vh;">
<div class="modal-header bg-warning text-dark">
<h5 class="modal-title" id="previaDocumentoModalLabel">
<i class="fa fa-eye"></i> {% trans "Prévia do Documento — Confirme antes de assinar" %}
</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body p-0" style="flex: 1; display: flex; flex-direction: column; overflow: hidden;">
<div id="previa-loading" class="text-center py-5">
<i class="fa fa-spinner fa-spin fa-3x text-primary mb-3"></i>
<h5>{% trans "Gerando prévia do documento..." %}</h5>
<p class="text-muted">{% trans "Aguarde enquanto o documento é convertido para visualização." %}</p>
</div>
<div id="previa-erro" class="p-4" style="display: none;">
<div class="alert alert-warning">
<i class="fa fa-exclamation-triangle"></i>
<strong>{% trans "Não foi possível gerar a prévia do documento." %}</strong>
<p class="mb-0 mt-2" id="previa-erro-msg">{% trans "Verifique se o documento está disponível." %}</p>
</div>
<p>{% trans "Você pode prosseguir com a assinatura mesmo sem a prévia." %}</p>
</div>
<iframe id="previa-iframe"
src=""
style="display: none; width: 100%; flex: 1; border: none; min-height: 0;"
title="Prévia do documento"></iframe>
</div>
<div class="modal-footer bg-light">
<div class="alert alert-info w-100 mb-2 py-2">
<i class="fa fa-info-circle"></i>
{% trans "Revise o documento acima. A assinatura digital tem validade jurídica e não pode ser desfeita sem permissão especial." %}
</div>
<button type="button" class="btn btn-secondary" data-dismiss="modal" id="btn-cancelar-previa">
<i class="fa fa-times"></i> {% trans "Cancelar — Fazer ajustes" %}
</button>
<button type="button" class="btn btn-success" id="btn-confirmar-assinatura" disabled>
<i class="fa fa-check-circle"></i> {% trans "Documento OK — Prosseguir com Assinatura" %}
</button>
</div>
</div>
</div>
</div>
<!-- Modal de Assinatura Digital --> <!-- Modal de Assinatura Digital -->
<div class="modal fade" id="assinaturaModal" tabindex="-1" role="dialog" aria-labelledby="assinaturaModalLabel" aria-hidden="true"> <div class="modal fade" id="assinaturaModal" tabindex="-1" role="dialog" aria-labelledby="assinaturaModalLabel" aria-hidden="true">
<div class="modal-dialog modal-lg" role="document"> <div class="modal-dialog modal-lg" role="document">
@ -253,6 +300,7 @@ document.addEventListener('DOMContentLoaded', function() {
init: function(materiaId, urlPrefix) { init: function(materiaId, urlPrefix) {
this.materiaId = materiaId; this.materiaId = materiaId;
this.urlPrefix = urlPrefix || ('/materia/' + materiaId); this.urlPrefix = urlPrefix || ('/materia/' + materiaId);
this._pdfPreviaUrl = this.urlPrefix + '/pdf-previa';
this.bindEvents(); this.bindEvents();
}, },
@ -273,11 +321,28 @@ document.addEventListener('DOMContentLoaded', function() {
}); });
}); });
// Botão assinar // Botão assinar → abre prévia primeiro
document.getElementById('btn-assinar').addEventListener('click', function() { document.getElementById('btn-assinar').addEventListener('click', function() {
self.mostrarPrevia();
});
// Confirmação na prévia → executa assinatura
document.getElementById('btn-confirmar-assinatura').addEventListener('click', function() {
$('#previaDocumentoModal').modal('hide');
self.assinar(); self.assinar();
}); });
// Habilitar botão de confirmação após alguns segundos (forçar leitura)
$('#previaDocumentoModal').on('shown.bs.modal', function() {
var btn = document.getElementById('btn-confirmar-assinatura');
btn.disabled = true;
btn.innerHTML = '<i class="fa fa-hourglass-half"></i> {% trans "Aguarde para confirmar..." %}';
setTimeout(function() {
btn.disabled = false;
btn.innerHTML = '<i class="fa fa-check-circle"></i> {% trans "Documento OK — Prosseguir com Assinatura" %}';
}, 4000);
});
// Tentar novamente A3 // Tentar novamente A3
var btnTentarNovamente = document.querySelector('.btn-tentar-novamente-a3'); var btnTentarNovamente = document.querySelector('.btn-tentar-novamente-a3');
if (btnTentarNovamente) { if (btnTentarNovamente) {
@ -378,6 +443,70 @@ document.addEventListener('DOMContentLoaded', function() {
document.getElementById('a3-nao-detectado').style.display = 'block'; document.getElementById('a3-nao-detectado').style.display = 'block';
}, },
mostrarPrevia: function() {
var self = this;
// Valida campos antes de abrir a prévia
if (this.tipoSelecionado === 'A1') {
var certificado = document.getElementById('certificado-a1').files[0];
var senha = document.getElementById('senha-a1').value;
if (!certificado) { alert('{% trans "Selecione o arquivo do certificado." %}'); return; }
if (!senha) { alert('{% trans "Digite a senha do certificado." %}'); return; }
}
// Reset prévia
document.getElementById('previa-loading').style.display = 'block';
document.getElementById('previa-erro').style.display = 'none';
var iframe = document.getElementById('previa-iframe');
iframe.style.display = 'none';
iframe.src = '';
document.getElementById('btn-confirmar-assinatura').disabled = true;
// Abre modal de prévia
$('#previaDocumentoModal').modal('show');
// Carrega PDF na iframe
var pdfUrl = this._pdfPreviaUrl;
iframe.onload = function() {
document.getElementById('previa-loading').style.display = 'none';
// Verifica se carregou com sucesso (se a URL retornou 200)
try {
// tenta acessar o conteúdo — se for PDF embutido, não há acesso ao contentDocument
document.getElementById('previa-iframe').style.display = 'block';
} catch(e) {
document.getElementById('previa-iframe').style.display = 'block';
}
};
iframe.onerror = function() {
document.getElementById('previa-loading').style.display = 'none';
document.getElementById('previa-erro').style.display = 'block';
document.getElementById('previa-erro-msg').textContent =
'{% trans "Erro ao carregar o documento." %}';
document.getElementById('btn-confirmar-assinatura').disabled = false;
document.getElementById('btn-confirmar-assinatura').innerHTML =
'<i class="fa fa-check-circle"></i> {% trans "Prosseguir com Assinatura" %}';
};
// Usa fetch para verificar disponibilidade antes de colocar no iframe
fetch(pdfUrl, { method: 'GET', credentials: 'same-origin' })
.then(function(resp) {
if (!resp.ok) { throw new Error('status ' + resp.status); }
document.getElementById('previa-loading').style.display = 'none';
iframe.src = pdfUrl;
iframe.style.display = 'block';
})
.catch(function(err) {
document.getElementById('previa-loading').style.display = 'none';
document.getElementById('previa-erro').style.display = 'block';
document.getElementById('previa-erro-msg').textContent =
'{% trans "Não foi possível gerar a prévia" %}: ' + err.message;
// Permite assinar mesmo sem prévia
document.getElementById('btn-confirmar-assinatura').disabled = false;
document.getElementById('btn-confirmar-assinatura').innerHTML =
'<i class="fa fa-check-circle"></i> {% trans "Prosseguir com Assinatura" %}';
});
},
assinar: function() { assinar: function() {
if (this.tipoSelecionado === 'A1') { if (this.tipoSelecionado === 'A1') {
this.assinarA1(); this.assinarA1();
@ -487,6 +616,14 @@ document.addEventListener('DOMContentLoaded', function() {
this.tipoSelecionado = null; this.tipoSelecionado = null;
this.a3AppInfo = null; this.a3AppInfo = null;
// Fecha prévia se estiver aberta
$('#previaDocumentoModal').modal('hide');
var iframe = document.getElementById('previa-iframe');
iframe.src = '';
iframe.style.display = 'none';
document.getElementById('previa-loading').style.display = 'block';
document.getElementById('previa-erro').style.display = 'none';
document.getElementById('selecao-tipo-certificado').style.display = 'block'; document.getElementById('selecao-tipo-certificado').style.display = 'block';
document.getElementById('formulario-a1').style.display = 'none'; document.getElementById('formulario-a1').style.display = 'none';
document.getElementById('formulario-a3').style.display = 'none'; document.getElementById('formulario-a3').style.display = 'none';
@ -552,4 +689,31 @@ document.addEventListener('DOMContentLoaded', function() {
#a3-cert-info { #a3-cert-info {
background-color: #f8f9fa; background-color: #f8f9fa;
} }
/* Prévia do documento */
#previaDocumentoModal .modal-content {
display: flex;
flex-direction: column;
}
#previaDocumentoModal .modal-body {
flex: 1;
overflow: hidden;
display: flex;
flex-direction: column;
}
#previaDocumentoModal .modal-footer {
flex-direction: column;
align-items: stretch;
}
#previaDocumentoModal .modal-footer .alert {
font-size: 0.875rem;
}
#btn-confirmar-assinatura {
font-size: 1.05rem;
padding: 0.6rem 1.5rem;
}
</style> </style>

8
sapl/templates/materia/materialegislativa_detail.html

@ -25,13 +25,13 @@
<i class="fa fa-check-circle"></i> {% trans "Ver PDF Assinado" %} <i class="fa fa-check-circle"></i> {% trans "Ver PDF Assinado" %}
</a> </a>
{% endif %} {% endif %}
{% if is_autor and not ja_assinou %} {% if is_autor or can_edit_materia %}
{% if not ja_assinou and object.texto_original %}
<button type="button" class="btn btn-warning" data-toggle="modal" data-target="#assinaturaModal"> <button type="button" class="btn btn-warning" data-toggle="modal" data-target="#assinaturaModal">
<i class="fa fa-certificate"></i> {% trans "Assinar PDF Digitalmente" %} <i class="fa fa-certificate"></i> {% trans "Assinar PDF Digitalmente" %}
</button> </button>
{% endif %} {% endif %}
{% endif %} {% endif %}
{% if object.documentoacessorio_set.all.exists %}
<a class="btn btn-danger" href="{% url 'sapl.materia:pdf_completo_materia' object.pk %}" title="{% trans 'Mescla matéria e acessórios em um único PDF' %}"> <a class="btn 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" %} <i class="fa fa-file-pdf-o"></i> {% trans "Todos em PDF" %}
</a> </a>
@ -247,7 +247,8 @@
{% block extra_js %} {% block extra_js %}
{{ block.super }} {{ block.super }}
{% if object.numero_protocolo and object.texto_original and is_autor and not ja_assinou %} {% if object.texto_original and not ja_assinou %}
{% if is_autor or can_edit_materia %}
{% include "materia/assinatura_modal.html" %} {% include "materia/assinatura_modal.html" %}
<script> <script>
document.addEventListener('DOMContentLoaded', function() { document.addEventListener('DOMContentLoaded', function() {
@ -256,6 +257,7 @@
} }
}); });
</script> </script>
{% endif %}
{% endif %} {% endif %}
{% if pode_remover_assinatura and object.pdf_assinado %} {% if pode_remover_assinatura and object.pdf_assinado %}
{% include "materia/remover_assinatura_modal.html" %} {% include "materia/remover_assinatura_modal.html" %}

114
sapl/templates/materia/materialegislativa_filter.html

@ -381,6 +381,28 @@
</form> </form>
</div> </div>
{# ── Passo 1.5: prévia dos documentos ── #}
<div id="lote-previa-step" style="display:none;">
<div class="alert alert-info mb-3">
<i class="fa fa-eye"></i>
<strong>Revise os documentos antes de assinar.</strong>
Clique em cada matéria para visualizar o PDF em uma nova aba.
A assinatura digital tem validade jurídica e não pode ser desfeita sem permissão especial.
</div>
<div id="lote-previa-lista" style="max-height:300px;overflow-y:auto;border:1px solid #dee2e6;border-radius:4px;padding:8px;" class="mb-3">
{# Preenchido via JS com as matérias selecionadas + link para PDF #}
</div>
<div class="form-check mt-3">
<input class="form-check-input" type="checkbox" id="lote-previa-confirmacao">
<label class="form-check-label font-weight-bold text-danger" for="lote-previa-confirmacao">
<i class="fa fa-check-square-o"></i>
Confirmo que revisei os documentos e que estão corretos para assinatura.
</label>
</div>
</div>
{# ── Passo 2: progresso ── #} {# ── Passo 2: progresso ── #}
<div id="lote-progresso-step" style="display:none;"> <div id="lote-progresso-step" style="display:none;">
<h6 class="mb-3"> <h6 class="mb-3">
@ -413,11 +435,18 @@
<i class="fa fa-arrow-right"></i> <i class="fa fa-arrow-right"></i>
Continuar — <span id="lote-btn-proximo-contagem">{{ materias_pendentes_lote|length }}</span> Matéria(s) Continuar — <span id="lote-btn-proximo-contagem">{{ materias_pendentes_lote|length }}</span> Matéria(s)
</button> </button>
{# Passo 1: voltar / assinar #} {# Passo 1 → 1.5 #}
<button type="button" class="btn btn-outline-secondary" id="lote-btn-voltar" style="display:none;"> <button type="button" class="btn btn-outline-secondary" id="lote-btn-voltar" style="display:none;">
<i class="fa fa-arrow-left"></i> Voltar <i class="fa fa-arrow-left"></i> Voltar
</button> </button>
<button type="button" class="btn btn-warning" id="lote-btn-assinar" style="display:none;"> <button type="button" class="btn btn-info" id="lote-btn-previa" style="display:none;">
<i class="fa fa-eye"></i> Visualizar Documentos
</button>
{# Passo 1.5 → 2 #}
<button type="button" class="btn btn-outline-secondary" id="lote-btn-voltar-form" style="display:none;">
<i class="fa fa-arrow-left"></i> Voltar
</button>
<button type="button" class="btn btn-warning" id="lote-btn-assinar" style="display:none;" disabled>
<i class="fa fa-certificate"></i> <i class="fa fa-certificate"></i>
Assinar <span id="lote-btn-assinar-contagem">{{ materias_pendentes_lote|length }}</span> Matéria(s) Assinar <span id="lote-btn-assinar-contagem">{{ materias_pendentes_lote|length }}</span> Matéria(s)
</button> </button>
@ -492,15 +521,73 @@
function irParaSelecao() { function irParaSelecao() {
document.getElementById('lote-selecao-step').style.display = 'block'; document.getElementById('lote-selecao-step').style.display = 'block';
document.getElementById('lote-form-step').style.display = 'none'; document.getElementById('lote-form-step').style.display = 'none';
document.getElementById('lote-previa-step').style.display = 'none';
document.getElementById('lote-progresso-step').style.display = 'none'; document.getElementById('lote-progresso-step').style.display = 'none';
document.getElementById('lote-resumo-step').style.display = 'none'; document.getElementById('lote-resumo-step').style.display = 'none';
document.getElementById('lote-btn-proximo').style.display = 'inline-block'; document.getElementById('lote-btn-proximo').style.display = 'inline-block';
document.getElementById('lote-btn-voltar').style.display = 'none'; document.getElementById('lote-btn-voltar').style.display = 'none';
document.getElementById('lote-btn-previa').style.display = 'none';
document.getElementById('lote-btn-voltar-form').style.display= 'none';
document.getElementById('lote-btn-assinar').style.display = 'none'; document.getElementById('lote-btn-assinar').style.display = 'none';
document.getElementById('lote-btn-recarregar').style.display = 'none'; document.getElementById('lote-btn-recarregar').style.display = 'none';
document.getElementById('lote-btn-fechar').disabled = false; document.getElementById('lote-btn-fechar').disabled = false;
} }
function irParaForm() {
document.getElementById('lote-selecao-step').style.display = 'none';
document.getElementById('lote-form-step').style.display = 'block';
document.getElementById('lote-previa-step').style.display = 'none';
document.getElementById('lote-progresso-step').style.display = 'none';
document.getElementById('lote-resumo-step').style.display = 'none';
document.getElementById('lote-btn-proximo').style.display = 'none';
document.getElementById('lote-btn-voltar').style.display = 'inline-block';
document.getElementById('lote-btn-previa').style.display = 'inline-block';
document.getElementById('lote-btn-voltar-form').style.display= 'none';
document.getElementById('lote-btn-assinar').style.display = 'none';
document.getElementById('lote-btn-recarregar').style.display = 'none';
}
function irParaPrevia() {
var certFile = document.getElementById('lote-certificado').files[0];
var senha = document.getElementById('lote-senha').value;
if (!certFile) { alert('Selecione o arquivo do certificado.'); return; }
if (!senha) { alert('Digite a senha do certificado.'); return; }
var idsSel = getIdsSelecionados();
// Monta lista de matérias com link para prévia do PDF
var html = '';
MATERIAS_LOTE.filter(function(m){ return idsSel.indexOf(m.id) !== -1; })
.forEach(function(m) {
html += '<div class="d-flex align-items-center justify-content-between py-2" ' +
'style="border-bottom:1px solid #f0f0f0;">' +
'<span><i class="fa fa-file-pdf-o text-danger mr-1"></i>' +
'<strong>' + escHtml(m.descricao) + '</strong></span>' +
'<a href="/materia/' + m.id + '/pdf-previa" target="_blank" ' +
'class="btn btn-sm btn-outline-primary ml-2" title="Visualizar PDF">' +
'<i class="fa fa-eye"></i> Visualizar PDF</a>' +
'</div>';
});
document.getElementById('lote-previa-lista').innerHTML = html || '<p class="text-muted">Nenhuma matéria selecionada.</p>';
// Reset checkbox de confirmação
var chk = document.getElementById('lote-previa-confirmacao');
chk.checked = false;
document.getElementById('lote-btn-assinar').disabled = true;
document.getElementById('lote-selecao-step').style.display = 'none';
document.getElementById('lote-form-step').style.display = 'none';
document.getElementById('lote-previa-step').style.display = 'block';
document.getElementById('lote-progresso-step').style.display = 'none';
document.getElementById('lote-resumo-step').style.display = 'none';
document.getElementById('lote-btn-proximo').style.display = 'none';
document.getElementById('lote-btn-voltar').style.display = 'none';
document.getElementById('lote-btn-previa').style.display = 'none';
document.getElementById('lote-btn-voltar-form').style.display= 'inline-block';
document.getElementById('lote-btn-assinar').style.display = 'inline-block';
document.getElementById('lote-btn-recarregar').style.display = 'none';
}
// Abre modal ao clicar no botão externo // Abre modal ao clicar no botão externo
document.getElementById('btn-assinar-em-lote').addEventListener('click', function () { document.getElementById('btn-assinar-em-lote').addEventListener('click', function () {
// Reset checkboxes para todos marcados // Reset checkboxes para todos marcados
@ -519,17 +606,24 @@
alert('Selecione ao menos uma matéria para assinar.'); alert('Selecione ao menos uma matéria para assinar.');
return; return;
} }
document.getElementById('lote-selecao-step').style.display = 'none'; irParaForm();
document.getElementById('lote-form-step').style.display = 'block';
document.getElementById('lote-btn-proximo').style.display = 'none';
document.getElementById('lote-btn-voltar').style.display = 'inline-block';
document.getElementById('lote-btn-assinar').style.display = 'inline-block';
}); });
// Passo 1 → 0: Voltar // Passo 1 → 0: Voltar
document.getElementById('lote-btn-voltar').addEventListener('click', irParaSelecao); document.getElementById('lote-btn-voltar').addEventListener('click', irParaSelecao);
// Botão Assinar (passo 1 → 2) // Passo 1 → 1.5: Visualizar Prévia
document.getElementById('lote-btn-previa').addEventListener('click', irParaPrevia);
// Passo 1.5 → 1: Voltar ao formulário
document.getElementById('lote-btn-voltar-form').addEventListener('click', irParaForm);
// Habilitar botão assinar apenas quando confirmação marcada
document.getElementById('lote-previa-confirmacao').addEventListener('change', function() {
document.getElementById('lote-btn-assinar').disabled = !this.checked;
});
// Botão Assinar (passo 1.5 → 2)
document.getElementById('lote-btn-assinar').addEventListener('click', function () { document.getElementById('lote-btn-assinar').addEventListener('click', function () {
var certFile = document.getElementById('lote-certificado').files[0]; var certFile = document.getElementById('lote-certificado').files[0];
var senha = document.getElementById('lote-senha').value; var senha = document.getElementById('lote-senha').value;
@ -544,9 +638,9 @@
fd.append('senha', senha); fd.append('senha', senha);
fd.append('ids', JSON.stringify(idsSel)); fd.append('ids', JSON.stringify(idsSel));
document.getElementById('lote-form-step').style.display = 'none'; document.getElementById('lote-previa-step').style.display = 'none';
document.getElementById('lote-progresso-step').style.display = 'block'; document.getElementById('lote-progresso-step').style.display = 'block';
document.getElementById('lote-btn-voltar').style.display = 'none'; document.getElementById('lote-btn-voltar-form').style.display= 'none';
document.getElementById('lote-btn-assinar').style.display = 'none'; document.getElementById('lote-btn-assinar').style.display = 'none';
document.getElementById('lote-btn-fechar').disabled = true; document.getElementById('lote-btn-fechar').disabled = true;

Loading…
Cancel
Save