225 lines
7.7 KiB
JavaScript
225 lines
7.7 KiB
JavaScript
/*
|
|
|--------------------------------------------------------------------------
|
|
| Agência PSI
|
|
|--------------------------------------------------------------------------
|
|
| Criado e desenvolvido por Leonardo Nohama
|
|
|
|
|
| Tecnologia aplicada à escuta.
|
|
| Estrutura para o cuidado.
|
|
|
|
|
| Arquivo: src/services/Medicos.service.js
|
|
| Data: 2026
|
|
| Local: São Carlos/SP — Brasil
|
|
|--------------------------------------------------------------------------
|
|
| © 2026 — Todos os direitos reservados
|
|
|--------------------------------------------------------------------------
|
|
*/
|
|
import { supabase } from '@/lib/supabase/client';
|
|
|
|
// ── Helpers ──────────────────────────────────────────────────
|
|
|
|
async function getOwnerId() {
|
|
const { data, error } = await supabase.auth.getUser();
|
|
if (error) throw error;
|
|
const uid = data?.user?.id;
|
|
if (!uid) throw new Error('Sessão inválida.');
|
|
return uid;
|
|
}
|
|
|
|
async function getActiveTenantId(uid) {
|
|
const { data, error } = await supabase
|
|
.from('tenant_members')
|
|
.select('tenant_id')
|
|
.eq('user_id', uid)
|
|
.eq('status', 'active')
|
|
.order('created_at', { ascending: false })
|
|
.limit(1)
|
|
.single();
|
|
if (error) throw error;
|
|
if (!data?.tenant_id) throw new Error('Tenant não encontrado.');
|
|
return data.tenant_id;
|
|
}
|
|
|
|
function normalizeNome(s) {
|
|
return String(s || '').trim().toLowerCase().replace(/\s+/g, ' ');
|
|
}
|
|
|
|
function isUniqueViolation(err) {
|
|
if (!err) return false;
|
|
if (err.code === '23505') return true;
|
|
return /duplicate key value violates unique constraint/i.test(String(err.message || ''));
|
|
}
|
|
|
|
// ── List ─────────────────────────────────────────────────────
|
|
|
|
/**
|
|
* Lista médicos ativos do owner com contagem de pacientes encaminhados.
|
|
* A contagem é feita buscando quantos patients possuem o nome do médico
|
|
* no campo `encaminhado_por` (text).
|
|
*/
|
|
export async function listMedicosWithPatientCounts() {
|
|
const ownerId = await getOwnerId();
|
|
|
|
const { data: medicos, error } = await supabase
|
|
.from('medicos')
|
|
.select('id, nome, crm, especialidade, telefone_profissional, telefone_pessoal, email, clinica, cidade, estado, observacoes, ativo, owner_id, tenant_id, created_at, updated_at')
|
|
.eq('owner_id', ownerId)
|
|
.eq('ativo', true)
|
|
.order('nome', { ascending: true });
|
|
|
|
if (error) throw error;
|
|
|
|
// Busca pacientes do owner para contar encaminhamentos por médico
|
|
const { data: patients, error: pErr } = await supabase
|
|
.from('patients')
|
|
.select('id, encaminhado_por')
|
|
.eq('owner_id', ownerId);
|
|
|
|
if (pErr) throw pErr;
|
|
|
|
const countMap = new Map();
|
|
for (const med of medicos || []) {
|
|
countMap.set(med.id, 0);
|
|
}
|
|
|
|
for (const p of patients || []) {
|
|
const enc = String(p.encaminhado_por || '').toLowerCase();
|
|
if (!enc) continue;
|
|
for (const med of medicos || []) {
|
|
const nomeLower = med.nome.toLowerCase();
|
|
if (enc.includes(nomeLower)) {
|
|
countMap.set(med.id, (countMap.get(med.id) || 0) + 1);
|
|
}
|
|
}
|
|
}
|
|
|
|
return (medicos || []).map((m) => ({
|
|
...m,
|
|
patients_count: countMap.get(m.id) || 0
|
|
}));
|
|
}
|
|
|
|
// ── Create ───────────────────────────────────────────────────
|
|
|
|
export async function createMedico(payload) {
|
|
const ownerId = await getOwnerId();
|
|
const tenantId = await getActiveTenantId(ownerId);
|
|
|
|
const nome = String(payload.nome || '').trim();
|
|
if (!nome) throw new Error('Nome do médico é obrigatório.');
|
|
|
|
const row = {
|
|
owner_id: ownerId,
|
|
tenant_id: tenantId,
|
|
nome,
|
|
crm: String(payload.crm || '').trim() || null,
|
|
especialidade: payload.especialidade || null,
|
|
telefone_profissional: payload.telefone_profissional || null,
|
|
telefone_pessoal: payload.telefone_pessoal || null,
|
|
email: String(payload.email || '').trim() || null,
|
|
clinica: String(payload.clinica || '').trim() || null,
|
|
cidade: String(payload.cidade || '').trim() || null,
|
|
estado: String(payload.estado || '').trim() || null,
|
|
observacoes: String(payload.observacoes || '').trim() || null,
|
|
ativo: true
|
|
};
|
|
|
|
const { data, error } = await supabase
|
|
.from('medicos')
|
|
.insert(row)
|
|
.select('*')
|
|
.single();
|
|
|
|
if (error) {
|
|
if (isUniqueViolation(error)) throw new Error('Já existe um médico com este CRM.');
|
|
throw error;
|
|
}
|
|
|
|
return data;
|
|
}
|
|
|
|
// ── Update ───────────────────────────────────────────────────
|
|
|
|
export async function updateMedico(id, payload) {
|
|
const ownerId = await getOwnerId();
|
|
|
|
if (!id) throw new Error('ID inválido.');
|
|
const nome = String(payload.nome || '').trim();
|
|
if (!nome) throw new Error('Nome do médico é obrigatório.');
|
|
|
|
const row = {
|
|
nome,
|
|
crm: String(payload.crm || '').trim() || null,
|
|
especialidade: payload.especialidade || null,
|
|
telefone_profissional: payload.telefone_profissional || null,
|
|
telefone_pessoal: payload.telefone_pessoal || null,
|
|
email: String(payload.email || '').trim() || null,
|
|
clinica: String(payload.clinica || '').trim() || null,
|
|
cidade: String(payload.cidade || '').trim() || null,
|
|
estado: String(payload.estado || '').trim() || null,
|
|
observacoes: String(payload.observacoes || '').trim() || null,
|
|
updated_at: new Date().toISOString()
|
|
};
|
|
|
|
const { data, error } = await supabase
|
|
.from('medicos')
|
|
.update(row)
|
|
.eq('id', id)
|
|
.eq('owner_id', ownerId)
|
|
.select('*')
|
|
.single();
|
|
|
|
if (error) {
|
|
if (isUniqueViolation(error)) throw new Error('Já existe um médico com este CRM.');
|
|
throw error;
|
|
}
|
|
|
|
return data;
|
|
}
|
|
|
|
// ── Delete (soft) ────────────────────────────────────────────
|
|
|
|
export async function deleteMedico(id) {
|
|
const ownerId = await getOwnerId();
|
|
if (!id) throw new Error('ID inválido.');
|
|
|
|
const { error } = await supabase
|
|
.from('medicos')
|
|
.update({ ativo: false, updated_at: new Date().toISOString() })
|
|
.eq('id', id)
|
|
.eq('owner_id', ownerId);
|
|
|
|
if (error) throw error;
|
|
return true;
|
|
}
|
|
|
|
// ── Pacientes de um médico ───────────────────────────────────
|
|
|
|
/**
|
|
* Busca pacientes do owner cujo campo `encaminhado_por` contém o nome do médico.
|
|
*/
|
|
export async function fetchPatientsByMedicoNome(medicoNome) {
|
|
const ownerId = await getOwnerId();
|
|
const nomeLower = String(medicoNome || '').trim().toLowerCase();
|
|
if (!nomeLower) return [];
|
|
|
|
const { data, error } = await supabase
|
|
.from('patients')
|
|
.select('id, nome_completo, email_principal, telefone, avatar_url, encaminhado_por')
|
|
.eq('owner_id', ownerId)
|
|
.ilike('encaminhado_por', `%${nomeLower}%`);
|
|
|
|
if (error) throw error;
|
|
|
|
return (data || [])
|
|
.map((p) => ({
|
|
id: p.id,
|
|
full_name: p.nome_completo || '—',
|
|
email: p.email_principal || '—',
|
|
phone: p.telefone || '—',
|
|
avatar_url: p.avatar_url || null,
|
|
encaminhado_por: p.encaminhado_por || ''
|
|
}))
|
|
.sort((a, b) => String(a.full_name).localeCompare(String(b.full_name), 'pt-BR'));
|
|
}
|