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);
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<FinanceProps> = ({ 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<FinanceProps> = ({ 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<FinanceProps> = ({ 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);
}

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)`);
}
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({