refactor(backend): migrate all queries to native PostgreSQL, remove MySQL shim
- Remove translateQuery function, pool.query override, and pool.getConnection mock entirely
- Replace all ? placeholders with $N positional parameters throughout
- Replace INSERT INTO t SET ? with buildInsert() helper across pacientes, dentistas, especialidades, planos, procedimentos, financeiro, leads, gto_items
- Replace UPDATE t SET ? WHERE id = ? with buildUpdate() helper across the same tables
- Replace all pool.getConnection()/beginTransaction/commit/rollback blocks with withTransaction()
- Convert ON DUPLICATE KEY UPDATE to ON CONFLICT DO UPDATE SET in dentistas/register (usuarios, vinculos, dentistas tables)
- Fix dentistas/register to use withTransaction instead of SET FOREIGN_KEY_CHECKS
- Fix all [rows] array destructuring to { rows } object destructuring (PG native result shape)
- Fix guias dynamic WHERE builder to use incrementing $N parameter indices
- Fix double-quoted string literal bug in procedimentos categoria query ("Particular" → 'Particular')
- Quote camelCase column names in gto_items/gto_builders queries ("builderId", "valorTotal", etc.)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
+171
-262
@@ -22,94 +22,6 @@ const pool = new Pool({
|
||||
pool.on('connect', () => console.log('[PG] Connected to PostgreSQL'));
|
||||
pool.on('error', (err) => console.error('[PG] Pool error:', err.message));
|
||||
|
||||
// =============================================================================
|
||||
// PostgreSQL Translator & Compatibility Wrapper
|
||||
// =============================================================================
|
||||
function translateQuery(query, params) {
|
||||
if (typeof query !== 'string') return { sql: query, pgParams: params };
|
||||
let sql = query;
|
||||
let pgParams = params ? (Array.isArray(params) ? [...params] : [params]) : [];
|
||||
|
||||
sql = sql.replace(/`([^`]+)`/g, '"$1"');
|
||||
|
||||
if (sql.match(/INSERT\s+INTO\s+([^\s]+)\s+SET\s+\?/i)) {
|
||||
const table = sql.match(/INSERT\s+INTO\s+([^\s]+)\s+SET\s+\?/i)[1].replace(/"/g, '');
|
||||
const dataObj = pgParams[0];
|
||||
if (dataObj && typeof dataObj === 'object') {
|
||||
const keys = Object.keys(dataObj);
|
||||
const cols = keys.map(k => `"${k}"`).join(', ');
|
||||
const valuesPlaceholder = keys.map((_, idx) => `$${idx + 1}`).join(', ');
|
||||
sql = `INSERT INTO "${table}" (${cols}) VALUES (${valuesPlaceholder})`;
|
||||
pgParams = keys.map(k => dataObj[k]);
|
||||
}
|
||||
}
|
||||
|
||||
if (sql.match(/UPDATE\s+([^\s]+)\s+SET\s+\?\s+WHERE\s+(.+)/i)) {
|
||||
const match = sql.match(/UPDATE\s+([^\s]+)\s+SET\s+\?\s+WHERE\s+(.+)/i);
|
||||
const table = match[1].replace(/"/g, '');
|
||||
const whereClause = match[2];
|
||||
const dataObj = pgParams[0];
|
||||
const extraParams = pgParams.slice(1);
|
||||
if (dataObj && typeof dataObj === 'object') {
|
||||
const keys = Object.keys(dataObj);
|
||||
let paramIdx = 1;
|
||||
const setClause = keys.map(k => `"${k}" = $${paramIdx++}`).join(', ');
|
||||
let postgresWhere = whereClause;
|
||||
let tempIdx = paramIdx;
|
||||
postgresWhere = postgresWhere.replace(/\?/g, () => `$${tempIdx++}`);
|
||||
sql = `UPDATE "${table}" SET ${setClause} WHERE ${postgresWhere}`;
|
||||
pgParams = [...keys.map(k => dataObj[k]), ...extraParams];
|
||||
}
|
||||
}
|
||||
|
||||
let count = 1;
|
||||
sql = sql.replace(/\?/g, () => `$${count++}`);
|
||||
|
||||
if (sql.match(/ON\s+DUPLICATE\s+KEY\s+UPDATE/i)) {
|
||||
if (sql.toLowerCase().includes('settings')) sql = sql.replace(/ON\s+DUPLICATE\s+KEY\s+UPDATE\s+data\s*=\s*VALUES\(data\)|ON\s+DUPLICATE\s+KEY\s+UPDATE\s+data\s*=\s*\$4|ON\s+DUPLICATE\s+KEY\s+UPDATE\s+data\s*=\s*EXCLUDED\.data/i, 'ON CONFLICT (id) DO UPDATE SET data = EXCLUDED.data');
|
||||
else if (sql.toLowerCase().includes('google_tokens')) sql = sql.replace(/ON\s+DUPLICATE\s+KEY\s+UPDATE[\s\S]+/i, 'ON CONFLICT (owner_id) DO UPDATE SET refresh_token = EXCLUDED.refresh_token, access_token = EXCLUDED.access_token, expiry_date = EXCLUDED.expiry_date');
|
||||
else if (sql.toLowerCase().includes('usuarios')) sql = sql.replace(/ON\s+DUPLICATE\s+KEY\s+UPDATE[\s\S]+/i, 'ON CONFLICT (id) DO UPDATE SET nome = EXCLUDED.nome, cro = EXCLUDED.cro, cro_uf = EXCLUDED.cro_uf, celular = EXCLUDED.celular, especialidade = EXCLUDED.especialidade');
|
||||
else if (sql.toLowerCase().includes('vinculos')) sql = sql.replace(/ON\s+DUPLICATE\s+KEY\s+UPDATE[\s\S]+/i, 'ON CONFLICT (usuario_id, clinica_id) DO UPDATE SET role = EXCLUDED.role');
|
||||
else if (sql.toLowerCase().includes('dentistas_horarios')) sql = sql.replace(/ON\s+DUPLICATE\s+KEY\s+UPDATE[\s\S]+/i, 'ON CONFLICT (id) DO UPDATE SET dia_semana = EXCLUDED.dia_semana, hora_inicio = EXCLUDED.hora_inicio, hora_fim = EXCLUDED.hora_fim, ativo = EXCLUDED.ativo');
|
||||
else if (sql.toLowerCase().includes('dentistas')) sql = sql.replace(/ON\s+DUPLICATE\s+KEY\s+UPDATE[\s\S]+/i, 'ON CONFLICT (id) DO UPDATE SET nome = EXCLUDED.nome, especialidade = EXCLUDED.especialidade');
|
||||
else if (sql.toLowerCase().includes('procedimentos_valores_planos')) sql = sql.replace(/ON\s+DUPLICATE\s+KEY\s+UPDATE[\s\S]+/i, 'ON CONFLICT (id) DO UPDATE SET valor = EXCLUDED.valor');
|
||||
else if (sql.toLowerCase().includes('procedimentos')) sql = sql.replace(/ON\s+DUPLICATE\s+KEY\s+UPDATE[\s\S]+/i, 'ON CONFLICT (id) DO UPDATE SET nome = EXCLUDED.nome, "especialidadeId" = EXCLUDED."especialidadeId", "especialidadeNome" = EXCLUDED."especialidadeNome", "valorParticular" = EXCLUDED."valorParticular", "codigoInterno" = EXCLUDED."codigoInterno", categoria = EXCLUDED.categoria, tipo_regiao = EXCLUDED.tipo_regiao, exige_face = EXCLUDED.exige_face, codigo = EXCLUDED.codigo');
|
||||
}
|
||||
|
||||
sql = sql.replace(/DATE_FORMAT\(([^,]+),\s*['"]%Y-%m['"]\)/gi, "TO_CHAR($1, 'YYYY-MM')");
|
||||
sql = sql.replace(/DATE_SUB\(CURDATE\(\),\s*INTERVAL\s+(\d+)\s+MONTH\)/gi, "CURRENT_DATE - INTERVAL '$1 MONTH'");
|
||||
if (sql.match(/SET\s+FOREIGN_KEY_CHECKS\s*=\s*\d+/i)) sql = "SELECT 1";
|
||||
|
||||
return { sql, pgParams };
|
||||
}
|
||||
|
||||
const originalPoolQuery = pool.query.bind(pool);
|
||||
pool.query = async function (queryText, params) {
|
||||
const { sql, pgParams } = translateQuery(queryText, params);
|
||||
const result = await originalPoolQuery(sql, pgParams);
|
||||
const resArray = [result.rows, result.fields];
|
||||
resArray.rows = result.rows;
|
||||
resArray.rowCount = result.rowCount;
|
||||
return resArray;
|
||||
};
|
||||
|
||||
pool.getConnection = async function () {
|
||||
const client = await pool.connect();
|
||||
const originalClientQuery = client.query.bind(client);
|
||||
client.query = async function (queryText, params) {
|
||||
const { sql, pgParams } = translateQuery(queryText, params);
|
||||
const result = await originalClientQuery(sql, pgParams);
|
||||
const resArray = [result.rows, result.fields];
|
||||
resArray.rows = result.rows;
|
||||
resArray.rowCount = result.rowCount;
|
||||
return resArray;
|
||||
};
|
||||
client.beginTransaction = async () => await originalClientQuery('BEGIN');
|
||||
client.commit = async () => await originalClientQuery('COMMIT');
|
||||
client.rollback = async () => await originalClientQuery('ROLLBACK');
|
||||
return client;
|
||||
};
|
||||
|
||||
// Helper: transaction wrapper
|
||||
async function withTransaction(fn) {
|
||||
const client = await pool.connect();
|
||||
@@ -312,7 +224,7 @@ app.get('/api/oauth2callback', async (req, res) => {
|
||||
|
||||
app.get('/api/auth/google/status', async (req, res) => {
|
||||
try {
|
||||
const [rows] = await pool.query('SELECT owner_id, updated_at FROM google_tokens');
|
||||
const { rows } = await pool.query('SELECT owner_id, updated_at FROM google_tokens');
|
||||
res.json(rows);
|
||||
} catch (err) { res.status(500).json({ error: err.message }); }
|
||||
});
|
||||
@@ -358,33 +270,32 @@ app.post('/api/dentistas/magic-link', async (req, res) => {
|
||||
app.post('/api/dentistas/register', async (req, res) => {
|
||||
const { id, nome, email, senha, cro, cro_uf, celular, especialidade, clinicaId } = req.body;
|
||||
try {
|
||||
await pool.query('SET FOREIGN_KEY_CHECKS = 0');
|
||||
// 1. Create User
|
||||
await pool.query(`
|
||||
INSERT INTO usuarios (id, nome, email, senha, cro, cro_uf, celular, especialidade)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
||||
ON DUPLICATE KEY UPDATE
|
||||
nome = VALUES(nome), cro = VALUES(cro), cro_uf = VALUES(cro_uf),
|
||||
celular = VALUES(celular), especialidade = VALUES(especialidade)
|
||||
`, [id, nome, email, senha, cro, cro_uf, celular, especialidade]);
|
||||
await withTransaction(async (client) => {
|
||||
// 1. Create or update User
|
||||
await client.query(`
|
||||
INSERT INTO usuarios (id, nome, email, senha, cro, cro_uf, celular, especialidade)
|
||||
VALUES ($1, $2, $3, $4, $5, $6, $7, $8)
|
||||
ON CONFLICT (id) DO UPDATE SET
|
||||
nome = EXCLUDED.nome, cro = EXCLUDED.cro, cro_uf = EXCLUDED.cro_uf,
|
||||
celular = EXCLUDED.celular, especialidade = EXCLUDED.especialidade
|
||||
`, [id, nome, email, senha, cro, cro_uf, celular, especialidade]);
|
||||
|
||||
// 2. Create Link to clinic
|
||||
const vinculoId = `v_${id}_${clinicaId}`;
|
||||
await pool.query(`
|
||||
INSERT INTO vinculos (id, usuario_id, clinica_id, role)
|
||||
VALUES (?, ?, ?, 'dentista')
|
||||
ON DUPLICATE KEY UPDATE role = 'dentista'
|
||||
`, [vinculoId, id, clinicaId]);
|
||||
// 2. Create or update link to clinic
|
||||
const vinculoId = `v_${id}_${clinicaId}`;
|
||||
await client.query(`
|
||||
INSERT INTO vinculos (id, usuario_id, clinica_id, role)
|
||||
VALUES ($1, $2, $3, 'dentista')
|
||||
ON CONFLICT (usuario_id, clinica_id) DO UPDATE SET role = 'dentista'
|
||||
`, [vinculoId, id, clinicaId]);
|
||||
|
||||
// 3. Create Dentist record for agenda
|
||||
await pool.query(`
|
||||
INSERT INTO dentistas (id, clinica_id, nome, email, telefone, especialidade, corAgenda)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?)
|
||||
ON DUPLICATE KEY UPDATE
|
||||
nome = VALUES(nome), especialidade = VALUES(especialidade)
|
||||
`, [id, clinicaId, nome, email, celular, especialidade, '#2563eb']);
|
||||
|
||||
await pool.query('SET FOREIGN_KEY_CHECKS = 1');
|
||||
// 3. Create or update Dentist record for agenda
|
||||
await client.query(`
|
||||
INSERT INTO dentistas (id, clinica_id, nome, email, telefone, especialidade, "corAgenda")
|
||||
VALUES ($1, $2, $3, $4, $5, $6, $7)
|
||||
ON CONFLICT (id) DO UPDATE SET
|
||||
nome = EXCLUDED.nome, especialidade = EXCLUDED.especialidade
|
||||
`, [id, clinicaId, nome, email, celular, especialidade, '#2563eb']);
|
||||
});
|
||||
res.json({ success: true });
|
||||
} catch (err) { res.status(500).json({ error: err.message }); }
|
||||
});
|
||||
@@ -392,7 +303,7 @@ app.post('/api/dentistas/register', async (req, res) => {
|
||||
app.get('/api/google/events', async (req, res) => {
|
||||
try {
|
||||
// 1. Check for connected accounts in google_tokens
|
||||
const [tokenRows] = await pool.query('SELECT * FROM google_tokens');
|
||||
const { rows: tokenRows } = await pool.query('SELECT * FROM google_tokens');
|
||||
const allEvents = [];
|
||||
const now = new Date();
|
||||
const timeMin = now.toISOString();
|
||||
@@ -418,7 +329,7 @@ app.get('/api/google/events', async (req, res) => {
|
||||
if (response.data.items) {
|
||||
// Try to resolve owner name
|
||||
let ownerName = tokenData.owner_id;
|
||||
const [dent] = await pool.query('SELECT nome FROM dentistas WHERE id = ?', [tokenData.owner_id]);
|
||||
const { rows: dent } = await pool.query('SELECT nome FROM dentistas WHERE id = $1', [tokenData.owner_id]);
|
||||
if (dent[0]) ownerName = dent[0].nome;
|
||||
|
||||
const mapped = response.data.items.map(item => ({
|
||||
@@ -439,9 +350,9 @@ app.get('/api/google/events', async (req, res) => {
|
||||
}
|
||||
|
||||
// 2. Legacy Support: Check Settings for Public Calendars (API KEY)
|
||||
const [rows] = await pool.query("SELECT data FROM settings WHERE id = 'main'");
|
||||
if (rows[0]) {
|
||||
const settings = JSON.parse(rows[0].data);
|
||||
const { rows: settingsRows } = await pool.query("SELECT data FROM settings WHERE id = 'main'");
|
||||
if (settingsRows[0]) {
|
||||
const settings = JSON.parse(settingsRows[0].data);
|
||||
const apiKey = settings.googleApiKey;
|
||||
if (apiKey) {
|
||||
const legacyIds = [];
|
||||
@@ -450,7 +361,7 @@ app.get('/api/google/events', async (req, res) => {
|
||||
if (settings.googleCalendarIds) {
|
||||
for (const [dentistId, calId] of Object.entries(settings.googleCalendarIds)) {
|
||||
if (calId && !tokenRows.find(t => t.owner_id === dentistId)) {
|
||||
const [dName] = await pool.query('SELECT nome FROM dentistas WHERE id = ?', [dentistId]);
|
||||
const { rows: dName } = await pool.query('SELECT nome FROM dentistas WHERE id = $1', [dentistId]);
|
||||
legacyIds.push({ id: calId, owner: dName[0]?.nome || 'DENTISTA' });
|
||||
}
|
||||
}
|
||||
@@ -503,7 +414,7 @@ app.get('/api/dashboard/stats', async (req, res) => {
|
||||
|
||||
// Growth data (last 6 months)
|
||||
const { rows: growth } = await pool.query(`
|
||||
SELECT
|
||||
SELECT
|
||||
TO_CHAR(datavencimento, 'YYYY-MM') as mes,
|
||||
SUM(CASE WHEN tipo = 'RECEITA' OR tipo IS NULL THEN valor ELSE 0 END) as receita,
|
||||
SUM(CASE WHEN tipo = 'DESPESA' THEN valor ELSE 0 END) as despesa
|
||||
@@ -576,7 +487,7 @@ 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');
|
||||
const { rows } = await pool.query('SELECT * FROM pacientes');
|
||||
res.json(rows);
|
||||
} catch (err) {
|
||||
res.status(500).json({ error: err.message });
|
||||
@@ -586,7 +497,8 @@ app.get('/api/pacientes', async (req, res) => {
|
||||
app.post('/api/pacientes', async (req, res) => {
|
||||
try {
|
||||
const p = req.body;
|
||||
await pool.query('INSERT INTO pacientes SET ?', p);
|
||||
const ins = buildInsert('pacientes', p);
|
||||
await pool.query(ins.text, ins.values);
|
||||
res.json({ success: true, data: p });
|
||||
} catch (err) {
|
||||
res.status(500).json({ error: err.message });
|
||||
@@ -595,7 +507,8 @@ app.post('/api/pacientes', async (req, res) => {
|
||||
|
||||
app.put('/api/pacientes/:id', async (req, res) => {
|
||||
try {
|
||||
await pool.query('UPDATE pacientes SET ? WHERE id = ?', [req.body, req.params.id]);
|
||||
const upd = buildUpdate('pacientes', req.body, 'id', req.params.id);
|
||||
await pool.query(upd.text, upd.values);
|
||||
res.json({ success: true });
|
||||
} catch (err) {
|
||||
res.status(500).json({ error: err.message });
|
||||
@@ -609,11 +522,11 @@ app.get('/api/dentistas', async (req, res) => {
|
||||
let query = 'SELECT * FROM dentistas';
|
||||
const params = [];
|
||||
if (clinicaId) {
|
||||
query += ' WHERE clinica_id = ?';
|
||||
query += ' WHERE clinica_id = $1';
|
||||
params.push(clinicaId);
|
||||
}
|
||||
query += ' ORDER BY ordem ASC, nome ASC';
|
||||
const [rows] = await pool.query(query, params);
|
||||
const { rows } = await pool.query(query, params);
|
||||
res.json(rows.map(d => ({ ...d, ativo: !!d.ativo })));
|
||||
} catch (err) { res.status(500).json({ error: err.message }); }
|
||||
});
|
||||
@@ -622,62 +535,53 @@ app.post('/api/dentistas', async (req, res) => {
|
||||
try {
|
||||
const d = req.body;
|
||||
// Automatically set order to the end
|
||||
const [counts] = await pool.query('SELECT COUNT(*) as count FROM dentistas');
|
||||
const ordem = counts[0].count;
|
||||
await pool.query('INSERT INTO dentistas SET ?', { ...d, ordem });
|
||||
const { rows: counts } = await pool.query('SELECT COUNT(*) as count FROM dentistas');
|
||||
const ordem = parseInt(counts[0].count);
|
||||
const ins = buildInsert('dentistas', { ...d, ordem });
|
||||
await pool.query(ins.text, ins.values);
|
||||
res.json({ success: true });
|
||||
} catch (err) { res.status(500).json({ error: err.message }); }
|
||||
});
|
||||
|
||||
app.put('/api/dentistas/reorder', async (req, res) => {
|
||||
const { orders } = req.body; // Array of {id, ordem}
|
||||
const connection = await pool.getConnection();
|
||||
try {
|
||||
await connection.beginTransaction();
|
||||
for (const item of orders) {
|
||||
await connection.query('UPDATE dentistas SET ordem = ? WHERE id = ?', [item.ordem, item.id]);
|
||||
}
|
||||
await connection.commit();
|
||||
await withTransaction(async (client) => {
|
||||
for (const item of orders) {
|
||||
await client.query('UPDATE dentistas SET ordem = $1 WHERE id = $2', [item.ordem, item.id]);
|
||||
}
|
||||
});
|
||||
res.json({ success: true });
|
||||
} catch (err) {
|
||||
await connection.rollback();
|
||||
res.status(500).json({ error: err.message });
|
||||
} finally { connection.release(); }
|
||||
}
|
||||
});
|
||||
|
||||
app.put('/api/dentistas/:id', async (req, res) => {
|
||||
try {
|
||||
await pool.query('UPDATE dentistas SET ? WHERE id = ?', [req.body, req.params.id]);
|
||||
const upd = buildUpdate('dentistas', req.body, 'id', req.params.id);
|
||||
await pool.query(upd.text, upd.values);
|
||||
res.json({ success: true });
|
||||
} catch (err) { res.status(500).json({ error: err.message }); }
|
||||
});
|
||||
|
||||
app.delete('/api/dentistas/:id', async (req, res) => {
|
||||
const connection = await pool.getConnection();
|
||||
try {
|
||||
const { id } = req.params;
|
||||
await connection.beginTransaction();
|
||||
|
||||
// 1. Remove Google Tokens associated with this dentist
|
||||
await connection.query('DELETE FROM google_tokens WHERE owner_id = ?', [id]);
|
||||
|
||||
// 2. Remove clinical links (if any)
|
||||
await connection.query('DELETE FROM vinculos WHERE usuario_id = ?', [id]);
|
||||
|
||||
// 3. Set to NULL in agendamentos if it exists
|
||||
await connection.query('UPDATE agendamentos SET dentistaId = NULL WHERE dentistaId = ?', [id]);
|
||||
|
||||
// 4. Delete from dentistas table
|
||||
await connection.query('DELETE FROM dentistas WHERE id = ?', [id]);
|
||||
|
||||
await connection.commit();
|
||||
await withTransaction(async (client) => {
|
||||
// 1. Remove Google Tokens associated with this dentist
|
||||
await client.query('DELETE FROM google_tokens WHERE owner_id = $1', [id]);
|
||||
// 2. Remove clinical links (if any)
|
||||
await client.query('DELETE FROM vinculos WHERE usuario_id = $1', [id]);
|
||||
// 3. Set to NULL in agendamentos if it exists
|
||||
await client.query('UPDATE agendamentos SET dentistaid = NULL WHERE dentistaid = $1', [id]);
|
||||
// 4. Delete from dentistas table
|
||||
await client.query('DELETE FROM dentistas WHERE id = $1', [id]);
|
||||
});
|
||||
res.json({ success: true });
|
||||
} catch (err) {
|
||||
await connection.rollback();
|
||||
console.error('Error deleting dentist:', err);
|
||||
res.status(500).json({ error: err.message });
|
||||
} finally {
|
||||
connection.release();
|
||||
}
|
||||
});
|
||||
|
||||
@@ -686,14 +590,14 @@ app.get('/api/dentistas/:dentistaId/horarios', async (req, res) => {
|
||||
try {
|
||||
const { dentistaId } = req.params;
|
||||
const { clinicaId } = req.query;
|
||||
let query = 'SELECT * FROM dentistas_horarios WHERE dentista_id = ?';
|
||||
let query = 'SELECT * FROM dentistas_horarios WHERE dentista_id = $1';
|
||||
const params = [dentistaId];
|
||||
if (clinicaId) {
|
||||
query += ' AND clinica_id = ?';
|
||||
query += ' AND clinica_id = $2';
|
||||
params.push(clinicaId);
|
||||
}
|
||||
query += ' ORDER BY dia_semana ASC, hora_inicio ASC';
|
||||
const [rows] = await pool.query(query, params);
|
||||
const { rows } = await pool.query(query, params);
|
||||
res.json(rows);
|
||||
} catch (err) { res.status(500).json({ error: err.message }); }
|
||||
});
|
||||
@@ -701,20 +605,21 @@ app.get('/api/dentistas/:dentistaId/horarios', async (req, res) => {
|
||||
app.post('/api/dentistas/horarios', async (req, res) => {
|
||||
try {
|
||||
const { id, dentista_id, clinica_id, dia_semana, hora_inicio, hora_fim, ativo } = req.body;
|
||||
const horaId = id || `h_${Math.random().toString(36).substring(2, 9)}`;
|
||||
await pool.query(`
|
||||
INSERT INTO dentistas_horarios (id, dentista_id, clinica_id, dia_semana, hora_inicio, hora_fim, ativo)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?)
|
||||
ON DUPLICATE KEY UPDATE
|
||||
dia_semana = VALUES(dia_semana), hora_inicio = VALUES(hora_inicio),
|
||||
hora_fim = VALUES(hora_fim), ativo = VALUES(ativo)
|
||||
`, [id || `h_${Math.random().toString(36).substring(2, 9)}`, dentista_id, clinica_id, dia_semana, hora_inicio, hora_fim, ativo ?? 1]);
|
||||
VALUES ($1, $2, $3, $4, $5, $6, $7)
|
||||
ON CONFLICT (id) DO UPDATE SET
|
||||
dia_semana = EXCLUDED.dia_semana, hora_inicio = EXCLUDED.hora_inicio,
|
||||
hora_fim = EXCLUDED.hora_fim, ativo = EXCLUDED.ativo
|
||||
`, [horaId, dentista_id, clinica_id, dia_semana, hora_inicio, hora_fim, ativo ?? 1]);
|
||||
res.json({ success: true });
|
||||
} catch (err) { res.status(500).json({ error: err.message }); }
|
||||
});
|
||||
|
||||
app.delete('/api/dentistas/horarios/:id', async (req, res) => {
|
||||
try {
|
||||
await pool.query('DELETE FROM dentistas_horarios WHERE id = ?', [req.params.id]);
|
||||
await pool.query('DELETE FROM dentistas_horarios WHERE id = $1', [req.params.id]);
|
||||
res.json({ success: true });
|
||||
} catch (err) { res.status(500).json({ error: err.message }); }
|
||||
});
|
||||
@@ -727,17 +632,17 @@ app.get('/api/reports/productivity', async (req, res) => {
|
||||
|
||||
// Financial Totals
|
||||
const { rows: finRows } = await pool.query(`
|
||||
SELECT
|
||||
SELECT
|
||||
SUM(CASE WHEN tipo = 'RECEITA' AND status = 'Pago' THEN valor ELSE 0 END) as receita,
|
||||
SUM(CASE WHEN tipo = 'DESPESA' AND status = 'Pago' THEN valor ELSE 0 END) as despesa,
|
||||
COUNT(*) as totalTransacoes
|
||||
FROM financeiro
|
||||
FROM financeiro
|
||||
WHERE clinica_id = $1 AND datavencimento BETWEEN $2 AND $3
|
||||
`, [clinicaId, start || '2000-01-01', end || '2100-01-01']);
|
||||
|
||||
// Appointments by Dentist
|
||||
const { rows: agRows } = await pool.query(`
|
||||
SELECT
|
||||
SELECT
|
||||
d.nome as dentista,
|
||||
COUNT(a.id) as totalAgendamentos,
|
||||
SUM(CASE WHEN a.status = 'Concluido' THEN 1 ELSE 0 END) as concluidos,
|
||||
@@ -773,8 +678,8 @@ app.post('/api/agendamentos', async (req, res) => {
|
||||
// Lock check: SELECT the conflicting row FOR UPDATE to prevent race conditions
|
||||
if (dentistaId && start) {
|
||||
const { rows: conflict } = await client.query(
|
||||
`SELECT id FROM agendamentos
|
||||
WHERE dentistaid = $1 AND start_time = $2
|
||||
`SELECT id FROM agendamentos
|
||||
WHERE dentistaid = $1 AND start_time = $2
|
||||
LIMIT 1 FOR UPDATE`,
|
||||
[dentistaId, start]
|
||||
);
|
||||
@@ -784,7 +689,7 @@ app.post('/api/agendamentos', async (req, res) => {
|
||||
}
|
||||
|
||||
const id = rest.id || `ag_${Date.now()}_${Math.random().toString(36).substring(2, 7)}`;
|
||||
|
||||
|
||||
// Map keys from camelCase/old attributes to correct lowercase PG columns if necessary
|
||||
const payload = {
|
||||
id,
|
||||
@@ -849,7 +754,7 @@ app.put('/api/agendamentos/:id', async (req, res) => {
|
||||
|
||||
app.delete('/api/agendamentos/:id', async (req, res) => {
|
||||
try {
|
||||
await pool.query('DELETE FROM agendamentos WHERE id = ?', [req.params.id]);
|
||||
await pool.query('DELETE FROM agendamentos WHERE id = $1', [req.params.id]);
|
||||
res.json({ success: true });
|
||||
} catch (err) { res.status(500).json({ error: err.message }); }
|
||||
});
|
||||
@@ -857,7 +762,7 @@ app.delete('/api/agendamentos/:id', async (req, res) => {
|
||||
// --- ESPECIALIDADES ---
|
||||
app.get('/api/especialidades', async (req, res) => {
|
||||
try {
|
||||
const [rows] = await pool.query('SELECT * FROM especialidades ORDER BY ordem ASC, nome ASC');
|
||||
const { rows } = await pool.query('SELECT * FROM especialidades ORDER BY ordem ASC, nome ASC');
|
||||
res.json(rows);
|
||||
} catch (err) { res.status(500).json({ error: err.message }); }
|
||||
});
|
||||
@@ -865,39 +770,39 @@ app.get('/api/especialidades', async (req, res) => {
|
||||
app.post('/api/especialidades', async (req, res) => {
|
||||
try {
|
||||
// Automatically set order to the end
|
||||
const [counts] = await pool.query('SELECT COUNT(*) as count FROM especialidades');
|
||||
const ordem = counts[0].count;
|
||||
await pool.query('INSERT INTO especialidades SET ?', { ...req.body, ordem });
|
||||
const { rows: counts } = await pool.query('SELECT COUNT(*) as count FROM especialidades');
|
||||
const ordem = parseInt(counts[0].count);
|
||||
const ins = buildInsert('especialidades', { ...req.body, ordem });
|
||||
await pool.query(ins.text, ins.values);
|
||||
res.json({ success: true });
|
||||
} catch (err) { res.status(500).json({ error: err.message }); }
|
||||
});
|
||||
|
||||
app.put('/api/especialidades/reorder', async (req, res) => {
|
||||
const { orders } = req.body; // Array of {id, ordem}
|
||||
const connection = await pool.getConnection();
|
||||
try {
|
||||
await connection.beginTransaction();
|
||||
for (const item of orders) {
|
||||
await connection.query('UPDATE especialidades SET ordem = ? WHERE id = ?', [item.ordem, item.id]);
|
||||
}
|
||||
await connection.commit();
|
||||
await withTransaction(async (client) => {
|
||||
for (const item of orders) {
|
||||
await client.query('UPDATE especialidades SET ordem = $1 WHERE id = $2', [item.ordem, item.id]);
|
||||
}
|
||||
});
|
||||
res.json({ success: true });
|
||||
} catch (err) {
|
||||
await connection.rollback();
|
||||
res.status(500).json({ error: err.message });
|
||||
} finally { connection.release(); }
|
||||
}
|
||||
});
|
||||
|
||||
app.put('/api/especialidades/:id', async (req, res) => {
|
||||
try {
|
||||
await pool.query('UPDATE especialidades SET ? WHERE id = ?', [req.body, req.params.id]);
|
||||
const upd = buildUpdate('especialidades', req.body, 'id', req.params.id);
|
||||
await pool.query(upd.text, upd.values);
|
||||
res.json({ success: true });
|
||||
} catch (err) { res.status(500).json({ error: err.message }); }
|
||||
});
|
||||
|
||||
app.delete('/api/especialidades/:id', async (req, res) => {
|
||||
try {
|
||||
await pool.query('DELETE FROM especialidades WHERE id = ?', [req.params.id]);
|
||||
await pool.query('DELETE FROM especialidades WHERE id = $1', [req.params.id]);
|
||||
res.json({ success: true });
|
||||
} catch (err) { res.status(500).json({ error: err.message }); }
|
||||
});
|
||||
@@ -905,28 +810,30 @@ app.delete('/api/especialidades/:id', async (req, res) => {
|
||||
// --- PLANOS ---
|
||||
app.get('/api/planos', async (req, res) => {
|
||||
try {
|
||||
const [rows] = await pool.query('SELECT * FROM planos');
|
||||
const { rows } = await pool.query('SELECT * FROM planos');
|
||||
res.json(rows);
|
||||
} catch (err) { res.status(500).json({ error: err.message }); }
|
||||
});
|
||||
|
||||
app.post('/api/planos', async (req, res) => {
|
||||
try {
|
||||
await pool.query('INSERT INTO planos SET ?', req.body);
|
||||
const ins = buildInsert('planos', req.body);
|
||||
await pool.query(ins.text, ins.values);
|
||||
res.json({ success: true });
|
||||
} catch (err) { res.status(500).json({ error: err.message }); }
|
||||
});
|
||||
|
||||
app.put('/api/planos/:id', async (req, res) => {
|
||||
try {
|
||||
await pool.query('UPDATE planos SET ? WHERE id = ?', [req.body, req.params.id]);
|
||||
const upd = buildUpdate('planos', req.body, 'id', req.params.id);
|
||||
await pool.query(upd.text, upd.values);
|
||||
res.json({ success: true });
|
||||
} catch (err) { res.status(500).json({ error: err.message }); }
|
||||
});
|
||||
|
||||
app.delete('/api/planos/:id', async (req, res) => {
|
||||
try {
|
||||
await pool.query('DELETE FROM planos WHERE id = ?', [req.params.id]);
|
||||
await pool.query('DELETE FROM planos WHERE id = $1', [req.params.id]);
|
||||
res.json({ success: true });
|
||||
} catch (err) { res.status(500).json({ error: err.message }); }
|
||||
});
|
||||
@@ -934,8 +841,8 @@ app.delete('/api/planos/:id', async (req, res) => {
|
||||
// --- PROCEDIMENTOS ---
|
||||
app.get('/api/procedimentos', async (req, res) => {
|
||||
try {
|
||||
const [procs] = await pool.query('SELECT * FROM procedimentos ORDER BY ordem ASC, nome ASC');
|
||||
const [valores] = await pool.query('SELECT * FROM procedimentos_valores_planos');
|
||||
const { rows: procs } = await pool.query('SELECT * FROM procedimentos ORDER BY ordem ASC, nome ASC');
|
||||
const { rows: valores } = await pool.query('SELECT * FROM procedimentos_valores_planos');
|
||||
|
||||
const data = procs.map(p => ({
|
||||
...p,
|
||||
@@ -949,21 +856,19 @@ app.get('/api/procedimentos', async (req, res) => {
|
||||
});
|
||||
|
||||
app.post('/api/procedimentos', async (req, res) => {
|
||||
const connection = await pool.getConnection();
|
||||
try {
|
||||
await connection.beginTransaction();
|
||||
const { valoresPlanos, ...proc } = req.body;
|
||||
|
||||
// Auto-generate codigoInterno for PARTICULAR
|
||||
let codigoInternoVal = proc.codigoInterno;
|
||||
if (proc.categoria === 'Particular' && (!codigoInternoVal || codigoInternoVal === '')) {
|
||||
const [rows] = await connection.query('SELECT COUNT(*) as count FROM procedimentos WHERE categoria = "Particular"');
|
||||
codigoInternoVal = (rows[0].count + 1).toString();
|
||||
const { rows: partCount } = await pool.query("SELECT COUNT(*) as count FROM procedimentos WHERE categoria = 'Particular'");
|
||||
codigoInternoVal = (parseInt(partCount[0].count) + 1).toString();
|
||||
}
|
||||
|
||||
// Automatically set order to the end
|
||||
const [counts] = await connection.query('SELECT COUNT(*) as count FROM procedimentos');
|
||||
const ordemValue = counts[0].count;
|
||||
const { rows: counts } = await pool.query('SELECT COUNT(*) as count FROM procedimentos');
|
||||
const ordemValue = parseInt(counts[0].count);
|
||||
|
||||
const payload = {
|
||||
...proc,
|
||||
@@ -972,68 +877,63 @@ app.post('/api/procedimentos', async (req, res) => {
|
||||
tipo_regiao: proc.tipo_regiao || 'GERAL',
|
||||
exige_face: proc.exige_face ? 1 : 0
|
||||
};
|
||||
|
||||
if (!payload.id) payload.id = Math.random().toString(36).substring(2, 10).toUpperCase();
|
||||
|
||||
await connection.query('INSERT INTO procedimentos SET ?', payload);
|
||||
if (valoresPlanos && valoresPlanos.length > 0) {
|
||||
for (const v of valoresPlanos) {
|
||||
await connection.query('INSERT INTO procedimentos_valores_planos SET ?', { ...v, procedimentoId: payload.id });
|
||||
await withTransaction(async (client) => {
|
||||
const ins = buildInsert('procedimentos', payload);
|
||||
await client.query(ins.text, ins.values);
|
||||
if (valoresPlanos && valoresPlanos.length > 0) {
|
||||
for (const v of valoresPlanos) {
|
||||
const vIns = buildInsert('procedimentos_valores_planos', { ...v, procedimentoId: payload.id });
|
||||
await client.query(vIns.text, vIns.values);
|
||||
}
|
||||
}
|
||||
}
|
||||
await connection.commit();
|
||||
});
|
||||
res.json({ success: true });
|
||||
} catch (err) {
|
||||
await connection.rollback();
|
||||
res.status(500).json({ error: err.message });
|
||||
} finally { connection.release(); }
|
||||
}
|
||||
});
|
||||
|
||||
app.put('/api/procedimentos/:id', async (req, res) => {
|
||||
const connection = await pool.getConnection();
|
||||
try {
|
||||
await connection.beginTransaction();
|
||||
const { valoresPlanos, ...proc } = req.body;
|
||||
const updateData = { ...proc, exige_face: proc.exige_face ? 1 : 0 };
|
||||
|
||||
const updateData = {
|
||||
...proc,
|
||||
exige_face: proc.exige_face ? 1 : 0
|
||||
};
|
||||
|
||||
await connection.query('UPDATE procedimentos SET ? WHERE id = ?', [updateData, req.params.id]);
|
||||
await connection.query('DELETE FROM procedimentos_valores_planos WHERE procedimentoId = ?', [req.params.id]);
|
||||
if (valoresPlanos && valoresPlanos.length > 0) {
|
||||
for (const v of valoresPlanos) {
|
||||
await connection.query('INSERT INTO procedimentos_valores_planos SET ?', { ...v, procedimentoId: req.params.id });
|
||||
await withTransaction(async (client) => {
|
||||
const upd = buildUpdate('procedimentos', updateData, 'id', req.params.id);
|
||||
await client.query(upd.text, upd.values);
|
||||
await client.query('DELETE FROM procedimentos_valores_planos WHERE "procedimentoId" = $1', [req.params.id]);
|
||||
if (valoresPlanos && valoresPlanos.length > 0) {
|
||||
for (const v of valoresPlanos) {
|
||||
const vIns = buildInsert('procedimentos_valores_planos', { ...v, procedimentoId: req.params.id });
|
||||
await client.query(vIns.text, vIns.values);
|
||||
}
|
||||
}
|
||||
}
|
||||
await connection.commit();
|
||||
});
|
||||
res.json({ success: true });
|
||||
} catch (err) {
|
||||
await connection.rollback();
|
||||
res.status(500).json({ error: err.message });
|
||||
} finally { connection.release(); }
|
||||
}
|
||||
});
|
||||
|
||||
app.put('/api/procedimentos/reorder', async (req, res) => {
|
||||
const { orders } = req.body; // Array of {id, ordem}
|
||||
const connection = await pool.getConnection();
|
||||
try {
|
||||
await connection.beginTransaction();
|
||||
for (const item of orders) {
|
||||
await connection.query('UPDATE procedimentos SET ordem = ? WHERE id = ?', [item.ordem, item.id]);
|
||||
}
|
||||
await connection.commit();
|
||||
await withTransaction(async (client) => {
|
||||
for (const item of orders) {
|
||||
await client.query('UPDATE procedimentos SET ordem = $1 WHERE id = $2', [item.ordem, item.id]);
|
||||
}
|
||||
});
|
||||
res.json({ success: true });
|
||||
} catch (err) {
|
||||
await connection.rollback();
|
||||
res.status(500).json({ error: err.message });
|
||||
} finally { connection.release(); }
|
||||
}
|
||||
});
|
||||
|
||||
app.delete('/api/procedimentos/:id', async (req, res) => {
|
||||
try {
|
||||
await pool.query('DELETE FROM procedimentos WHERE id = ?', [req.params.id]);
|
||||
await pool.query('DELETE FROM procedimentos WHERE id = $1', [req.params.id]);
|
||||
res.json({ success: true });
|
||||
} catch (err) { res.status(500).json({ error: err.message }); }
|
||||
});
|
||||
@@ -1041,28 +941,30 @@ app.delete('/api/procedimentos/:id', async (req, res) => {
|
||||
// --- FINANCEIRO ---
|
||||
app.get('/api/financeiro', async (req, res) => {
|
||||
try {
|
||||
const [rows] = await pool.query('SELECT * FROM financeiro');
|
||||
const { rows } = await pool.query('SELECT * FROM financeiro');
|
||||
res.json(rows.map(r => ({ ...r, valor: parseFloat(r.valor || 0) })));
|
||||
} catch (err) { res.status(500).json({ error: err.message }); }
|
||||
});
|
||||
|
||||
app.post('/api/financeiro', async (req, res) => {
|
||||
try {
|
||||
await pool.query('INSERT INTO financeiro SET ?', req.body);
|
||||
const ins = buildInsert('financeiro', req.body);
|
||||
await pool.query(ins.text, ins.values);
|
||||
res.json({ success: true });
|
||||
} catch (err) { res.status(500).json({ error: err.message }); }
|
||||
});
|
||||
|
||||
app.put('/api/financeiro/:id', async (req, res) => {
|
||||
try {
|
||||
await pool.query('UPDATE financeiro SET ? WHERE id = ?', [req.body, req.params.id]);
|
||||
const upd = buildUpdate('financeiro', req.body, 'id', req.params.id);
|
||||
await pool.query(upd.text, upd.values);
|
||||
res.json({ success: true });
|
||||
} catch (err) { res.status(500).json({ error: err.message }); }
|
||||
});
|
||||
|
||||
app.delete('/api/financeiro/:id', async (req, res) => {
|
||||
try {
|
||||
await pool.query('DELETE FROM financeiro WHERE id = ?', [req.params.id]);
|
||||
await pool.query('DELETE FROM financeiro WHERE id = $1', [req.params.id]);
|
||||
res.json({ success: true });
|
||||
} catch (err) { res.status(500).json({ error: err.message }); }
|
||||
});
|
||||
@@ -1070,14 +972,15 @@ app.delete('/api/financeiro/:id', async (req, res) => {
|
||||
// --- LEADS ---
|
||||
app.get('/api/leads', async (req, res) => {
|
||||
try {
|
||||
const [rows] = await pool.query('SELECT * FROM leads');
|
||||
const { rows } = await pool.query('SELECT * FROM leads');
|
||||
res.json(rows);
|
||||
} catch (err) { res.status(500).json({ error: err.message }); }
|
||||
});
|
||||
|
||||
app.post('/api/leads', async (req, res) => {
|
||||
try {
|
||||
await pool.query('INSERT INTO leads SET ?', req.body);
|
||||
const ins = buildInsert('leads', req.body);
|
||||
await pool.query(ins.text, ins.values);
|
||||
res.json({ success: true });
|
||||
} catch (err) { res.status(500).json({ error: err.message }); }
|
||||
});
|
||||
@@ -1102,48 +1005,54 @@ app.get('/api/guias', async (req, res) => {
|
||||
|
||||
let where = ' WHERE 1=1';
|
||||
const params = [];
|
||||
let paramIdx = 1;
|
||||
|
||||
if (gto) {
|
||||
where += ' AND (numeroGuiaPrestador LIKE ? OR numeroGuiaOperadora LIKE ?)';
|
||||
where += ` AND ("numeroGuiaPrestador" LIKE $${paramIdx} OR "numeroGuiaOperadora" LIKE $${paramIdx + 1})`;
|
||||
params.push(`%${gto}%`, `%${gto}%`);
|
||||
paramIdx += 2;
|
||||
}
|
||||
|
||||
if (search) {
|
||||
where += ' AND (beneficiarioNome LIKE ? OR beneficiarioIdentificacao LIKE ?)';
|
||||
where += ` AND ("beneficiarioNome" LIKE $${paramIdx} OR "beneficiarioIdentificacao" LIKE $${paramIdx + 1})`;
|
||||
params.push(`%${search}%`, `%${search}%`);
|
||||
paramIdx += 2;
|
||||
}
|
||||
|
||||
if (status) {
|
||||
where += ' AND status = ?';
|
||||
where += ` AND status = $${paramIdx}`;
|
||||
params.push(status);
|
||||
paramIdx++;
|
||||
}
|
||||
|
||||
if (dataInicio) {
|
||||
where += ' AND dataSolicitacao >= ?';
|
||||
where += ` AND "dataSolicitacao" >= $${paramIdx}`;
|
||||
params.push(dataInicio);
|
||||
paramIdx++;
|
||||
}
|
||||
|
||||
if (dataFim) {
|
||||
where += ' AND dataSolicitacao <= ?';
|
||||
where += ` AND "dataSolicitacao" <= $${paramIdx}`;
|
||||
params.push(dataFim);
|
||||
paramIdx++;
|
||||
}
|
||||
|
||||
// Validate sortField and sortOrder to prevent SQL injection
|
||||
const allowedSortFields = ['dataSolicitacao', 'beneficiarioNome', 'status', 'numeroGuiaPrestador'];
|
||||
const finalSortField = allowedSortFields.includes(sortField) ? sortField : 'dataSolicitacao';
|
||||
const finalSortField = allowedSortFields.includes(sortField) ? `"${sortField}"` : '"dataSolicitacao"';
|
||||
const finalSortOrder = sortOrder.toUpperCase() === 'ASC' ? 'ASC' : 'DESC';
|
||||
|
||||
const query = `
|
||||
SELECT * FROM guias_odontologicas
|
||||
${where}
|
||||
ORDER BY ${finalSortField} ${finalSortOrder}
|
||||
LIMIT ? OFFSET ?
|
||||
SELECT * FROM guias_odontologicas
|
||||
${where}
|
||||
ORDER BY ${finalSortField} ${finalSortOrder}
|
||||
LIMIT $${paramIdx} OFFSET $${paramIdx + 1}
|
||||
`;
|
||||
|
||||
const countQuery = `SELECT COUNT(*) as total FROM guias_odontologicas ${where}`;
|
||||
|
||||
const [rows] = await pool.query(query, [...params, limit, offset]);
|
||||
const [totalRows] = await pool.query(countQuery, params);
|
||||
const { rows } = await pool.query(query, [...params, limit, offset]);
|
||||
const { rows: totalRows } = await pool.query(countQuery, params);
|
||||
|
||||
res.json({
|
||||
data: rows,
|
||||
@@ -1162,7 +1071,7 @@ app.post('/api/gto-builder', async (req, res) => {
|
||||
const { pacienteId, dentistaId, credenciadaId } = req.body;
|
||||
const id = Math.random().toString(36).substring(2, 9).toUpperCase();
|
||||
await pool.query(
|
||||
"INSERT INTO gto_builders (id, pacienteId, dentistaId, credenciadaId, status, total) VALUES ($1, $2, $3, $4, 'RASCUNHO', 0)",
|
||||
"INSERT INTO gto_builders (id, \"pacienteId\", \"dentistaId\", \"credenciadaId\", status, total) VALUES ($1, $2, $3, $4, 'RASCUNHO', 0)",
|
||||
[id, pacienteId, dentistaId, credenciadaId]
|
||||
);
|
||||
res.json({ id });
|
||||
@@ -1171,8 +1080,8 @@ app.post('/api/gto-builder', async (req, res) => {
|
||||
|
||||
app.get('/api/gto-builder/:id', async (req, res) => {
|
||||
try {
|
||||
const [builder] = await pool.query('SELECT * FROM gto_builders WHERE id = ?', [req.params.id]);
|
||||
const [items] = await pool.query('SELECT * FROM gto_items WHERE builderId = ?', [req.params.id]);
|
||||
const { rows: builder } = await pool.query('SELECT * FROM gto_builders WHERE id = $1', [req.params.id]);
|
||||
const { rows: items } = await pool.query('SELECT * FROM gto_items WHERE "builderId" = $1', [req.params.id]);
|
||||
res.json({ ...builder[0], items });
|
||||
} catch (err) { res.status(500).json({ error: err.message }); }
|
||||
});
|
||||
@@ -1181,11 +1090,12 @@ app.post('/api/gto-builder/:id/item', async (req, res) => {
|
||||
try {
|
||||
const itemId = Math.random().toString(36).substring(2, 9).toUpperCase();
|
||||
const item = { ...req.body, id: itemId, builderId: req.params.id };
|
||||
await pool.query('INSERT INTO gto_items SET ?', item);
|
||||
const ins = buildInsert('gto_items', item);
|
||||
await pool.query(ins.text, ins.values);
|
||||
|
||||
// Update total
|
||||
await pool.query(
|
||||
'UPDATE gto_builders SET total = (SELECT SUM(valorTotal) FROM gto_items WHERE builderId = ?) WHERE id = ?',
|
||||
'UPDATE gto_builders SET total = (SELECT SUM("valorTotal") FROM gto_items WHERE "builderId" = $1) WHERE id = $2',
|
||||
[req.params.id, req.params.id]
|
||||
);
|
||||
|
||||
@@ -1195,10 +1105,10 @@ app.post('/api/gto-builder/:id/item', async (req, res) => {
|
||||
|
||||
app.delete('/api/gto-builder/:id/item/:itemId', async (req, res) => {
|
||||
try {
|
||||
await pool.query('DELETE FROM gto_items WHERE id = ?', [req.params.itemId]);
|
||||
await pool.query('DELETE FROM gto_items WHERE id = $1', [req.params.itemId]);
|
||||
// Update total
|
||||
await pool.query(
|
||||
'UPDATE gto_builders SET total = COALESCE((SELECT SUM(valorTotal) FROM gto_items WHERE builderId = ?), 0) WHERE id = ?',
|
||||
'UPDATE gto_builders SET total = COALESCE((SELECT SUM("valorTotal") FROM gto_items WHERE "builderId" = $1), 0) WHERE id = $2',
|
||||
[req.params.id, req.params.id]
|
||||
);
|
||||
res.json({ success: true });
|
||||
@@ -1222,7 +1132,7 @@ async function generateIntelligentNotifications() {
|
||||
try {
|
||||
// 1. Check for upcoming appointments (next 2 hours)
|
||||
const { rows: upcoming } = await pool.query(`
|
||||
SELECT a.*, p.nome as "pacienteNome"
|
||||
SELECT a.*, p.nome as "pacienteNome"
|
||||
FROM agendamentos a
|
||||
LEFT JOIN pacientes p ON a.pacientenome = p.nome
|
||||
WHERE a.start_time BETWEEN NOW() AND NOW() + INTERVAL '2 hours'
|
||||
@@ -1244,7 +1154,7 @@ async function generateIntelligentNotifications() {
|
||||
|
||||
// 2. Check for overdue bills
|
||||
const { rows: overdue } = await pool.query(`
|
||||
SELECT * FROM financeiro
|
||||
SELECT * FROM financeiro
|
||||
WHERE datavencimento < CURRENT_DATE AND status = 'Pendente'
|
||||
`);
|
||||
|
||||
@@ -1263,7 +1173,7 @@ async function generateIntelligentNotifications() {
|
||||
|
||||
// 3. Check for bills due today
|
||||
const { rows: dueToday } = await pool.query(`
|
||||
SELECT * FROM financeiro
|
||||
SELECT * FROM financeiro
|
||||
WHERE datavencimento = CURRENT_DATE AND status = 'Pendente'
|
||||
`);
|
||||
|
||||
@@ -1307,4 +1217,3 @@ app.listen(PORT, '0.0.0.0', () => {
|
||||
generateIntelligentNotifications();
|
||||
setInterval(generateIntelligentNotifications, 5 * 60 * 1000);
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user