a7f6bcbe66
- useTenantDb composable + lib/supabase/tenantClient (tenantDb/tenantSchemaName)
- tenantStore: getters activeTenantSlug/activeTenantSchema; my_tenants() RPC
passa a devolver slug+nome (migration 07)
- codemod scripts/codemod-tenant-db.py: supabase.from('<84 tabelas + 6 views
tenant>') -> tenantDb().from(...) em 139 arquivos (777 chamadas), remove
.eq('tenant_id') das cadeias tenant (173)
- passada manual (4 agentes): remove tenant_id de payloads insert/upsert/update,
selects, .or/.is de defaults; onConflict ajustado pros uniques sem tenant_id
(singletons usam 'singleton'); realtime de tabelas tenant aponta pro schema
do tenant ativo; repos dropam tenant_id defensivamente de payloads externos
- agendaSelects: tenant_id fora do AGENDA_EVENT_SELECT (quebraria PostgREST)
- zero embeds cross-schema (todos FK embeds sao tenant->tenant ou global->global)
- build de producao passa; 67 .js checados
Pendente (fora do escopo F3, sao cross-tenant/anon -> F4/F6):
- AgendadorPublicoPage (anon, resolve tenant por link_slug)
- Saas{Feriados,NotificationTemplates,DocumentTemplates,Whatsapp}Page
(gerenciam defaults do sistema / views cross-tenant)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
152 lines
5.6 KiB
JavaScript
152 lines
5.6 KiB
JavaScript
/*
|
|
|--------------------------------------------------------------------------
|
|
| Agência PSI
|
|
|--------------------------------------------------------------------------
|
|
| Arquivo: src/composables/useConversationAssignment.js
|
|
| Data: 2026-04-21
|
|
|
|
|
| Atribuicao de thread de conversa a um terapeuta/membro do tenant.
|
|
| Uma linha por (tenant_id, thread_key). Reatribuir = UPSERT.
|
|
|--------------------------------------------------------------------------
|
|
*/
|
|
|
|
import { ref } from 'vue';
|
|
import { supabase } from '@/lib/supabase/client';
|
|
import { tenantDb } from '@/lib/supabase/tenantClient';
|
|
import { useTenantStore } from '@/stores/tenantStore';
|
|
|
|
export function useConversationAssignment() {
|
|
const tenantStore = useTenantStore();
|
|
|
|
const assignment = ref(null); // { assigned_to, assigned_by, assigned_at, _assignee_name }
|
|
const members = ref([]); // lista de membros do tenant (pra Select)
|
|
const loading = ref(false);
|
|
const saving = ref(false);
|
|
const error = ref(null);
|
|
|
|
async function loadMembers() {
|
|
const tenantId = tenantStore.activeTenantId;
|
|
if (!tenantId) { members.value = []; return; }
|
|
try {
|
|
const { data, error: err } = await supabase
|
|
.from('v_tenant_members_with_profiles')
|
|
.select('user_id, full_name, email, role')
|
|
.eq('tenant_id', tenantId)
|
|
.in('role', ['tenant_admin', 'therapist', 'secretary'])
|
|
.eq('status', 'active')
|
|
.order('full_name', { ascending: true });
|
|
if (err) throw err;
|
|
members.value = (data || []).map((m) => ({
|
|
user_id: m.user_id,
|
|
label: m.full_name || m.email || m.user_id,
|
|
email: m.email,
|
|
role: m.role
|
|
}));
|
|
} catch (e) {
|
|
error.value = e?.message || 'Falha ao carregar membros';
|
|
members.value = [];
|
|
}
|
|
}
|
|
|
|
async function load(threadKey) {
|
|
if (!threadKey) { assignment.value = null; return; }
|
|
const tenantId = tenantStore.activeTenantId;
|
|
if (!tenantId) { assignment.value = null; return; }
|
|
|
|
loading.value = true;
|
|
error.value = null;
|
|
try {
|
|
const { data, error: err } = await tenantDb().from('conversation_assignments')
|
|
.select('thread_key, patient_id, contact_number, assigned_to, assigned_by, assigned_at')
|
|
.eq('thread_key', threadKey)
|
|
.maybeSingle();
|
|
if (err) throw err;
|
|
|
|
if (!data || !data.assigned_to) {
|
|
assignment.value = data || null;
|
|
return;
|
|
}
|
|
|
|
// Resolve nome do assignee via membros (se carregado) ou profiles
|
|
let assigneeName = null;
|
|
const hit = members.value.find((m) => m.user_id === data.assigned_to);
|
|
if (hit) assigneeName = hit.label;
|
|
if (!assigneeName) {
|
|
const { data: u } = await supabase.from('profiles').select('full_name').eq('id', data.assigned_to).maybeSingle();
|
|
assigneeName = u?.full_name || null;
|
|
}
|
|
assignment.value = { ...data, _assignee_name: assigneeName };
|
|
} catch (e) {
|
|
error.value = e?.message || 'Falha ao carregar atribuicao';
|
|
assignment.value = null;
|
|
} finally {
|
|
loading.value = false;
|
|
}
|
|
}
|
|
|
|
async function assign({ threadKey, patientId = null, contactNumber = null, assignedTo }) {
|
|
const tenantId = tenantStore.activeTenantId;
|
|
if (!tenantId || !threadKey) return { ok: false, error: 'invalid_params' };
|
|
|
|
saving.value = true;
|
|
try {
|
|
const { data: authData } = await supabase.auth.getUser();
|
|
const userId = authData?.user?.id;
|
|
if (!userId) return { ok: false, error: 'not_authenticated' };
|
|
|
|
const payload = {
|
|
thread_key: threadKey,
|
|
patient_id: patientId || null,
|
|
contact_number: contactNumber || null,
|
|
assigned_to: assignedTo || null,
|
|
assigned_by: userId,
|
|
assigned_at: new Date().toISOString()
|
|
};
|
|
|
|
const { data, error: err } = await tenantDb().from('conversation_assignments')
|
|
.upsert(payload, { onConflict: 'thread_key' })
|
|
.select('thread_key, patient_id, contact_number, assigned_to, assigned_by, assigned_at')
|
|
.single();
|
|
if (err) throw err;
|
|
|
|
let assigneeName = null;
|
|
if (data.assigned_to) {
|
|
const hit = members.value.find((m) => m.user_id === data.assigned_to);
|
|
assigneeName = hit?.label || null;
|
|
if (!assigneeName) {
|
|
const { data: u } = await supabase.from('profiles').select('full_name').eq('id', data.assigned_to).maybeSingle();
|
|
assigneeName = u?.full_name || null;
|
|
}
|
|
}
|
|
assignment.value = { ...data, _assignee_name: assigneeName };
|
|
return { ok: true, assignment: assignment.value };
|
|
} catch (e) {
|
|
return { ok: false, error: e?.message || 'assign_failed' };
|
|
} finally {
|
|
saving.value = false;
|
|
}
|
|
}
|
|
|
|
async function unassign({ threadKey, patientId = null, contactNumber = null }) {
|
|
return assign({ threadKey, patientId, contactNumber, assignedTo: null });
|
|
}
|
|
|
|
function clear() {
|
|
assignment.value = null;
|
|
error.value = null;
|
|
}
|
|
|
|
return {
|
|
assignment,
|
|
members,
|
|
loading,
|
|
saving,
|
|
error,
|
|
loadMembers,
|
|
load,
|
|
assign,
|
|
unassign,
|
|
clear
|
|
};
|
|
}
|