fix: restauração da sincronização SQL -> JSON para estabilidade financeira
This commit is contained in:
parent
790c6d055f
commit
abe767a7a6
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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({
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue