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)}
Abrir
`;
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 ? `Revisar ` : ""}
`;
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)}
${conta.observacao ? pidDcEscapeHtml(conta.observacao) : "+ Observação"}
`;
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)}
${linha.observacao ? pidDcEscapeHtml(linha.observacao) : "+ Observação"}
`;
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");
});