const PID_DC_STATUS_LABELS = { revisao: "Em revisão", concluida: "Concluída" }; const PID_DC_SEVERIDADE_LABELS = { alta: "Alta", media: "Média", baixa: "Baixa" }; const PID_DC_ACHADO_STATUS_LABELS = { pendente: "Pendente", tratado: "Tratado", ignorado: "Ignorado" }; function pidDcFormatMoeda(valor) { const numero = Number(valor || 0); return numero.toLocaleString("pt-BR", { style: "currency", currency: "BRL" }); } function pidDcFormatCompetencia(iso) { if (!iso) return ""; const [ano, mes] = iso.split("-"); return `${mes}/${ano}`; } function pidDcFormatData(iso) { if (!iso) return ""; return new Date(iso).toLocaleDateString("pt-BR"); } function pidDcEscapeHtml(texto) { const div = document.createElement("div"); div.textContent = texto == null ? "" : String(texto); return div.innerHTML; } async function pidFetchApuracoesContabil() { return pidApiRequest("/contabil-apuracoes/"); } async function pidFetchApuracaoContabil(id) { return pidApiRequest(`/contabil-apuracoes/${id}/`); } async function pidCriarApuracaoContabil(formData) { return pidApiRequest("/contabil-apuracoes/", { method: "POST", body: formData }); } async function pidExcluirApuracaoContabil(id) { return pidApiRequest(`/contabil-apuracoes/${id}/`, { method: "DELETE" }); } async function pidConcluirApuracaoContabil(id) { return pidApiRequest(`/contabil-apuracoes/${id}/concluir/`, { method: "POST" }); } async function pidAtualizarObservacaoContaContabil(id, observacao) { return pidApiRequest(`/contabil-contas/${id}/`, { method: "PATCH", body: { observacao } }); } async function pidAtualizarObservacaoLinhaDreContabil(id, observacao) { return pidApiRequest(`/contabil-linhas-dre/${id}/`, { method: "PATCH", body: { observacao } }); } async function pidAtualizarAchadoContabil(id, status, observacaoContador) { return pidApiRequest(`/contabil-achados/${id}/`, { method: "PATCH", body: { status, observacao_contador: observacaoContador }, }); } document.addEventListener("DOMContentLoaded", async () => { const listView = document.getElementById("dc-list-view"); if (!listView) return; const me = await pidGetMe(); if (!me) return; const relatoriosPerms = (me.permissoes_efetivas && me.permissoes_efetivas["relatorios"]) || {}; const canUse = !!(relatoriosPerms.enabled && relatoriosPerms.apps && relatoriosPerms.apps["dashboard-contabil"]); const noAccessEl = document.getElementById("dc-no-access"); const contentEl = document.getElementById("dc-content"); if (noAccessEl) noAccessEl.hidden = canUse; if (contentEl) contentEl.hidden = !canUse; if (!canUse) return; const formView = document.getElementById("dc-form-view"); const reviewView = document.getElementById("dc-review-view"); let apuracaoAtual = null; let filtroSeveridade = "todas"; let filtroStatusAchado = "pendente"; let achadoEmEdicao = null; // Ids de ContabilConta com filhos recolhidos na árvore do Balancete — // reiniciado toda vez que uma apuração é aberta/criada (ver renderRevisao). let dcContasColapsadas = new Set(); function showView(view) { listView.hidden = view !== "list"; formView.hidden = view !== "form"; reviewView.hidden = view !== "review"; } // ---- Histórico ---- async function carregarLista() { const apuracoes = await pidFetchApuracoesContabil(); const body = document.getElementById("dc-list-body"); const empty = document.getElementById("dc-list-empty"); body.innerHTML = ""; empty.hidden = apuracoes.length > 0; apuracoes.forEach((apuracao) => { const tr = document.createElement("tr"); tr.innerHTML = ` ${pidDcEscapeHtml(apuracao.nome_empresa)} ${pidDcFormatCompetencia(apuracao.competencia)} ${apuracao.total_achados_pendentes} ${PID_DC_STATUS_LABELS[apuracao.status] || apuracao.status} ${pidDcEscapeHtml(apuracao.criado_por_nome || "-")} ${pidDcFormatData(apuracao.criado_em)} `; body.appendChild(tr); }); } document.getElementById("dc-list-body").addEventListener("click", async (event) => { const abrirBtn = event.target.closest("[data-dc-abrir]"); if (abrirBtn) { await abrirRevisao(Number(abrirBtn.getAttribute("data-dc-abrir"))); return; } const excluirBtn = event.target.closest("[data-dc-excluir]"); if (excluirBtn) { if (!(await pidConfirm("Excluir esta análise? Essa ação não pode ser desfeita.", { perigoso: true }))) return; await pidExcluirApuracaoContabil(Number(excluirBtn.getAttribute("data-dc-excluir"))); await carregarLista(); } }); // ---- Nova Análise ---- function wireArquivoField(inputId) { const input = document.getElementById(inputId); const nameEl = document.getElementById(`${inputId}-name`); const removeBtn = document.getElementById(`${inputId}-remove`); function atualizar() { const arquivo = input.files[0]; nameEl.textContent = arquivo ? arquivo.name : "Nenhum arquivo selecionado"; removeBtn.hidden = !arquivo; } input.addEventListener("change", atualizar); removeBtn.addEventListener("click", () => { input.value = ""; atualizar(); }); return atualizar; } const resetArquivoField = wireArquivoField("dc-form-arquivo"); document.getElementById("dc-new-btn").addEventListener("click", () => { document.getElementById("dc-form-arquivo").value = ""; resetArquivoField(); document.getElementById("dc-form-error").textContent = ""; showView("form"); }); document.getElementById("dc-form-back-btn").addEventListener("click", () => showView("list")); document.getElementById("dc-form-cancel-btn").addEventListener("click", () => showView("list")); document.getElementById("dc-form-submit-btn").addEventListener("click", async () => { const errorEl = document.getElementById("dc-form-error"); errorEl.textContent = ""; const arquivo = document.getElementById("dc-form-arquivo").files[0]; if (!arquivo) { errorEl.textContent = "Anexe o PDF de Balancete + DRE."; return; } const formData = new FormData(); formData.append("arquivo", arquivo); const submitBtn = document.getElementById("dc-form-submit-btn"); submitBtn.disabled = true; try { const apuracao = await pidCriarApuracaoContabil(formData); apuracaoAtual = apuracao; filtroSeveridade = "todas"; filtroStatusAchado = "pendente"; renderRevisao(); showView("review"); await carregarLista(); } catch (e) { errorEl.textContent = e.message; } finally { submitBtn.disabled = false; } }); // ---- Revisão ---- async function abrirRevisao(id) { apuracaoAtual = await pidFetchApuracaoContabil(id); filtroSeveridade = "todas"; filtroStatusAchado = "pendente"; renderRevisao(); showView("review"); } function renderRevisao() { const apuracao = apuracaoAtual; const concluida = apuracao.status === "concluida"; document.getElementById("dc-review-empresa").textContent = `— ${apuracao.nome_empresa}`; document.getElementById("dc-review-subtitle").textContent = `CNPJ ${apuracao.cnpj || "não informado"} · Competência ${pidDcFormatCompetencia(apuracao.competencia)} · ${apuracao.contas.length} conta(s)`; document.getElementById("dc-concluida-aviso").hidden = !concluida; document.getElementById("dc-concluir-btn").hidden = concluida; dcContasColapsadas = new Set(); renderAchados(); renderContas(); renderDre(); } function achadosFiltrados() { return apuracaoAtual.achados.filter((achado) => { if (filtroSeveridade !== "todas" && achado.severidade !== filtroSeveridade) return false; if (filtroStatusAchado === "pendente" && achado.status !== "pendente") return false; return true; }); } function renderAchados() { const lista = document.getElementById("dc-achados-list"); const empty = document.getElementById("dc-achados-empty"); const achados = achadosFiltrados(); lista.innerHTML = ""; empty.hidden = achados.length > 0; achados.forEach((achado) => { const card = document.createElement("div"); card.className = `dc-achado-card dc-achado-card--${achado.severidade}`; const podeEditar = apuracaoAtual.status !== "concluida" && achado.status === "pendente"; card.innerHTML = `
${PID_DC_SEVERIDADE_LABELS[achado.severidade] || achado.severidade} ${PID_DC_ACHADO_STATUS_LABELS[achado.status] || achado.status} ${pidDcEscapeHtml(achado.titulo)}
${pidDcEscapeHtml(achado.mensagem)}
${achado.observacao_contador ? `
${pidDcEscapeHtml(achado.observacao_contador)}${achado.tratado_por_nome ? ` — ${pidDcEscapeHtml(achado.tratado_por_nome)}` : ""}
` : ""}
${podeEditar ? `` : ""}
`; lista.appendChild(card); }); } document.getElementById("dc-achados-list").addEventListener("click", (event) => { const btn = event.target.closest("[data-dc-achado-editar]"); if (!btn) return; abrirModalAchado(Number(btn.getAttribute("data-dc-achado-editar"))); }); document.querySelectorAll("#dc-achados-severidade-chips [data-dc-severidade]").forEach((chip) => { chip.addEventListener("click", () => { filtroSeveridade = chip.getAttribute("data-dc-severidade"); document.querySelectorAll("#dc-achados-severidade-chips .dc-chip").forEach((c) => c.classList.toggle("is-active", c === chip)); renderAchados(); }); }); document.querySelectorAll("#dc-achados-status-chips [data-dc-status]").forEach((chip) => { chip.addEventListener("click", () => { filtroStatusAchado = chip.getAttribute("data-dc-status"); document.querySelectorAll("#dc-achados-status-chips .dc-chip").forEach((c) => c.classList.toggle("is-active", c === chip)); renderAchados(); }); }); // O código da conta (ex. "1.01.01.002.001") já chega do backend na mesma // ordem hierárquica/sequencial do PDF (uma sintética seguida das suas // analíticas, ver portal_api/dashboard_contabil/CLAUDE.md) — o nível de // indentação vem direto da contagem de segmentos separados por ".", sem // precisar reconstruir uma árvore por id/parent (o mesmo código pode se // repetir em várias linhas irmãs, ex. três bancos diferentes sob o mesmo // "1.01.01.002.001", então não dá pra usar o código como chave única). function dcContaNivel(conta) { return conta.codigo.split(".").length - 1; } function renderContas() { const body = document.getElementById("dc-contas-body"); body.innerHTML = ""; const contas = apuracaoAtual.contas; const niveis = contas.map(dcContaNivel); const temFilhos = contas.map((_, i) => i + 1 < contas.length && niveis[i + 1] > niveis[i]); // Pilha dos níveis de ancestrais recolhidos — enquanto uma linha tiver // algum ancestral na pilha, ela fica oculta (é descendente de um grupo // fechado), mesmo padrão de renderização de árvore a partir de uma // lista plana com profundidade já conhecida. const pilhaColapso = []; contas.forEach((conta, i) => { const nivel = niveis[i]; while (pilhaColapso.length && pilhaColapso[pilhaColapso.length - 1] >= nivel) { pilhaColapso.pop(); } const oculta = pilhaColapso.length > 0; if (temFilhos[i] && dcContasColapsadas.has(conta.id)) { pilhaColapso.push(nivel); } if (oculta) return; const expandida = !dcContasColapsadas.has(conta.id); const toggle = temFilhos[i] ? `` : ``; const tr = document.createElement("tr"); if (conta.tipo === "S") tr.classList.add("dc-conta-row--sintetica"); tr.innerHTML = ` ${pidDcEscapeHtml(conta.codigo)} ${toggle}${pidDcEscapeHtml(conta.descricao)} ${pidDcFormatMoeda(conta.saldo_anterior)} ${pidDcFormatMoeda(conta.debito)} ${pidDcFormatMoeda(conta.credito)} ${pidDcFormatMoeda(conta.saldo_atual)} `; body.appendChild(tr); }); } document.getElementById("dc-contas-body").addEventListener("click", (event) => { const toggleBtn = event.target.closest("[data-dc-conta-toggle]"); if (toggleBtn) { const id = Number(toggleBtn.getAttribute("data-dc-conta-toggle")); if (dcContasColapsadas.has(id)) dcContasColapsadas.delete(id); else dcContasColapsadas.add(id); renderContas(); return; } const btn = event.target.closest("[data-dc-conta-observacao]"); if (!btn) return; abrirModalObservacao("conta", Number(btn.getAttribute("data-dc-conta-observacao"))); }); function renderDre() { const body = document.getElementById("dc-dre-body"); body.innerHTML = ""; apuracaoAtual.linhas_dre.forEach((linha) => { const tr = document.createElement("tr"); tr.className = linha.totalizador ? "dc-dre-row dc-dre-row--totalizador" : "dc-dre-row"; const indentacao = Math.max(0, linha.nivel) * 16; tr.innerHTML = ` ${pidDcEscapeHtml(linha.descricao)} ${pidDcFormatMoeda(linha.valor)} `; body.appendChild(tr); }); } document.getElementById("dc-dre-body").addEventListener("click", (event) => { const btn = event.target.closest("[data-dc-dre-observacao]"); if (!btn) return; abrirModalObservacao("dre", Number(btn.getAttribute("data-dc-dre-observacao"))); }); // ---- Tabs ---- document.getElementById("dc-tabs").addEventListener("click", (event) => { const tabBtn = event.target.closest("[data-dc-tab]"); if (!tabBtn) return; const tab = tabBtn.getAttribute("data-dc-tab"); document.querySelectorAll("#dc-tabs .pa-tab").forEach((t) => t.classList.toggle("is-active", t === tabBtn)); document.querySelectorAll("[data-dc-panel]").forEach((panel) => { panel.hidden = panel.getAttribute("data-dc-panel") !== tab; }); }); // ---- Modal de observação (reaproveitado por conta do Balancete e linha da DRE) ---- const observacaoModal = document.getElementById("dc-observacao-modal"); let observacaoEmEdicaoTipo = null; // "conta" | "dre" let observacaoEmEdicaoId = null; function abrirModalObservacao(tipo, id) { const lista = tipo === "dre" ? apuracaoAtual.linhas_dre : apuracaoAtual.contas; const item = lista.find((i) => i.id === id); if (!item) return; observacaoEmEdicaoTipo = tipo; observacaoEmEdicaoId = id; document.getElementById("dc-observacao-modal-conta").textContent = tipo === "dre" ? item.descricao : `${item.codigo} — ${item.descricao}`; document.getElementById("dc-observacao-modal-texto").value = item.observacao || ""; document.getElementById("dc-observacao-modal-error").textContent = ""; document.getElementById("dc-observacao-modal-texto").disabled = apuracaoAtual.status === "concluida"; document.getElementById("dc-observacao-modal-save-btn").hidden = apuracaoAtual.status === "concluida"; observacaoModal.hidden = false; } function fecharModalObservacao() { observacaoModal.hidden = true; observacaoEmEdicaoTipo = null; observacaoEmEdicaoId = null; } document.getElementById("dc-observacao-modal-cancel-btn").addEventListener("click", fecharModalObservacao); observacaoModal.addEventListener("click", (event) => { if (event.target === observacaoModal) fecharModalObservacao(); }); document.getElementById("dc-observacao-modal-save-btn").addEventListener("click", async () => { const errorEl = document.getElementById("dc-observacao-modal-error"); errorEl.textContent = ""; const texto = document.getElementById("dc-observacao-modal-texto").value; try { if (observacaoEmEdicaoTipo === "dre") { const linha = await pidAtualizarObservacaoLinhaDreContabil(observacaoEmEdicaoId, texto); const indice = apuracaoAtual.linhas_dre.findIndex((l) => l.id === observacaoEmEdicaoId); if (indice >= 0) apuracaoAtual.linhas_dre[indice] = linha; renderDre(); } else { const conta = await pidAtualizarObservacaoContaContabil(observacaoEmEdicaoId, texto); const indice = apuracaoAtual.contas.findIndex((c) => c.id === observacaoEmEdicaoId); if (indice >= 0) apuracaoAtual.contas[indice] = conta; renderContas(); } fecharModalObservacao(); } catch (e) { errorEl.textContent = e.message; } }); // ---- Modal de achado (tratar/ignorar) ---- const achadoModal = document.getElementById("dc-achado-modal"); function abrirModalAchado(achadoId) { const achado = apuracaoAtual.achados.find((a) => a.id === achadoId); if (!achado) return; achadoEmEdicao = achadoId; document.getElementById("dc-achado-modal-titulo").textContent = achado.titulo; document.getElementById("dc-achado-modal-mensagem").textContent = achado.mensagem; document.getElementById("dc-achado-modal-texto").value = achado.observacao_contador || ""; document.getElementById("dc-achado-modal-error").textContent = ""; achadoModal.hidden = false; } function fecharModalAchado() { achadoModal.hidden = true; achadoEmEdicao = null; } document.getElementById("dc-achado-modal-cancel-btn").addEventListener("click", fecharModalAchado); achadoModal.addEventListener("click", (event) => { if (event.target === achadoModal) fecharModalAchado(); }); async function salvarStatusAchado(novoStatus) { const errorEl = document.getElementById("dc-achado-modal-error"); errorEl.textContent = ""; const texto = document.getElementById("dc-achado-modal-texto").value.trim(); if (!texto) { errorEl.textContent = "Informe uma justificativa para o tratamento desta observação."; return; } try { const achado = await pidAtualizarAchadoContabil(achadoEmEdicao, novoStatus, texto); const indice = apuracaoAtual.achados.findIndex((a) => a.id === achadoEmEdicao); if (indice >= 0) apuracaoAtual.achados[indice] = achado; renderAchados(); fecharModalAchado(); await carregarLista(); } catch (e) { errorEl.textContent = e.message; } } document.getElementById("dc-achado-modal-tratar-btn").addEventListener("click", () => salvarStatusAchado("tratado")); document.getElementById("dc-achado-modal-ignorar-btn").addEventListener("click", () => salvarStatusAchado("ignorado")); // ---- Concluir análise ---- document.getElementById("dc-concluir-btn").addEventListener("click", async () => { if (!(await pidConfirm("Concluir esta análise? Depois de concluída, as observações não podem mais ser editadas."))) return; apuracaoAtual = await pidConcluirApuracaoContabil(apuracaoAtual.id); renderRevisao(); await carregarLista(); }); document.getElementById("dc-review-back-btn").addEventListener("click", () => showView("list")); await carregarLista(); showView("list"); });