Añade Ficha del Cliente: historial de servicios y notas del equipo
Nueva tabla client_notes + endpoints GET/POST /clients/:id/history y /clients/:id/notes. También corrige POST /clients/ensure para devolver la fila completa del cliente (antes solo devolvía portal_token, faltaba el id).
This commit is contained in:
90
server.js
90
server.js
@@ -4152,10 +4152,12 @@ app.post('/clients/ensure', authMiddleware, async (req, res) => {
|
|||||||
} else {
|
} else {
|
||||||
const newToken = crypto.randomBytes(6).toString('hex');
|
const newToken = crypto.randomBytes(6).toString('hex');
|
||||||
const insert = await pool.query(
|
const insert = await pool.query(
|
||||||
"INSERT INTO clients (owner_id, full_name, phone, addresses, portal_token) VALUES ($1, $2, $3, $4, $5) RETURNING portal_token",
|
"INSERT INTO clients (owner_id, full_name, phone, addresses, portal_token) VALUES ($1, $2, $3, $4, $5) RETURNING *",
|
||||||
[ownerId, name || "Cliente", phone, JSON.stringify([address || ""]), newToken]
|
[ownerId, name || "Cliente", phone, JSON.stringify([address || ""]), newToken]
|
||||||
);
|
);
|
||||||
res.json({ ok: true, client: { portal_token: insert.rows[0].portal_token } });
|
// 🆕 Devolvemos la fila completa (antes solo el portal_token), porque ahora la
|
||||||
|
// Ficha del Cliente de la App necesita el id del cliente recién creado.
|
||||||
|
res.json({ ok: true, client: insert.rows[0] });
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
res.status(500).json({ ok: false, error: "Error interno del servidor" });
|
res.status(500).json({ ok: false, error: "Error interno del servidor" });
|
||||||
@@ -5021,14 +5023,92 @@ app.get("/services/:id/logs", authMiddleware, async (req, res) => {
|
|||||||
const q = await pool.query(`
|
const q = await pool.query(`
|
||||||
SELECT l.* FROM scraped_service_logs l
|
SELECT l.* FROM scraped_service_logs l
|
||||||
JOIN scraped_services s ON l.scraped_id = s.id
|
JOIN scraped_services s ON l.scraped_id = s.id
|
||||||
WHERE l.scraped_id = $1
|
WHERE l.scraped_id = $1
|
||||||
AND s.owner_id = $2
|
AND s.owner_id = $2
|
||||||
ORDER BY l.created_at DESC
|
ORDER BY l.created_at DESC
|
||||||
`, [req.params.id, req.user.accountId]);
|
`, [req.params.id, req.user.accountId]);
|
||||||
|
|
||||||
res.json({ ok: true, logs: q.rows });
|
res.json({ ok: true, logs: q.rows });
|
||||||
} catch(e) {
|
} catch(e) {
|
||||||
res.status(500).json({ ok: false });
|
res.status(500).json({ ok: false });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// ==========================================
|
||||||
|
// 🧑🔧 FICHA DEL CLIENTE (App): historial de servicios + notas propias del equipo
|
||||||
|
// ==========================================
|
||||||
|
pool.query(`
|
||||||
|
CREATE TABLE IF NOT EXISTS client_notes (
|
||||||
|
id SERIAL PRIMARY KEY,
|
||||||
|
client_id INT REFERENCES clients(id) ON DELETE CASCADE,
|
||||||
|
owner_id INT REFERENCES users(id) ON DELETE CASCADE,
|
||||||
|
user_name TEXT,
|
||||||
|
note TEXT NOT NULL,
|
||||||
|
created_at TIMESTAMP DEFAULT NOW()
|
||||||
|
);
|
||||||
|
`).catch(console.error);
|
||||||
|
|
||||||
|
// Ficha completa: datos del cliente + todos sus servicios anteriores (de cualquier compañía),
|
||||||
|
// buscados por teléfono igual que hace /clients/search.
|
||||||
|
app.get("/clients/:id/history", authMiddleware, async (req, res) => {
|
||||||
|
try {
|
||||||
|
const ownerId = req.user.accountId;
|
||||||
|
const clientQ = await pool.query("SELECT * FROM clients WHERE id=$1 AND owner_id=$2", [req.params.id, ownerId]);
|
||||||
|
if (clientQ.rowCount === 0) return res.status(404).json({ ok: false, error: "Cliente no encontrado" });
|
||||||
|
const client = clientQ.rows[0];
|
||||||
|
|
||||||
|
const bare = extractValidPhone(client.phone);
|
||||||
|
const servicesQ = await pool.query(`
|
||||||
|
SELECT s.id, s.provider, s.service_ref, s.status, s.raw_data, s.created_at,
|
||||||
|
st.name as status_name, st.color as status_color, st.is_final as status_is_final
|
||||||
|
FROM scraped_services s
|
||||||
|
LEFT JOIN service_statuses st ON st.id::text = (s.raw_data->>'status_operativo')::text
|
||||||
|
WHERE s.owner_id=$1
|
||||||
|
AND regexp_replace(COALESCE(s.raw_data->>'Teléfono', s.raw_data->>'TELEFONO', s.raw_data->>'TELEFONOS', ''), '\\D', '', 'g') LIKE '%' || $2 || '%'
|
||||||
|
ORDER BY s.created_at DESC
|
||||||
|
LIMIT 100
|
||||||
|
`, [ownerId, bare]);
|
||||||
|
|
||||||
|
res.json({ ok: true, client, services: servicesQ.rows });
|
||||||
|
} catch (e) {
|
||||||
|
console.error("Error cargando ficha de cliente:", e);
|
||||||
|
res.status(500).json({ ok: false });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Notas del equipo sobre este cliente (visibles para toda la cuenta, no solo quien las escribe)
|
||||||
|
app.get("/clients/:id/notes", authMiddleware, async (req, res) => {
|
||||||
|
try {
|
||||||
|
const q = await pool.query(`
|
||||||
|
SELECT n.* FROM client_notes n
|
||||||
|
JOIN clients c ON n.client_id = c.id
|
||||||
|
WHERE n.client_id = $1 AND c.owner_id = $2
|
||||||
|
ORDER BY n.created_at DESC
|
||||||
|
`, [req.params.id, req.user.accountId]);
|
||||||
|
res.json({ ok: true, notes: q.rows });
|
||||||
|
} catch (e) {
|
||||||
|
res.status(500).json({ ok: false });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
app.post("/clients/:id/notes", authMiddleware, async (req, res) => {
|
||||||
|
try {
|
||||||
|
const { note } = req.body;
|
||||||
|
if (!note || !note.trim()) return res.status(400).json({ ok: false, error: "La nota está vacía" });
|
||||||
|
|
||||||
|
const check = await pool.query("SELECT id FROM clients WHERE id=$1 AND owner_id=$2", [req.params.id, req.user.accountId]);
|
||||||
|
if (check.rowCount === 0) return res.status(403).json({ ok: false, error: "No autorizado" });
|
||||||
|
|
||||||
|
const userQ = await pool.query("SELECT full_name FROM users WHERE id=$1", [req.user.sub]);
|
||||||
|
const userName = userQ.rows[0]?.full_name || "Usuario";
|
||||||
|
|
||||||
|
const insert = await pool.query(
|
||||||
|
"INSERT INTO client_notes (client_id, owner_id, user_name, note) VALUES ($1, $2, $3, $4) RETURNING *",
|
||||||
|
[req.params.id, req.user.accountId, userName, note.trim()]
|
||||||
|
);
|
||||||
|
res.json({ ok: true, note: insert.rows[0] });
|
||||||
|
} catch (e) {
|
||||||
|
res.status(500).json({ ok: false });
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user