fix: restauração da sincronização SQL -> JSON para estabilidade financeira

This commit is contained in:
Sidney 2026-05-08 11:36:00 -03:00
parent 790c6d055f
commit abe767a7a6
2 changed files with 62 additions and 29 deletions

View File

@ -183,6 +183,15 @@ const Finance: React.FC<FinanceProps> = ({ data, updateData }) => {
setIsSyncing(true); setIsSyncing(true);
try { 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'); const resp = await fetch('/api/admin/cobrancas');
if (!resp.ok) throw new Error('API fetch failed'); if (!resp.ok) throw new Error('API fetch failed');
const cloudPayments = await resp.json(); const cloudPayments = await resp.json();
@ -191,14 +200,7 @@ const Finance: React.FC<FinanceProps> = ({ data, updateData }) => {
let updatedCount = 0; let updatedCount = 0;
const currentPayments = dataPaymentsRef.current; const currentPayments = dataPaymentsRef.current;
const updatedPayments = currentPayments.map(p => { const updatedPayments = currentPayments.map(p => {
const match = cloudPayments.find((cp: any) => { const match = cloudPayments.find((cp: any) => cp.asaas_payment_id === p.asaasPaymentId);
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;
});
if (match) { if (match) {
const statusStr = (match.status || '').toLowerCase(); const statusStr = (match.status || '').toLowerCase();
@ -206,14 +208,13 @@ const Finance: React.FC<FinanceProps> = ({ data, updateData }) => {
statusStr === 'atrasado' ? 'overdue' : statusStr === 'atrasado' ? 'overdue' :
statusStr === 'cancelado' ? 'cancelled' : 'pending'; 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++; updatedCount++;
return { return {
...p, ...p,
status: newStatus as any, status: newStatus as any,
amount: match.valor, amount: Number(match.valor),
paidDate: match.data_pagamento || p.paidDate, paidDate: match.data_pagamento || p.paidDate,
installmentId: match.asaas_installment_id || match.installment || p.installmentId,
asaasPaymentUrl: match.link_boleto || p.asaasPaymentUrl, asaasPaymentUrl: match.link_boleto || p.asaasPaymentUrl,
asaasPaymentId: match.asaas_payment_id || p.asaasPaymentId asaasPaymentId: match.asaas_payment_id || p.asaasPaymentId
}; };
@ -224,28 +225,10 @@ const Finance: React.FC<FinanceProps> = ({ data, updateData }) => {
if (updatedCount > 0) { if (updatedCount > 0) {
updateData({ payments: updatedPayments }); 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) { } catch (error) {
console.error('Erro ao sincronizar pagamentos:', 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 { } finally {
setIsSyncing(false); setIsSyncing(false);
} }

View File

@ -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)`); 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() { async function inicializarAgendamento() {
try { try {
// Inicialização DB para colunas de automação (garantir no boot) // 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) // Sincronização de Integridade (JSON -> Tabelas Relacionais)
await syncJsonToRelationalTables(); await syncJsonToRelationalTables();
// Sincronização Reversa (SQL -> JSON) - Garante que status pagos no DB reflitam no painel administrativo
await syncRelationalToJsonPayments();
const appData = await getSchoolData(); const appData = await getSchoolData();
// Migração: Se existirem notas no JSON, movemos para a tabela e removemos do JSON // 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) // API para gerenciar o agendamento (suporte a preventivo e atrasado)
app.get('/api/cron/status', (req, res) => { app.get('/api/cron/status', (req, res) => {
res.json({ res.json({