From abe767a7a6fbdfbf4173ea0427db047d34f7626a Mon Sep 17 00:00:00 2001 From: Sidney Date: Fri, 8 May 2026 11:36:00 -0300 Subject: [PATCH] =?UTF-8?q?fix:=20restaura=C3=A7=C3=A3o=20da=20sincroniza?= =?UTF-8?q?=C3=A7=C3=A3o=20SQL=20->=20JSON=20para=20estabilidade=20finance?= =?UTF-8?q?ira?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- manager/components/Finance.tsx | 41 ++++++++-------------------- manager/server.selfhosted.js | 50 ++++++++++++++++++++++++++++++++++ 2 files changed, 62 insertions(+), 29 deletions(-) diff --git a/manager/components/Finance.tsx b/manager/components/Finance.tsx index b2c898f..82d4ddd 100644 --- a/manager/components/Finance.tsx +++ b/manager/components/Finance.tsx @@ -183,6 +183,15 @@ const Finance: React.FC = ({ data, updateData }) => { setIsSyncing(true); try { + // 1. Solicita ao backend que sincronize o SQL para o JSON + const syncResp = await fetch('/api/admin/sync-finance-json', { method: 'POST' }); + const syncResult = await syncResp.json(); + + if (syncResult.success && syncResult.updatedCount > 0) { + showAlert('Sincronização', `${syncResult.updatedCount} pagamentos foram atualizados e salvos no sistema.`, 'success'); + } + + // 2. Busca os dados atualizados para exibir na tela const resp = await fetch('/api/admin/cobrancas'); if (!resp.ok) throw new Error('API fetch failed'); const cloudPayments = await resp.json(); @@ -191,14 +200,7 @@ const Finance: React.FC = ({ data, updateData }) => { let updatedCount = 0; const currentPayments = dataPaymentsRef.current; const updatedPayments = currentPayments.map(p => { - const match = cloudPayments.find((cp: any) => { - if (p.asaasPaymentId) { - return cp.asaas_payment_id === p.asaasPaymentId; - } - return cp.aluno_id === p.studentId && - Math.abs(cp.valor - p.amount) < 0.01 && - cp.vencimento === p.dueDate; - }); + const match = cloudPayments.find((cp: any) => cp.asaas_payment_id === p.asaasPaymentId); if (match) { const statusStr = (match.status || '').toLowerCase(); @@ -206,14 +208,13 @@ const Finance: React.FC = ({ data, updateData }) => { statusStr === 'atrasado' ? 'overdue' : statusStr === 'cancelado' ? 'cancelled' : 'pending'; - if (p.status !== newStatus || p.amount !== match.valor || p.installmentId !== (match.asaas_installment_id || match.installment) || p.asaasPaymentUrl !== match.link_boleto || p.asaasPaymentId !== match.asaas_payment_id) { + if (p.status !== newStatus) { updatedCount++; return { ...p, status: newStatus as any, - amount: match.valor, + amount: Number(match.valor), paidDate: match.data_pagamento || p.paidDate, - installmentId: match.asaas_installment_id || match.installment || p.installmentId, asaasPaymentUrl: match.link_boleto || p.asaasPaymentUrl, asaasPaymentId: match.asaas_payment_id || p.asaasPaymentId }; @@ -224,28 +225,10 @@ const Finance: React.FC = ({ data, updateData }) => { if (updatedCount > 0) { updateData({ payments: updatedPayments }); - - const hasOverdue = updatedPayments.some((p, idx) => { - const oldP = currentPayments[idx]; - return oldP && oldP.status !== 'overdue' && p.status === 'overdue'; - }); - - const hasPaid = updatedPayments.some((p, idx) => { - const oldP = currentPayments[idx]; - return oldP && oldP.status !== 'paid' && p.status === 'paid'; - }); - - let message = `${updatedCount} pagamento(s) atualizado(s).`; - if (hasPaid && !hasOverdue) message = 'Pagamento confirmado e registrado.'; - if (hasOverdue && !hasPaid) message = 'Status atualizado para Atrasado.'; - if (hasPaid && hasOverdue) message = 'Pagamentos e atrasos atualizados.'; - - showAlert('Sincronização', message, 'success'); } } } catch (error) { console.error('Erro ao sincronizar pagamentos:', error); - // Suppress alert so it doesn't pop up randomly to the user if the server restarts temporarily } finally { setIsSyncing(false); } diff --git a/manager/server.selfhosted.js b/manager/server.selfhosted.js index 2347110..3ee479a 100644 --- a/manager/server.selfhosted.js +++ b/manager/server.selfhosted.js @@ -1324,6 +1324,43 @@ function agendarRotina(tipo, hora, minuto) { console.log(`[Cron:${label}] ✅ Rotina agendada para ${String(h).padStart(2, '0')}:${String(m).padStart(2, '0')} (America/Sao_Paulo)`); } +async function syncRelationalToJsonPayments() { + try { + const { rows: cloudPayments } = await pool.query('SELECT * FROM alunos_cobrancas'); + const appData = await getSchoolData(); + let updatedCount = 0; + + if (!appData || !appData.payments) return; + + const updatedPayments = appData.payments.map(p => { + const match = cloudPayments.find(cp => cp.asaas_payment_id === p.asaasPaymentId); + if (match) { + const statusStr = (match.status || '').toLowerCase(); + const newStatus = statusStr === 'pago' ? 'paid' : + statusStr === 'atrasado' ? 'overdue' : + statusStr === 'cancelado' ? 'cancelled' : 'pending'; + + if (p.status !== newStatus) { + updatedCount++; + return { ...p, status: newStatus, paidDate: match.data_pagamento || p.paidDate }; + } + } + return p; + }); + + if (updatedCount > 0) { + appData.payments = updatedPayments; + appData.lastUpdated = new Date().toISOString(); + await saveSchoolData(appData); + console.log(`[Sync:SQL->JSON] ✅ ${updatedCount} status de pagamentos sincronizados com sucesso.`); + } + return updatedCount; + } catch (err) { + console.error('[Sync:SQL->JSON] ❌ Erro na sincronização reversa:', err.message); + return 0; + } +} + async function inicializarAgendamento() { try { // Inicialização DB para colunas de automação (garantir no boot) @@ -1351,6 +1388,9 @@ async function inicializarAgendamento() { // Sincronização de Integridade (JSON -> Tabelas Relacionais) await syncJsonToRelationalTables(); + // Sincronização Reversa (SQL -> JSON) - Garante que status pagos no DB reflitam no painel administrativo + await syncRelationalToJsonPayments(); + const appData = await getSchoolData(); // Migração: Se existirem notas no JSON, movemos para a tabela e removemos do JSON @@ -1426,6 +1466,16 @@ async function startServer() { } }); + // Endpoint para forçar sincronização SQL -> JSON (Aba Financeiro) + app.post('/api/admin/sync-finance-json', async (req, res) => { + try { + const updatedCount = await syncRelationalToJsonPayments(); + res.json({ success: true, updatedCount }); + } catch (e) { + res.status(500).json({ error: e.message }); + } + }); + // API para gerenciar o agendamento (suporte a preventivo e atrasado) app.get('/api/cron/status', (req, res) => { res.json({