fix(sync): truncate fields in import-legacy to match column VARCHAR limits

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
VPS 4 Builder
2026-05-14 07:05:31 +02:00
parent ca584dca72
commit 6b67fbc26e
+78 -2
View File
@@ -728,8 +728,19 @@ app.put('/api/notificacoes/:id/read', async (req, res) => {
// --- PACIENTES ---
app.get('/api/pacientes', async (req, res) => {
try {
const { rows } = await pool.query('SELECT * FROM pacientes');
res.json(rows);
const { q } = req.query;
let text, params;
if (q && q.trim()) {
const term = `%${q.trim().toUpperCase()}%`;
text = `SELECT * FROM pacientes WHERE UPPER(nome) LIKE $1 OR cpf LIKE $1 ORDER BY nome LIMIT 100`;
params = [term];
} else {
text = `SELECT * FROM pacientes ORDER BY nome LIMIT 50`;
params = [];
}
const { rows } = await pool.query(text, params);
const { rows: total } = await pool.query('SELECT COUNT(*) as total FROM pacientes');
res.json({ data: rows, total: parseInt(total[0].total) });
} catch (err) {
res.status(500).json({ error: err.message });
}
@@ -1492,6 +1503,71 @@ app.post('/api/sync/pull', tenantGuard, async (req, res) => {
}
});
// Import patients from Apps Script legacy sheet (PACIENTES-CONSULTT-CLINIC tab)
app.post('/api/sync/import-legacy', tenantGuard, async (req, res) => {
const logs = [];
try {
const sheets = await getSheetsClient();
logs.push('[←] LENDO ABA PACIENTES-CONSULTT-CLINIC...');
const realTab = await ensureSheetTab(sheets, 'PACIENTES-CONSULTT-CLINIC');
const resp = await sheets.spreadsheets.values.get({
spreadsheetId: SHEETS_BACKUP_ID,
range: `${realTab}!A:ZZ`
});
const sheetRows = resp.data.values || [];
if (sheetRows.length < 2) {
logs.push('[AVISO] ABA VAZIA OU SEM DADOS.');
return res.json({ success: true, logs, imported: 0 });
}
const headers = sheetRows[0].map(h => h.trim());
const dataRows = sheetRows.slice(1).filter(r => r.some(c => c && c.trim() !== ''));
logs.push(`[OK] ${dataRows.length} LINHAS ENCONTRADAS`);
const idx = h => headers.indexOf(h);
const iNome = idx('Nome'); const iSobre = idx('SobreNome');
const iCpf = idx('CPF'); const iCred = idx('Credenciada');
const iNasci = idx('DataNasci'); const iTel = idx('TefefonePrincipal');
let imported = 0, skipped = 0;
for (let i = 0; i < dataRows.length; i++) {
const row = dataRows[i];
const get = j => (j >= 0 && j < row.length ? (row[j] || '').trim() : '');
const nome = [get(iNome), get(iSobre)].filter(Boolean).join(' ').toUpperCase();
if (!nome) { skipped++; continue; }
const cpfRaw = get(iCpf);
const cpfDigits = cpfRaw.replace(/\D/g, '');
const id = cpfDigits || `legacy_${i + 1}`;
try {
const trunc = (v, n) => v ? v.substring(0, n) : null;
await pool.query(
`INSERT INTO pacientes (id,nome,cpf,telefone,email,ultimotratamento,convenio,datanascimento)
VALUES ($1,$2,$3,$4,$5,$6,$7,$8)
ON CONFLICT (id) DO UPDATE SET
nome=EXCLUDED.nome,cpf=EXCLUDED.cpf,telefone=EXCLUDED.telefone,
convenio=EXCLUDED.convenio,datanascimento=EXCLUDED.datanascimento`,
[trunc(id,50), trunc(nome,255), trunc(cpfRaw,20)||null, trunc(get(iTel),20)||null, null, null, trunc(get(iCred),100)||null, trunc(get(iNasci),20)||null]
);
imported++;
} catch (e) {
skipped++;
if (skipped <= 3) logs.push(`[AVISO] linha ${i + 2}: ${e.message}`);
}
}
logs.push(`[✓] ${imported} PACIENTES IMPORTADOS, ${skipped} IGNORADOS`);
if (imported > 0) {
await saveSyncStatus({ pacientes: { lastPush: new Date().toISOString(), count: imported, source: 'legacy' } });
schedulePush('pacientes');
}
res.json({ success: true, logs, imported, skipped });
} catch (err) {
const msg = err.message === 'NO_GOOGLE_TOKEN'
? 'NENHUMA CONTA GOOGLE CONECTADA.'
: err.message;
logs.push(`[ERRO] ${msg}`);
res.status(err.message === 'NO_GOOGLE_TOKEN' ? 400 : 500).json({ success: false, logs });
}
});
// initTables() was removed as database is native PostgreSQL and schema DDL is maintained externally.
async function generateIntelligentNotifications() {
if (!pool) return;