feat(rx): tenant_id (conta/dono) acima de clinica_id — multi-tenant 2 níveis
Hierarquia tenant_id (usuarios.id do dono) / clinica_id / pc / paciente, espelhando o scoreodonto (conta multi-clínica planejada: um dono gerencia várias clínicas). - Colunas tenant_id em clinics_devices e images (+índices); backfill device + 2776 imgs. - Cadastro de device, socket (machine_token), JWT de client e uploads gravam tenant_id. - Listagens (/images, /patients, /by-patient, /unique-clinics) filtram por tenant_id e/ou clinica_id. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -53,19 +53,24 @@ router.get('/unique-clients', async (req, res) => {
|
||||
});
|
||||
|
||||
// ================================================================
|
||||
// GET /api/images/unique-clinics - Tenants (clinica_id) com contagem + rótulo
|
||||
// GET /api/images/unique-clinics - Clínicas (clinica_id) de uma conta (tenant_id),
|
||||
// com rótulo e contagens. ?tenant_id=<conta> filtra; sem ele, todas.
|
||||
// ================================================================
|
||||
router.get('/unique-clinics', async (req, res) => {
|
||||
try {
|
||||
const tenantId = req.query.tenant_id || '';
|
||||
const rows = await db.all(
|
||||
`SELECT clinica_id,
|
||||
`SELECT tenant_id,
|
||||
clinica_id,
|
||||
MAX(clinic_name) AS clinic_name,
|
||||
COUNT(*)::integer AS image_count,
|
||||
COUNT(DISTINCT patient_name)::integer AS patient_count
|
||||
FROM images
|
||||
WHERE clinica_id IS NOT NULL AND enabled = 1
|
||||
GROUP BY clinica_id
|
||||
ORDER BY clinic_name`
|
||||
AND ($1 = '' OR tenant_id = $1)
|
||||
GROUP BY tenant_id, clinica_id
|
||||
ORDER BY clinic_name`,
|
||||
[tenantId]
|
||||
);
|
||||
res.json(rows);
|
||||
} catch (error) {
|
||||
@@ -254,13 +259,19 @@ router.get('/', async (req, res) => {
|
||||
const showDisabled = req.query.disabled === 'true';
|
||||
const search = req.query.search || '';
|
||||
const clientName = req.query.client || '';
|
||||
const clinicaId = req.query.clinica_id || ''; // tenant do scoreodonto (isolamento)
|
||||
const tenantId = req.query.tenant_id || ''; // conta/dono (isolamento por conta)
|
||||
const clinicaId = req.query.clinica_id || ''; // clínica (isolamento por unidade)
|
||||
const clinicName = req.query.clinic || ''; // rótulo legível (filtro opcional)
|
||||
|
||||
let query = `SELECT * FROM images WHERE enabled = $1`;
|
||||
let params = [showDisabled ? 0 : 1];
|
||||
let paramIndex = 2;
|
||||
|
||||
if (tenantId) {
|
||||
query += ` AND tenant_id = $${paramIndex++}`;
|
||||
params.push(tenantId);
|
||||
}
|
||||
|
||||
if (clinicaId) {
|
||||
query += ` AND clinica_id = $${paramIndex++}`;
|
||||
params.push(clinicaId);
|
||||
@@ -300,7 +311,8 @@ router.get('/patients', async (req, res) => {
|
||||
const showDisabled = req.query.disabled === 'true';
|
||||
const search = req.query.search || '';
|
||||
const clientName = req.query.client || '';
|
||||
const clinicaId = req.query.clinica_id || ''; // tenant do scoreodonto (isolamento)
|
||||
const tenantId = req.query.tenant_id || ''; // conta/dono (isolamento por conta)
|
||||
const clinicaId = req.query.clinica_id || ''; // clínica (isolamento por unidade)
|
||||
const clinicName = req.query.clinic || ''; // rótulo legível (filtro opcional)
|
||||
const enabledVal = showDisabled ? 0 : 1;
|
||||
|
||||
@@ -313,6 +325,11 @@ router.get('/patients', async (req, res) => {
|
||||
const offset = (page - 1) * limit;
|
||||
|
||||
let whereStr = `WHERE 1=1`;
|
||||
if (tenantId) {
|
||||
whereStr += ` AND tenant_id = $${paramIndex}`;
|
||||
params.push(tenantId);
|
||||
paramIndex++;
|
||||
}
|
||||
if (clinicaId) {
|
||||
whereStr += ` AND clinica_id = $${paramIndex}`;
|
||||
params.push(clinicaId);
|
||||
@@ -398,7 +415,8 @@ router.get('/by-patient', async (req, res) => {
|
||||
try {
|
||||
const patientName = req.query.name || '';
|
||||
const showDisabled = req.query.disabled === 'true';
|
||||
const clinicaId = req.query.clinica_id || ''; // tenant do scoreodonto (isolamento)
|
||||
const tenantId = req.query.tenant_id || ''; // conta/dono (isolamento por conta)
|
||||
const clinicaId = req.query.clinica_id || ''; // clínica (isolamento por unidade)
|
||||
|
||||
if (!patientName) {
|
||||
return res.status(400).json({ error: 'Parâmetro name obrigatório' });
|
||||
@@ -406,6 +424,10 @@ router.get('/by-patient', async (req, res) => {
|
||||
|
||||
let sql = `SELECT * FROM images WHERE patient_name = $1 AND enabled = $2`;
|
||||
const params = [patientName, showDisabled ? 0 : 1];
|
||||
if (tenantId) {
|
||||
sql += ` AND tenant_id = $${params.length + 1}`;
|
||||
params.push(tenantId);
|
||||
}
|
||||
if (clinicaId) {
|
||||
sql += ` AND clinica_id = $${params.length + 1}`;
|
||||
params.push(clinicaId);
|
||||
|
||||
Reference in New Issue
Block a user