mirror of https://github.com/interlegis/sapl.git
9 changed files with 1658 additions and 1120 deletions
@ -0,0 +1,18 @@ |
|||
# Generated manually |
|||
|
|||
from django.db import migrations, models |
|||
|
|||
|
|||
class Migration(migrations.Migration): |
|||
|
|||
dependencies = [ |
|||
('tce', '0002_tcearquivo_subpasta'), |
|||
] |
|||
|
|||
operations = [ |
|||
migrations.AddField( |
|||
model_name='tcepasta', |
|||
name='subpastas_customizadas', |
|||
field=models.TextField(blank=True, default='[]', help_text='JSON: Lista de subpastas adicionadas pelo usuário além das padrões', verbose_name='Subpastas Customizadas'), |
|||
), |
|||
] |
|||
File diff suppressed because it is too large
@ -0,0 +1,947 @@ |
|||
{% extends "base.html" %} |
|||
{% load i18n %} |
|||
|
|||
{% block title %}{{ subpasta_nome }} - {{ processo.titulo }}{% endblock %} |
|||
|
|||
{% block base_content %} |
|||
<div class="container-fluid" style="margin-top: 20px;"> |
|||
<!-- Breadcrumb --> |
|||
<div class="breadcrumb-custom mb-4"> |
|||
<a href="{% url 'sapl.tce:admin_index' %}"> |
|||
<i class="fas fa-home"></i> TCE |
|||
</a> |
|||
<span class="mx-2">/</span> |
|||
<a href="{% url 'sapl.tce:processo_detail' processo.id %}"> |
|||
{{ processo.titulo }} |
|||
</a> |
|||
<span class="mx-2">/</span> |
|||
<span>{{ subpasta_nome }}</span> |
|||
</div> |
|||
|
|||
<!-- Header da Subpasta --> |
|||
<div class="row mb-4"> |
|||
<div class="col-12"> |
|||
<div class="card"> |
|||
<div class="card-header" style="background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white;"> |
|||
<div class="d-flex justify-content-between align-items-center"> |
|||
<h3 class="mb-0"> |
|||
<i class="fas fa-folder-open"></i> {{ subpasta_nome }} |
|||
</h3> |
|||
<button class="btn btn-light" onclick="showUploadModal()"> |
|||
<i class="fas fa-plus"></i> {% trans 'Adicionar Arquivos' %} |
|||
</button> |
|||
</div> |
|||
</div> |
|||
<div class="card-body"> |
|||
<p class="mb-0"> |
|||
<strong>Pasta:</strong> {{ pasta.nome }} |
|||
<span class="mx-2">|</span> |
|||
<strong>Processo:</strong> {{ processo.titulo }} ({{ processo.ano }} - {{ processo.periodo }}) |
|||
</p> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
|
|||
<!-- Lista de Arquivos --> |
|||
<div class="row"> |
|||
<div class="col-12"> |
|||
<div class="card"> |
|||
<div class="card-header bg-light"> |
|||
<h5 class="mb-0"> |
|||
<i class="fas fa-file"></i> Arquivos |
|||
<span class="badge badge-primary ml-2">{{ arquivos.count }}</span> |
|||
</h5> |
|||
</div> |
|||
<div class="card-body"> |
|||
{% if arquivos %} |
|||
<div class="table-responsive"> |
|||
<table class="table table-hover"> |
|||
<thead> |
|||
<tr> |
|||
<th><i class="fas fa-file"></i> Nome</th> |
|||
<th><i class="fas fa-weight"></i> Tamanho</th> |
|||
<th><i class="fas fa-check-circle"></i> Status</th> |
|||
<th><i class="fas fa-calendar"></i> Data</th> |
|||
<th><i class="fas fa-cog"></i> Ações</th> |
|||
</tr> |
|||
</thead> |
|||
<tbody> |
|||
{% for arquivo in arquivos %} |
|||
<tr> |
|||
<td> |
|||
<i class="fas {% if arquivo.tipo == 'xml' %}fa-file-code text-success{% else %}fa-file-pdf text-danger{% endif %}"></i> |
|||
<strong>{{ arquivo.nome_original }}</strong> |
|||
{% if arquivo.p7s_gerado and arquivo.arquivo_p7s %} |
|||
<br> |
|||
<small class="text-muted"> |
|||
<i class="fas fa-file-export text-info"></i> |
|||
<a href="{{ arquivo.arquivo_p7s.url }}" target="_blank"> |
|||
{{ arquivo.nome_original|slice:":-4" }}.p7s |
|||
</a> |
|||
</small> |
|||
{% endif %} |
|||
</td> |
|||
<td>{{ arquivo.tamanho_mb|floatformat:2 }} MB</td> |
|||
<td> |
|||
{% if arquivo.validado %} |
|||
<span class="badge badge-success">Validado</span> |
|||
{% elif arquivo.p7s_gerado %} |
|||
<span class="badge badge-info">Quase pronto</span> |
|||
{% elif arquivo.assinado %} |
|||
<span class="badge badge-primary">Processado</span> |
|||
{% elif not arquivo.tamanho_ok %} |
|||
<span class="badge badge-warning">Arquivo muito grande</span> |
|||
{% elif not arquivo.ocr_ok %} |
|||
<span class="badge badge-warning">OCR pendente</span> |
|||
{% else %} |
|||
<span class="badge badge-secondary">Novo</span> |
|||
{% endif %} |
|||
</td> |
|||
<td><small>{{ arquivo.criado_em|date:"d/m/Y H:i" }}</small></td> |
|||
<td> |
|||
<button class="btn btn-sm btn-outline-primary" onclick="visualizarArquivo('{{ arquivo.id }}')"> |
|||
<i class="fas fa-eye"></i> |
|||
</button> |
|||
<button class="btn btn-sm btn-outline-danger" onclick="excluirArquivo('{{ arquivo.id }}')"> |
|||
<i class="fas fa-trash"></i> |
|||
</button> |
|||
</td> |
|||
</tr> |
|||
{% endfor %} |
|||
</tbody> |
|||
</table> |
|||
</div> |
|||
{% else %} |
|||
<div class="text-center py-5"> |
|||
<i class="fas fa-folder-open fa-3x text-muted mb-3"></i> |
|||
<h5 class="text-muted">Nenhum arquivo nesta pasta</h5> |
|||
<p class="text-muted">Clique em "Adicionar Arquivos" para fazer upload</p> |
|||
</div> |
|||
{% endif %} |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
|
|||
<!-- Botão Voltar --> |
|||
<div class="row mt-4 mb-5"> |
|||
<div class="col-12"> |
|||
<a href="{% url 'sapl.tce:processo_detail' processo.id %}" class="btn btn-secondary"> |
|||
<i class="fas fa-arrow-left"></i> {% trans 'Voltar ao Processo' %} |
|||
</a> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
|
|||
<!-- Modal: Upload de Arquivo - Wizard com Abas --> |
|||
<div class="modal fade" id="uploadFileModal" tabindex="-1"> |
|||
<div class="modal-dialog modal-xl"> |
|||
<div class="modal-content"> |
|||
<div class="modal-header bg-primary text-white"> |
|||
<h5 class="modal-title"> |
|||
<i class="fas fa-upload"></i> {% trans 'Adicionar Arquivo' %} |
|||
<br> |
|||
<small>{{ subpasta_nome }}</small> |
|||
</h5> |
|||
<button type="button" class="close text-white" data-dismiss="modal"> |
|||
<span>×</span> |
|||
</button> |
|||
</div> |
|||
<div class="modal-body"> |
|||
<!-- Abas do Wizard --> |
|||
<ul class="nav nav-pills nav-justified mb-4" id="wizard-tabs"> |
|||
<li class="nav-item"> |
|||
<a class="nav-link active" id="tab1-link" data-toggle="pill" href="#tab1-arquivo"> |
|||
<i class="fas fa-file-upload"></i><br> |
|||
<small>1. Arquivo</small> |
|||
</a> |
|||
</li> |
|||
<li class="nav-item"> |
|||
<a class="nav-link disabled" id="tab2-link" data-toggle="pill" href="#tab2-tamanho"> |
|||
<i class="fas fa-weight"></i><br> |
|||
<small>2. Tamanho</small> |
|||
</a> |
|||
</li> |
|||
<li class="nav-item"> |
|||
<a class="nav-link disabled" id="tab3-link" data-toggle="pill" href="#tab3-ocr"> |
|||
<i class="fas fa-search"></i><br> |
|||
<small>3. OCR</small> |
|||
</a> |
|||
</li> |
|||
<li class="nav-item"> |
|||
<a class="nav-link disabled" id="tab4-link" data-toggle="pill" href="#tab4-assinatura"> |
|||
<i class="fas fa-signature"></i><br> |
|||
<small>4. Assinatura</small> |
|||
</a> |
|||
</li> |
|||
<li class="nav-item"> |
|||
<a class="nav-link disabled" id="tab5-link" data-toggle="pill" href="#tab5-p7s"> |
|||
<i class="fas fa-file-export"></i><br> |
|||
<small>5. .p7s</small> |
|||
</a> |
|||
</li> |
|||
</ul> |
|||
|
|||
<!-- Conteúdo das Abas --> |
|||
<div class="tab-content"> |
|||
<!-- Aba 1: Upload do Arquivo --> |
|||
<div class="tab-pane fade show active" id="tab1-arquivo"> |
|||
<h5><i class="fas fa-file-upload"></i> Selecionar Arquivo</h5> |
|||
<hr> |
|||
<div class="form-group"> |
|||
<label>{% trans 'Upload de Arquivo PDF' %}</label> |
|||
<div class="custom-file"> |
|||
<input type="file" class="custom-file-input" id="fileInput" accept=".pdf"> |
|||
<label class="custom-file-label" for="fileInput">{% trans 'Escolher arquivo...' %}</label> |
|||
</div> |
|||
</div> |
|||
<div id="file-preview" class="mt-3" style="display:none;"> |
|||
<div class="alert alert-info"> |
|||
<i class="fas fa-file-pdf"></i> <strong id="file-name"></strong> |
|||
<br> |
|||
<small>Tamanho: <span id="file-size"></span></small> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
|
|||
<!-- Aba 2: Verificação de Tamanho --> |
|||
<div class="tab-pane fade" id="tab2-tamanho"> |
|||
<h5><i class="fas fa-weight"></i> Verificação de Tamanho</h5> |
|||
<hr> |
|||
<div id="tamanho-status"> |
|||
<div class="text-center py-4"> |
|||
<i class="fas fa-spinner fa-spin fa-3x text-primary mb-3"></i> |
|||
<p>Verificando tamanho do arquivo...</p> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
|
|||
<!-- Aba 3: OCR --> |
|||
<div class="tab-pane fade" id="tab3-ocr"> |
|||
<h5><i class="fas fa-search"></i> Tornar Pesquisável (OCR)</h5> |
|||
<hr> |
|||
<div id="ocr-status"> |
|||
<div class="text-center py-4"> |
|||
<p class="mb-3">Aplicar OCR para tornar o PDF pesquisável?</p> |
|||
<button class="btn btn-success" onclick="aplicarOCR()"> |
|||
<i class="fas fa-check"></i> Sim, aplicar OCR |
|||
</button> |
|||
<button class="btn btn-secondary" onclick="pularOCR()"> |
|||
<i class="fas fa-forward"></i> Pular esta etapa |
|||
</button> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
|
|||
<!-- Aba 4: Assinatura Digital --> |
|||
<div class="tab-pane fade" id="tab4-assinatura"> |
|||
<h5><i class="fas fa-signature"></i> Assinatura Digital</h5> |
|||
<hr> |
|||
<div id="assinatura-status"> |
|||
<div class="form-group"> |
|||
<label>Tipo de Certificado:</label> |
|||
<select class="form-control" id="tipo-certificado"> |
|||
<option value="">-- Selecione --</option> |
|||
<option value="A1">A1 (Arquivo digital)</option> |
|||
<option value="A3">A3 (Token/Cartão)</option> |
|||
</select> |
|||
</div> |
|||
<div id="div-certificado-a1" style="display:none;"> |
|||
<div class="form-group"> |
|||
<label>Arquivo do Certificado (.pfx):</label> |
|||
<input type="file" class="form-control-file" id="arquivo-certificado" accept=".pfx,.p12"> |
|||
</div> |
|||
<div class="form-group"> |
|||
<label>Senha do Certificado:</label> |
|||
<input type="password" class="form-control" id="senha-certificado"> |
|||
</div> |
|||
</div> |
|||
<div class="text-center mt-3"> |
|||
<button class="btn btn-success" onclick="assinarDocumento()" id="btn-assinar" disabled> |
|||
<i class="fas fa-signature"></i> Assinar Documento |
|||
</button> |
|||
<button class="btn btn-secondary" onclick="pularAssinatura()"> |
|||
<i class="fas fa-forward"></i> Pular assinatura |
|||
</button> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
|
|||
<!-- Aba 5: Conversão para .p7s --> |
|||
<div class="tab-pane fade" id="tab5-p7s"> |
|||
<h5><i class="fas fa-file-export"></i> Gerar Arquivo .p7s</h5> |
|||
<hr> |
|||
<div id="p7s-status"> |
|||
<div class="text-center py-4"> |
|||
<p class="mb-3">Converter para formato .p7s?</p> |
|||
<button class="btn btn-success" onclick="gerarP7S()"> |
|||
<i class="fas fa-file-export"></i> Gerar .p7s |
|||
</button> |
|||
<button class="btn btn-secondary" onclick="pularP7S()"> |
|||
<i class="fas fa-forward"></i> Pular esta etapa |
|||
</button> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
<div class="modal-footer"> |
|||
<button type="button" class="btn btn-secondary" data-dismiss="modal"> |
|||
{% trans 'Fechar' %} |
|||
</button> |
|||
<button type="button" class="btn btn-outline-secondary" id="btn-voltar" onclick="voltarAba()" style="display:none;"> |
|||
<i class="fas fa-arrow-left"></i> Voltar |
|||
</button> |
|||
<button type="button" class="btn btn-primary" id="btn-proximo" onclick="proximaAba()"> |
|||
Próximo <i class="fas fa-arrow-right"></i> |
|||
</button> |
|||
<button type="button" class="btn btn-success" id="btn-finalizar" onclick="finalizarUpload()" style="display:none;"> |
|||
<i class="fas fa-check"></i> Finalizar |
|||
</button> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
|
|||
<style> |
|||
.breadcrumb-custom { |
|||
background: #f8f9fa; |
|||
padding: 10px 15px; |
|||
border-radius: 6px; |
|||
} |
|||
|
|||
.breadcrumb-custom a { |
|||
color: #007bff; |
|||
text-decoration: none; |
|||
} |
|||
|
|||
.breadcrumb-custom a:hover { |
|||
text-decoration: underline; |
|||
} |
|||
|
|||
#arquivos-list table tbody tr { |
|||
transition: all 0.2s; |
|||
} |
|||
|
|||
#arquivos-list table tbody tr:hover { |
|||
background-color: #f8f9fa; |
|||
box-shadow: 0 2px 4px rgba(0,0,0,0.1); |
|||
} |
|||
|
|||
/* Wizard tabs styling */ |
|||
#wizard-tabs .nav-link { |
|||
border: 2px solid #dee2e6; |
|||
margin: 0 5px; |
|||
transition: all 0.3s; |
|||
} |
|||
|
|||
#wizard-tabs .nav-link.active { |
|||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); |
|||
color: white !important; |
|||
border-color: #667eea; |
|||
} |
|||
|
|||
#wizard-tabs .nav-link.disabled { |
|||
opacity: 0.5; |
|||
cursor: not-allowed; |
|||
} |
|||
|
|||
#wizard-tabs .nav-link:not(.disabled):not(.active):hover { |
|||
border-color: #667eea; |
|||
background-color: #f8f9fa; |
|||
} |
|||
|
|||
.tab-content { |
|||
min-height: 300px; |
|||
} |
|||
</style> |
|||
|
|||
<!-- SweetAlert2 --> |
|||
<script src="https://cdn.jsdelivr.net/npm/sweetalert2@11"></script> |
|||
|
|||
<script> |
|||
// IDs importantes da página |
|||
const PASTA_ID = '{{ pasta.id }}'; |
|||
const SUBPASTA_NOME = '{{ subpasta_nome }}'; |
|||
const PROCESSO_ID = '{{ processo.id }}'; |
|||
|
|||
// Variáveis do wizard |
|||
let currentTab = 1; |
|||
let selectedFile = null; |
|||
let uploadedArquivoId = null; |
|||
|
|||
// Obter CSRF token do cookie |
|||
function getCookie(name) { |
|||
let cookieValue = null; |
|||
if (document.cookie && document.cookie !== '') { |
|||
const cookies = document.cookie.split(';'); |
|||
for (let i = 0; i < cookies.length; i++) { |
|||
const cookie = cookies[i].trim(); |
|||
if (cookie.substring(0, name.length + 1) === (name + '=')) { |
|||
cookieValue = decodeURIComponent(cookie.substring(name.length + 1)); |
|||
break; |
|||
} |
|||
} |
|||
} |
|||
return cookieValue; |
|||
} |
|||
|
|||
// Mostrar modal de upload |
|||
function showUploadModal() { |
|||
// Reset wizard |
|||
currentTab = 1; |
|||
selectedFile = null; |
|||
uploadedArquivoId = null; |
|||
|
|||
// Reset form |
|||
document.getElementById('fileInput').value = ''; |
|||
document.getElementById('file-preview').style.display = 'none'; |
|||
|
|||
// Reset tabs |
|||
$('#tab1-link').addClass('active').removeClass('disabled'); |
|||
$('#tab2-link, #tab3-link, #tab4-link, #tab5-link').addClass('disabled').removeClass('active'); |
|||
$('#tab1-arquivo').addClass('show active'); |
|||
$('#tab2-tamanho, #tab3-ocr, #tab4-assinatura, #tab5-p7s').removeClass('show active'); |
|||
|
|||
// Reset buttons |
|||
document.getElementById('btn-voltar').style.display = 'none'; |
|||
document.getElementById('btn-proximo').style.display = 'inline-block'; |
|||
document.getElementById('btn-finalizar').style.display = 'none'; |
|||
|
|||
$('#uploadFileModal').modal('show'); |
|||
} |
|||
|
|||
// Preview do arquivo selecionado |
|||
document.getElementById('fileInput')?.addEventListener('change', function(e) { |
|||
selectedFile = e.target.files[0]; |
|||
if (selectedFile) { |
|||
document.getElementById('file-name').textContent = selectedFile.name; |
|||
document.getElementById('file-size').textContent = (selectedFile.size / 1024 / 1024).toFixed(2) + ' MB'; |
|||
document.getElementById('file-preview').style.display = 'block'; |
|||
|
|||
// Atualizar label do custom-file-input |
|||
const fileName = selectedFile.name; |
|||
const label = this.nextElementSibling; |
|||
if (label && label.classList.contains('custom-file-label')) { |
|||
label.classList.add('selected'); |
|||
label.textContent = fileName; |
|||
} |
|||
} |
|||
}); |
|||
|
|||
// Navegação entre abas |
|||
function proximaAba() { |
|||
if (currentTab === 1) { |
|||
if (!selectedFile) { |
|||
Swal.fire({ |
|||
title: 'Atenção!', |
|||
text: 'Por favor, selecione um arquivo antes de continuar.', |
|||
icon: 'warning' |
|||
}); |
|||
return; |
|||
} |
|||
// Upload do arquivo primeiro |
|||
uploadArquivo(); |
|||
} else if (currentTab === 2) { |
|||
// Verificar tamanho já foi feito automaticamente |
|||
irParaAba(3); |
|||
} else if (currentTab === 3) { |
|||
// OCR - avançar para assinatura |
|||
irParaAba(4); |
|||
} else if (currentTab === 4) { |
|||
// Assinatura - avançar para p7s |
|||
irParaAba(5); |
|||
} |
|||
} |
|||
|
|||
function voltarAba() { |
|||
if (currentTab > 1) { |
|||
irParaAba(currentTab - 1); |
|||
} |
|||
} |
|||
|
|||
function irParaAba(tabNum) { |
|||
currentTab = tabNum; |
|||
|
|||
// Remover active de todos os links e painéis |
|||
$('#tab1-link, #tab2-link, #tab3-link, #tab4-link, #tab5-link').removeClass('active'); |
|||
$('#tab1-arquivo, #tab2-tamanho, #tab3-ocr, #tab4-assinatura, #tab5-p7s').removeClass('show active'); |
|||
|
|||
// Mapear número da aba para o ID correto do painel |
|||
const painelIds = { |
|||
1: 'tab1-arquivo', |
|||
2: 'tab2-tamanho', |
|||
3: 'tab3-ocr', |
|||
4: 'tab4-assinatura', |
|||
5: 'tab5-p7s' |
|||
}; |
|||
|
|||
// Habilitar a aba desejada |
|||
$(`#tab${tabNum}-link`).removeClass('disabled').addClass('active'); |
|||
$(`#${painelIds[tabNum]}`).addClass('show active'); |
|||
|
|||
// Atualizar botões |
|||
document.getElementById('btn-voltar').style.display = tabNum > 1 ? 'inline-block' : 'none'; |
|||
document.getElementById('btn-proximo').style.display = tabNum < 5 ? 'inline-block' : 'none'; |
|||
document.getElementById('btn-finalizar').style.display = tabNum === 5 ? 'inline-block' : 'none'; |
|||
|
|||
// Executar ação específica da aba |
|||
if (tabNum === 2) { |
|||
verificarTamanho(); |
|||
} else if (tabNum === 3) { |
|||
// Reset OCR status se necessário |
|||
const ocrStatus = document.getElementById('ocr-status'); |
|||
if (!ocrStatus.querySelector('.alert-success') && !ocrStatus.querySelector('.alert-info')) { |
|||
ocrStatus.innerHTML = ` |
|||
<div class="text-center py-4"> |
|||
<p class="mb-3">Aplicar OCR para tornar o PDF pesquisável?</p> |
|||
<button class="btn btn-success" onclick="aplicarOCR()"> |
|||
<i class="fas fa-check"></i> Sim, aplicar OCR |
|||
</button> |
|||
<button class="btn btn-secondary" onclick="pularOCR()"> |
|||
<i class="fas fa-forward"></i> Pular esta etapa |
|||
</button> |
|||
</div> |
|||
`; |
|||
} |
|||
} else if (tabNum === 5) { |
|||
// Reset P7S status se necessário |
|||
const p7sStatus = document.getElementById('p7s-status'); |
|||
if (!p7sStatus.querySelector('.alert')) { |
|||
p7sStatus.innerHTML = ` |
|||
<div class="text-center py-4"> |
|||
<p class="mb-3">Converter para formato .p7s?</p> |
|||
<button class="btn btn-success" onclick="gerarP7S()"> |
|||
<i class="fas fa-file-export"></i> Gerar .p7s |
|||
</button> |
|||
<button class="btn btn-secondary" onclick="pularP7S()"> |
|||
<i class="fas fa-forward"></i> Pular esta etapa |
|||
</button> |
|||
</div> |
|||
`; |
|||
} |
|||
} |
|||
} |
|||
|
|||
// Upload inicial do arquivo |
|||
function uploadArquivo() { |
|||
const btnProximo = document.getElementById('btn-proximo'); |
|||
btnProximo.disabled = true; |
|||
btnProximo.innerHTML = '<i class="fas fa-spinner fa-spin"></i> Enviando...'; |
|||
|
|||
const formData = new FormData(); |
|||
formData.append('arquivo', selectedFile); |
|||
formData.append('pasta_id', PASTA_ID); |
|||
formData.append('subpasta', SUBPASTA_NOME); |
|||
|
|||
const csrftoken = getCookie('csrftoken'); |
|||
|
|||
fetch('/tce/api/arquivos/upload/', { |
|||
method: 'POST', |
|||
body: formData, |
|||
headers: { |
|||
'X-CSRFToken': csrftoken |
|||
} |
|||
}) |
|||
.then(response => response.json()) |
|||
.then(data => { |
|||
if (data.success) { |
|||
uploadedArquivoId = data.arquivo_id; |
|||
// Ir para aba de tamanho |
|||
irParaAba(2); |
|||
} else { |
|||
Swal.fire({ |
|||
title: 'Erro!', |
|||
text: data.error || 'Erro ao enviar arquivo', |
|||
icon: 'error' |
|||
}); |
|||
} |
|||
btnProximo.disabled = false; |
|||
btnProximo.innerHTML = 'Próximo <i class="fas fa-arrow-right"></i>'; |
|||
}) |
|||
.catch(error => { |
|||
console.error('Erro:', error); |
|||
Swal.fire({ |
|||
title: 'Erro!', |
|||
text: 'Erro ao enviar arquivo: ' + error.message, |
|||
icon: 'error' |
|||
}); |
|||
btnProximo.disabled = false; |
|||
btnProximo.innerHTML = 'Próximo <i class="fas fa-arrow-right"></i>'; |
|||
}); |
|||
} |
|||
|
|||
// Verificar tamanho do arquivo |
|||
function verificarTamanho() { |
|||
const tamanhoMB = selectedFile.size / 1024 / 1024; |
|||
const tamanhoStatus = document.getElementById('tamanho-status'); |
|||
|
|||
if (tamanhoMB <= 5) { |
|||
tamanhoStatus.innerHTML = ` |
|||
<div class="alert alert-success text-center"> |
|||
<i class="fas fa-check-circle fa-3x mb-3"></i> |
|||
<h5>Tamanho OK!</h5> |
|||
<p>O arquivo tem ${tamanhoMB.toFixed(2)} MB (≤ 5MB)</p> |
|||
</div> |
|||
`; |
|||
} else { |
|||
tamanhoStatus.innerHTML = ` |
|||
<div class="alert alert-warning text-center"> |
|||
<i class="fas fa-exclamation-triangle fa-3x mb-3"></i> |
|||
<h5>Arquivo muito grande!</h5> |
|||
<p>O arquivo tem ${tamanhoMB.toFixed(2)} MB (limite: 5MB)</p> |
|||
<button class="btn btn-warning mt-2" onclick="reduzirArquivo()"> |
|||
<i class="fas fa-compress"></i> Reduzir Arquivo |
|||
</button> |
|||
</div> |
|||
`; |
|||
} |
|||
} |
|||
|
|||
// Reduzir arquivo |
|||
function reduzirArquivo() { |
|||
const tamanhoStatus = document.getElementById('tamanho-status'); |
|||
tamanhoStatus.innerHTML = ` |
|||
<div class="text-center py-4"> |
|||
<i class="fas fa-spinner fa-spin fa-3x text-warning mb-3"></i> |
|||
<p>Reduzindo arquivo... (Funcionalidade em desenvolvimento)</p> |
|||
</div> |
|||
`; |
|||
|
|||
// Simular redução (em produção, chamar API) |
|||
setTimeout(() => { |
|||
tamanhoStatus.innerHTML = ` |
|||
<div class="alert alert-success text-center"> |
|||
<i class="fas fa-check-circle fa-3x mb-3"></i> |
|||
<h5>Arquivo reduzido com sucesso!</h5> |
|||
<p>Novo tamanho: 4.2 MB</p> |
|||
</div> |
|||
`; |
|||
}, 2000); |
|||
} |
|||
|
|||
// Aplicar OCR |
|||
function aplicarOCR() { |
|||
const ocrStatus = document.getElementById('ocr-status'); |
|||
ocrStatus.innerHTML = ` |
|||
<div class="text-center py-4"> |
|||
<i class="fas fa-spinner fa-spin fa-3x text-primary mb-3"></i> |
|||
<p>Aplicando OCR... (Funcionalidade em desenvolvimento)</p> |
|||
</div> |
|||
`; |
|||
|
|||
// Simular OCR (em produção, chamar API) |
|||
setTimeout(() => { |
|||
ocrStatus.innerHTML = ` |
|||
<div class="alert alert-success text-center"> |
|||
<i class="fas fa-check-circle fa-3x mb-3"></i> |
|||
<h5>OCR aplicado com sucesso!</h5> |
|||
<p>O documento agora está pesquisável</p> |
|||
<button class="btn btn-primary mt-2" onclick="irParaAba(4)"> |
|||
<i class="fas fa-arrow-right"></i> Continuar |
|||
</button> |
|||
</div> |
|||
`; |
|||
}, 2000); |
|||
} |
|||
|
|||
function pularOCR() { |
|||
irParaAba(4); |
|||
} |
|||
|
|||
// Configurar tipo de certificado |
|||
document.getElementById('tipo-certificado')?.addEventListener('change', function() { |
|||
const divA1 = document.getElementById('div-certificado-a1'); |
|||
const btnAssinar = document.getElementById('btn-assinar'); |
|||
|
|||
if (this.value === 'A1') { |
|||
divA1.style.display = 'block'; |
|||
btnAssinar.disabled = false; |
|||
} else if (this.value === 'A3') { |
|||
divA1.style.display = 'none'; |
|||
btnAssinar.disabled = false; |
|||
} else { |
|||
divA1.style.display = 'none'; |
|||
btnAssinar.disabled = true; |
|||
} |
|||
}); |
|||
|
|||
// Assinar documento |
|||
function assinarDocumento() { |
|||
if (!uploadedArquivoId) { |
|||
Swal.fire({ |
|||
title: 'Erro!', |
|||
text: 'Nenhum arquivo foi enviado.', |
|||
icon: 'error' |
|||
}); |
|||
return; |
|||
} |
|||
|
|||
const tipoCertificado = document.getElementById('tipo-certificado').value; |
|||
const senhaCertificado = document.getElementById('senha-certificado').value; |
|||
const arquivoCertificado = document.getElementById('arquivo-certificado').files[0]; |
|||
|
|||
// Validações |
|||
if (!tipoCertificado) { |
|||
Swal.fire({ |
|||
title: 'Atenção!', |
|||
text: 'Selecione o tipo de certificado', |
|||
icon: 'warning' |
|||
}); |
|||
return; |
|||
} |
|||
|
|||
if (tipoCertificado === 'A1' && (!arquivoCertificado || !senhaCertificado)) { |
|||
Swal.fire({ |
|||
title: 'Atenção!', |
|||
text: 'Selecione o arquivo do certificado e digite a senha', |
|||
icon: 'warning' |
|||
}); |
|||
return; |
|||
} |
|||
|
|||
const assinaturaStatus = document.getElementById('assinatura-status'); |
|||
assinaturaStatus.innerHTML = ` |
|||
<div class="text-center py-4"> |
|||
<i class="fas fa-spinner fa-spin fa-3x text-success mb-3"></i> |
|||
<p>Assinando documento digitalmente...</p> |
|||
</div> |
|||
`; |
|||
|
|||
const formData = new FormData(); |
|||
formData.append('tipo_certificado', tipoCertificado); |
|||
|
|||
if (tipoCertificado === 'A1') { |
|||
formData.append('arquivo_certificado', arquivoCertificado); |
|||
formData.append('senha_certificado', senhaCertificado); |
|||
} |
|||
|
|||
const csrftoken = getCookie('csrftoken'); |
|||
|
|||
fetch(`/tce/api/arquivos/${uploadedArquivoId}/assinar/`, { |
|||
method: 'POST', |
|||
body: formData, |
|||
headers: { |
|||
'X-CSRFToken': csrftoken |
|||
} |
|||
}) |
|||
.then(response => response.json()) |
|||
.then(data => { |
|||
if (data.success) { |
|||
assinaturaStatus.innerHTML = ` |
|||
<div class="alert alert-success text-center"> |
|||
<i class="fas fa-check-circle fa-3x mb-3"></i> |
|||
<h5>Documento assinado com sucesso!</h5> |
|||
<p>Assinatura digital aplicada</p> |
|||
${data.certificado ? `<small class="text-muted">Certificado: ${data.certificado.nome}<br>Válido até: ${data.certificado.validade}</small>` : ''} |
|||
<br> |
|||
<button class="btn btn-primary mt-3" onclick="irParaAba(5)"> |
|||
<i class="fas fa-arrow-right"></i> Continuar para .p7s |
|||
</button> |
|||
</div> |
|||
`; |
|||
} else { |
|||
assinaturaStatus.innerHTML = ` |
|||
<div class="alert alert-danger text-center"> |
|||
<i class="fas fa-exclamation-circle fa-3x mb-3"></i> |
|||
<h5>Erro ao assinar documento</h5> |
|||
<p>${data.error || 'Erro desconhecido'}</p> |
|||
${data.simulated ? '<small class="text-info">Para usar assinatura real, instale: pip install pyhanko pyhanko-certvalidator</small><br>' : ''} |
|||
<button class="btn btn-warning mt-2" onclick="pularAssinatura()"> |
|||
<i class="fas fa-forward"></i> Pular assinatura |
|||
</button> |
|||
</div> |
|||
`; |
|||
} |
|||
}) |
|||
.catch(error => { |
|||
console.error('Erro:', error); |
|||
assinaturaStatus.innerHTML = ` |
|||
<div class="alert alert-danger text-center"> |
|||
<i class="fas fa-exclamation-circle fa-3x mb-3"></i> |
|||
<h5>Erro ao assinar documento</h5> |
|||
<p>${error.message}</p> |
|||
<button class="btn btn-warning mt-2" onclick="pularAssinatura()"> |
|||
<i class="fas fa-forward"></i> Pular assinatura |
|||
</button> |
|||
</div> |
|||
`; |
|||
}); |
|||
} |
|||
|
|||
function pularAssinatura() { |
|||
irParaAba(5); |
|||
} |
|||
|
|||
// Gerar .p7s |
|||
function gerarP7S() { |
|||
if (!uploadedArquivoId) { |
|||
Swal.fire({ |
|||
title: 'Erro!', |
|||
text: 'Nenhum arquivo foi enviado.', |
|||
icon: 'error' |
|||
}); |
|||
return; |
|||
} |
|||
|
|||
const p7sStatus = document.getElementById('p7s-status'); |
|||
p7sStatus.innerHTML = ` |
|||
<div class="text-center py-4"> |
|||
<i class="fas fa-spinner fa-spin fa-3x text-success mb-3"></i> |
|||
<p>Gerando arquivo .p7s...</p> |
|||
</div> |
|||
`; |
|||
|
|||
const csrftoken = getCookie('csrftoken'); |
|||
|
|||
fetch(`/tce/api/arquivos/${uploadedArquivoId}/gerar-p7s/`, { |
|||
method: 'POST', |
|||
headers: { |
|||
'X-CSRFToken': csrftoken, |
|||
'Content-Type': 'application/json' |
|||
} |
|||
}) |
|||
.then(response => response.json()) |
|||
.then(data => { |
|||
if (data.success) { |
|||
p7sStatus.innerHTML = ` |
|||
<div class="alert alert-success text-center"> |
|||
<i class="fas fa-check-circle fa-3x mb-3"></i> |
|||
<h5>Arquivo .p7s gerado com sucesso!</h5> |
|||
<p>O arquivo está pronto para envio ao TCE</p> |
|||
<small class="text-muted">Arquivo: ${data.arquivo_p7s}</small> |
|||
</div> |
|||
`; |
|||
} else { |
|||
p7sStatus.innerHTML = ` |
|||
<div class="alert alert-danger text-center"> |
|||
<i class="fas fa-exclamation-circle fa-3x mb-3"></i> |
|||
<h5>Erro ao gerar .p7s</h5> |
|||
<p>${data.error || 'Erro desconhecido'}</p> |
|||
<button class="btn btn-warning mt-2" onclick="gerarP7S()"> |
|||
<i class="fas fa-redo"></i> Tentar novamente |
|||
</button> |
|||
</div> |
|||
`; |
|||
} |
|||
}) |
|||
.catch(error => { |
|||
console.error('Erro:', error); |
|||
p7sStatus.innerHTML = ` |
|||
<div class="alert alert-danger text-center"> |
|||
<i class="fas fa-exclamation-circle fa-3x mb-3"></i> |
|||
<h5>Erro ao gerar .p7s</h5> |
|||
<p>${error.message}</p> |
|||
<button class="btn btn-warning mt-2" onclick="gerarP7S()"> |
|||
<i class="fas fa-redo"></i> Tentar novamente |
|||
</button> |
|||
</div> |
|||
`; |
|||
}); |
|||
} |
|||
|
|||
function pularP7S() { |
|||
// Apenas marcar como completo |
|||
const p7sStatus = document.getElementById('p7s-status'); |
|||
p7sStatus.innerHTML = ` |
|||
<div class="alert alert-info text-center"> |
|||
<i class="fas fa-info-circle fa-3x mb-3"></i> |
|||
<h5>Etapa pulada</h5> |
|||
<p>Arquivo .p7s não foi gerado</p> |
|||
</div> |
|||
`; |
|||
} |
|||
|
|||
// Finalizar upload |
|||
function finalizarUpload() { |
|||
// Verificar se já temos um arquivo carregado |
|||
if (!uploadedArquivoId) { |
|||
Swal.fire({ |
|||
title: 'Erro!', |
|||
text: 'Nenhum arquivo foi enviado. Por favor, volte e selecione um arquivo.', |
|||
icon: 'error' |
|||
}); |
|||
return; |
|||
} |
|||
|
|||
Swal.fire({ |
|||
title: 'Sucesso!', |
|||
text: 'Arquivo processado com sucesso!', |
|||
icon: 'success', |
|||
timer: 1500, |
|||
showConfirmButton: false |
|||
}).then(() => { |
|||
$('#uploadFileModal').modal('hide'); |
|||
window.location.reload(); |
|||
}); |
|||
} |
|||
|
|||
// Visualizar arquivo |
|||
function visualizarArquivo(arquivoId) { |
|||
window.open(`/tce/arquivo/${arquivoId}/visualizar/`, '_blank'); |
|||
} |
|||
|
|||
// Excluir arquivo |
|||
function excluirArquivo(arquivoId) { |
|||
Swal.fire({ |
|||
title: 'Confirmar Exclusão', |
|||
text: 'Deseja realmente excluir este arquivo?', |
|||
icon: 'warning', |
|||
showCancelButton: true, |
|||
confirmButtonColor: '#d33', |
|||
cancelButtonColor: '#3085d6', |
|||
confirmButtonText: 'Sim, excluir!', |
|||
cancelButtonText: 'Cancelar' |
|||
}).then((result) => { |
|||
if (result.isConfirmed) { |
|||
excluirArquivoConfirmado(arquivoId); |
|||
} |
|||
}); |
|||
} |
|||
|
|||
function excluirArquivoConfirmado(arquivoId) { |
|||
const csrftoken = getCookie('csrftoken'); |
|||
const url = `/tce/api/arquivos/${arquivoId}/delete/`; |
|||
|
|||
fetch(url, { |
|||
method: 'POST', |
|||
headers: { |
|||
'X-CSRFToken': csrftoken, |
|||
'Content-Type': 'application/json' |
|||
} |
|||
}) |
|||
.then(response => response.json()) |
|||
.then(data => { |
|||
if (data.success) { |
|||
Swal.fire({ |
|||
title: 'Sucesso!', |
|||
text: 'Arquivo excluído com sucesso!', |
|||
icon: 'success', |
|||
timer: 1500, |
|||
showConfirmButton: false |
|||
}).then(() => { |
|||
// Recarregar a página para atualizar a lista |
|||
window.location.reload(); |
|||
}); |
|||
} else { |
|||
Swal.fire({ |
|||
title: 'Erro!', |
|||
text: 'Erro ao excluir: ' + (data.error || 'Erro desconhecido'), |
|||
icon: 'error' |
|||
}); |
|||
} |
|||
}) |
|||
.catch(error => { |
|||
console.error('Erro no fetch:', error); |
|||
Swal.fire({ |
|||
title: 'Erro!', |
|||
text: 'Erro ao excluir arquivo: ' + error.message, |
|||
icon: 'error' |
|||
}); |
|||
}); |
|||
} |
|||
|
|||
// Código removido - atualização do label agora está integrada no listener principal acima |
|||
</script> |
|||
{% endblock %} |
|||
Loading…
Reference in new issue