Files
agenciapsilmno/src/features/agenda/composables/usePatientDiscounts.js
T
Leonardo a7f6bcbe66 F3 schema-per-tenant: frontend usa tenantDb() pra tabelas tenant
- 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>
2026-06-13 04:44:59 -03:00

122 lines
5.0 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/*
|--------------------------------------------------------------------------
| Agência PSI
|--------------------------------------------------------------------------
| Criado e desenvolvido por Leonardo Nohama
|
| Tecnologia aplicada à escuta.
| Estrutura para o cuidado.
|
| Arquivo: src/features/agenda/composables/usePatientDiscounts.js
| Data: 2026
| Local: São Carlos/SP — Brasil
|--------------------------------------------------------------------------
| © 2026 — Todos os direitos reservados
|--------------------------------------------------------------------------
*/
// CRUD completo sobre a tabela public.patient_discounts.
//
// Interface pública:
// discounts ref([]) lista de descontos do owner
// loading ref(false)
// error ref('')
//
// load(ownerId) carrega todos os registros do owner
// save(payload) cria ou atualiza (id presente = update)
// remove(id) soft-delete (active = false)
// loadActive(ownerId, patientId) desconto ativo vigente para um paciente
import { ref } from 'vue';
import { supabase } from '@/lib/supabase/client';
import { tenantDb } from '@/lib/supabase/tenantClient';
export function usePatientDiscounts() {
const discounts = ref([]);
const loading = ref(false);
const error = ref('');
// ── Carregar todos os descontos do owner ─────────────────────────────
async function load(ownerId) {
if (!ownerId) return;
loading.value = true;
error.value = '';
try {
const { data, error: err } = await tenantDb().from('patient_discounts').select('*').eq('owner_id', ownerId).order('created_at', { ascending: false });
if (err) throw err;
discounts.value = data || [];
} catch (e) {
error.value = e?.message || 'Falha ao carregar descontos.';
discounts.value = [];
} finally {
loading.value = false;
}
}
// ── Criar ou atualizar um desconto ───────────────────────────────────
// payload deve conter: { owner_id, patient_id, discount_pct, discount_flat, ... }
// Se payload.id estiver presente, faz UPDATE; caso contrário, INSERT.
async function save(payload) {
error.value = '';
try {
if (payload.id) {
const { id, owner_id, tenant_id, ...fields } = payload;
const { error: err } = await tenantDb().from('patient_discounts').update(fields).eq('id', id).eq('owner_id', owner_id);
if (err) throw err;
} else {
// eslint-disable-next-line no-unused-vars
const { tenant_id: _dropTenantId, ...insertFields } = payload;
const { error: err } = await tenantDb().from('patient_discounts').insert(insertFields);
if (err) throw err;
}
} catch (e) {
error.value = e?.message || 'Falha ao salvar desconto.';
throw e;
}
}
// ── Soft-delete: marca active = false ───────────────────────────────
async function remove(id) {
error.value = '';
try {
const { error: err } = await tenantDb().from('patient_discounts').update({ active: false }).eq('id', id);
if (err) throw err;
discounts.value = discounts.value.filter((d) => d.id !== id);
} catch (e) {
error.value = e?.message || 'Falha ao desativar desconto.';
throw e;
}
}
// ── Desconto ativo vigente para um paciente específico ───────────────
// Retorna o primeiro registro que satisfaz:
// active = true
// active_from IS NULL OR active_from <= now()
// active_to IS NULL OR active_to >= now()
// Ordenado por created_at DESC (mais recente tem precedência).
async function loadActive(ownerId, patientId) {
if (!ownerId || !patientId) return null;
try {
const now = new Date().toISOString();
const { data, error: err } = await tenantDb().from('patient_discounts')
.select('*')
.eq('owner_id', ownerId)
.eq('patient_id', patientId)
.eq('active', true)
.or(`active_from.is.null,active_from.lte.${now}`)
.or(`active_to.is.null,active_to.gte.${now}`)
.order('created_at', { ascending: false })
.limit(1)
.maybeSingle();
if (err) throw err;
return data || null;
} catch (e) {
console.warn('[usePatientDiscounts] loadActive error:', e?.message);
return null;
}
}
return { discounts, loading, error, load, save, remove, loadActive };
}