feat(procedimentos): atende/não-atende + motivo + como falar (backend)
- Tabela agenda_procedimentos (clinica_id, nome, atende, motivo, como_falar). - agenda-config GET/PUT /clinica/:id/procedimentos-atende (GET semeia do catálogo procedimentos — incl. globais clinica_id NULL; PUT só dono). - /contexto (bridge) devolve procedimentos_nao_atende (nome+motivo+como_falar). Verificado E2E: seed 4; PUT marca não-atende; /contexto entrega o script. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -451,6 +451,12 @@ export function createAgendaBridge(pool) {
|
||||
const { rows } = await pool.query(`SELECT texto FROM agenda_situacoes WHERE clinica_id = $1 AND ativo = 1 ORDER BY ordem, created_at`, [clinicaId]);
|
||||
situacoes = rows.map((r) => r.texto);
|
||||
} catch { /* tabela pode não existir ainda */ }
|
||||
// Procedimentos que a clínica NÃO atende → a Secretária responde com o script.
|
||||
let procedimentos_nao_atende = [];
|
||||
try {
|
||||
const { rows } = await pool.query(`SELECT nome, motivo, como_falar FROM agenda_procedimentos WHERE clinica_id = $1 AND atende = 0 ORDER BY ordem, nome`, [clinicaId]);
|
||||
procedimentos_nao_atende = rows.map((r) => ({ nome: r.nome, motivo: r.motivo || null, como_falar: r.como_falar || null }));
|
||||
} catch { /* tabela pode não existir ainda */ }
|
||||
// Cautelas do checklist do dono (declaração × dados reais) → a IA não chuta.
|
||||
let cautelas = [];
|
||||
try {
|
||||
@@ -466,6 +472,7 @@ export function createAgendaBridge(pool) {
|
||||
especialidades: Array.isArray(d.especialidades) ? d.especialidades : (d.especialidade ? [d.especialidade] : []),
|
||||
})),
|
||||
situacoes,
|
||||
procedimentos_nao_atende,
|
||||
cautelas,
|
||||
});
|
||||
} catch (e) { res.status(500).json({ error: e.message }); }
|
||||
|
||||
@@ -243,6 +243,50 @@ export function createAgendaConfig(pool) {
|
||||
finally { client.release(); }
|
||||
});
|
||||
|
||||
// ── Procedimentos que a clínica atende / não atende (dono) ────────────────────
|
||||
router.get('/clinica/:clinicaId/procedimentos-atende', async (req, res) => {
|
||||
const clinicaId = req.params.clinicaId;
|
||||
try {
|
||||
let { rows } = await pool.query(
|
||||
`SELECT id, nome, atende, motivo, como_falar, ordem FROM agenda_procedimentos WHERE clinica_id = $1 ORDER BY ordem, nome`, [clinicaId]);
|
||||
// Primeira vez: semeia a lista a partir do catálogo de procedimentos da clínica (atende=1).
|
||||
if (!rows.length) {
|
||||
const { rows: cat } = await pool.query(
|
||||
`SELECT DISTINCT nome FROM procedimentos WHERE (clinica_id = $1 OR clinica_id IS NULL) AND ativo IS DISTINCT FROM false AND nome IS NOT NULL AND nome <> '' ORDER BY nome`, [clinicaId]);
|
||||
const client = await pool.connect();
|
||||
try {
|
||||
await client.query('BEGIN');
|
||||
let i = 0;
|
||||
for (const c of cat) await client.query(`INSERT INTO agenda_procedimentos (id, clinica_id, nome, atende, ordem) VALUES ($1,$2,$3,1,$4)`, [uid('prc'), clinicaId, c.nome, i++]);
|
||||
await client.query('COMMIT');
|
||||
} catch { try { await client.query('ROLLBACK'); } catch {} } finally { client.release(); }
|
||||
({ rows } = await pool.query(`SELECT id, nome, atende, motivo, como_falar, ordem FROM agenda_procedimentos WHERE clinica_id = $1 ORDER BY ordem, nome`, [clinicaId]));
|
||||
}
|
||||
res.json({ procedimentos: rows });
|
||||
} catch (e) { res.status(500).json({ error: e.message }); }
|
||||
});
|
||||
// Substitui toda a lista. Body: { procedimentos: [{nome, atende, motivo, como_falar}] }
|
||||
router.put('/clinica/:clinicaId/procedimentos-atende', async (req, res) => {
|
||||
const clinicaId = req.params.clinicaId;
|
||||
if (!(await isDono(pool, clinicaId, req.nwUser.userId))) return res.status(403).json({ error: 'Apenas o dono pode configurar os procedimentos.' });
|
||||
const linhas = Array.isArray(req.body?.procedimentos) ? req.body.procedimentos : [];
|
||||
const client = await pool.connect();
|
||||
try {
|
||||
await client.query('BEGIN');
|
||||
await client.query('DELETE FROM agenda_procedimentos WHERE clinica_id = $1', [clinicaId]);
|
||||
let i = 0;
|
||||
for (const p of linhas) {
|
||||
const nome = String(p?.nome || '').trim(); if (!nome) continue;
|
||||
const atende = (p?.atende === false || p?.atende === 0) ? 0 : 1;
|
||||
await client.query(
|
||||
`INSERT INTO agenda_procedimentos (id, clinica_id, nome, atende, motivo, como_falar, ordem) VALUES ($1,$2,$3,$4,$5,$6,$7)`,
|
||||
[uid('prc'), clinicaId, nome, atende, atende ? null : (String(p?.motivo || '').trim() || null), atende ? null : (String(p?.como_falar || '').trim() || null), i++]);
|
||||
}
|
||||
await client.query('COMMIT'); res.json({ ok: true });
|
||||
} catch (e) { try { await client.query('ROLLBACK'); } catch {} res.status(500).json({ error: e.message }); }
|
||||
finally { client.release(); }
|
||||
});
|
||||
|
||||
// ── Checklist do DONO + pendências (só o dono do workspace) ───────────────────
|
||||
// Conta dentistas/pacientes reais da clínica p/ conferir contra as respostas.
|
||||
async function contagens(clinicaId) {
|
||||
|
||||
@@ -98,6 +98,21 @@ export async function ensureAgendaSchema(pool) {
|
||||
)`);
|
||||
await pool.query(`CREATE INDEX IF NOT EXISTS idx_agenda_situacoes_clinica ON agenda_situacoes (clinica_id)`);
|
||||
|
||||
// ── agenda_procedimentos — o que a clínica ATENDE ou não. Desmarcado (atende=0)
|
||||
// = não atende: guarda o motivo e o "como_falar" (script p/ a Secretária responder).
|
||||
await pool.query(`
|
||||
CREATE TABLE IF NOT EXISTS agenda_procedimentos (
|
||||
id varchar PRIMARY KEY,
|
||||
clinica_id varchar NOT NULL,
|
||||
nome text NOT NULL,
|
||||
atende smallint DEFAULT 1,
|
||||
motivo text,
|
||||
como_falar text,
|
||||
ordem integer DEFAULT 0,
|
||||
created_at timestamptz DEFAULT now()
|
||||
)`);
|
||||
await pool.query(`CREATE INDEX IF NOT EXISTS idx_agenda_procedimentos_clinica ON agenda_procedimentos (clinica_id)`);
|
||||
|
||||
// ── agenda_checklist — respostas Sim/Não do DONO sobre a clínica (ver
|
||||
// checklist-defs.js). Conferidas contra o banco → geram pendências de config
|
||||
// e deixam a Secretária cautelosa. resposta: 1=sim, 0=não, NULL=não respondido.
|
||||
|
||||
Reference in New Issue
Block a user