F6.2 Lote D: RPCs user-facing roteadas pro schema do tenant
DB (supabase_admin, manual/f6_2d_user_rpcs.supabase_admin.sql): 14 RPCs. Helper _tenant_route(p_tenant_id) valida is_tenant_member + retorna schema (retorna, nao seta — set_config em helper com SET search_path proprio seria revertido na saida). Cada RPC: set_config search_path pro schema + unqualify tabelas tenant + remove WHERE tenant_id= e tenant_id de inserts. - Grupo 1 (ja tinham p_tenant_id, jsonb/void): delete_commitment_full, delete_determined_commitment, seed_default_patient_groups, seed_determined_commitments (no-op se schema nao existe) - Grupo 2 (novo p_tenant_id 1o param, DROP+CREATE): cancel_recurrence_from, cancelar_eventos_serie, split_recurrence_at, safe_delete_patient, export_patient_data (audit_logs global mantido), search_global (patient_intake_requests fica em public/F1b -> qualificado + filtro tenant_id) - Grupo 3 (RETURNS <tabela>->jsonb): mark_as_paid, create_financial_record_ for_session, mark_payout_as_paid, create_therapist_payout - can_delete_patient: unqualified, herda search_path do chamador Smoke: mark_as_paid (status=paid, jsonb) + search_global (acha paciente) OK. Frontend (18 sites): p_tenant_id de useTenantStore().activeTenantId (ou helper local resolveTenantId/currentTenantId). create_financial_record_for_session ja passava tenant; retorno SETOF->jsonb transparente (nenhum consumidor indexava array). Build passa. list_my_signatures (cross-tenant) -> Lote F. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -36,6 +36,7 @@ import { useConfirm } from 'primevue/useconfirm';
|
||||
|
||||
import { supabase } from '@/lib/supabase/client';
|
||||
import { tenantDb } from '@/lib/supabase/tenantClient';
|
||||
import { useTenantStore } from '@/stores/tenantStore';
|
||||
import { useAgendaFinanceiro } from '@/composables/useAgendaFinanceiro';
|
||||
import { emitirReciboParaSessao } from '@/services/DocumentGenerate.service';
|
||||
|
||||
@@ -52,6 +53,7 @@ const emit = defineEmits(['cobranca-atualizada']);
|
||||
// ── external ──────────────────────────────────────────────────────────────────
|
||||
const toast = useToast();
|
||||
const confirm = useConfirm();
|
||||
const tenantStore = useTenantStore();
|
||||
const { gerarCobrancaManual, loading: finLoading, error: finError } = useAgendaFinanceiro();
|
||||
|
||||
// ── estado local ──────────────────────────────────────────────────────────────
|
||||
@@ -186,6 +188,7 @@ async function confirmPayment() {
|
||||
payDlgLoading.value = true;
|
||||
try {
|
||||
const { data, error } = await supabase.rpc('mark_as_paid', {
|
||||
p_tenant_id: tenantStore.activeTenantId,
|
||||
p_financial_record_id: record.value.id,
|
||||
p_payment_method: payDlgMethod.value
|
||||
});
|
||||
|
||||
@@ -123,7 +123,7 @@ watch(query, (v) => {
|
||||
const mySeq = ++searchSeq;
|
||||
debounceT = setTimeout(async () => {
|
||||
try {
|
||||
const { data, error } = await supabase.rpc('search_global', { p_q: q, p_limit: 6 });
|
||||
const { data, error } = await supabase.rpc('search_global', { p_tenant_id: tenantStore.activeTenantId, p_q: q, p_limit: 6 });
|
||||
if (mySeq !== searchSeq) return; // resposta antiga, descarta
|
||||
if (error) {
|
||||
// eslint-disable-next-line no-console
|
||||
|
||||
@@ -254,14 +254,19 @@ export function useFinancialRecords() {
|
||||
error.value = null;
|
||||
|
||||
try {
|
||||
const tenantStore = useTenantStore();
|
||||
const tenantId = tenantStore.activeTenantId;
|
||||
assertTenantId(tenantId);
|
||||
|
||||
const { data, error: err } = await supabase.rpc('mark_as_paid', {
|
||||
p_tenant_id: tenantId,
|
||||
p_financial_record_id: recordId,
|
||||
p_payment_method: paymentMethod
|
||||
});
|
||||
|
||||
if (err) throw err;
|
||||
|
||||
// RPC retorna SETOF (array) — patch local direto, sem depender do retorno
|
||||
// RPC retorna jsonb (objeto único) — patch local direto, sem depender do retorno
|
||||
const idx = records.value.findIndex((r) => r.id === recordId);
|
||||
if (idx !== -1) {
|
||||
records.value[idx] = {
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
|
||||
import { ref } from 'vue';
|
||||
import { supabase } from '@/lib/supabase/client';
|
||||
import { useTenantStore } from '@/stores/tenantStore';
|
||||
import { downloadLgpdPDF } from '@/utils/lgpdExportFormats';
|
||||
|
||||
function slugify(s) {
|
||||
@@ -53,7 +54,8 @@ export function useLgpdExport() {
|
||||
throw new Error('patientId obrigatório');
|
||||
}
|
||||
|
||||
const { data, error: rpcErr } = await supabase.rpc('export_patient_data', { p_patient_id: patientId });
|
||||
const tenantId = useTenantStore().activeTenantId;
|
||||
const { data, error: rpcErr } = await supabase.rpc('export_patient_data', { p_tenant_id: tenantId, p_patient_id: patientId });
|
||||
if (rpcErr) throw rpcErr;
|
||||
return data;
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
import { supabase } from '@/lib/supabase/client';
|
||||
|
||||
import { tenantDb } from '@/lib/supabase/tenantClient';
|
||||
import { useTenantStore } from '@/stores/tenantStore';
|
||||
export function usePatientLifecycle() {
|
||||
async function canDelete(patientId) {
|
||||
const { data, error } = await supabase.rpc('can_delete_patient', { p_patient_id: patientId });
|
||||
@@ -25,7 +26,8 @@ export function usePatientLifecycle() {
|
||||
}
|
||||
|
||||
async function deletePatient(patientId) {
|
||||
const { data, error } = await supabase.rpc('safe_delete_patient', { p_patient_id: patientId });
|
||||
const tenantId = useTenantStore().activeTenantId;
|
||||
const { data, error } = await supabase.rpc('safe_delete_patient', { p_tenant_id: tenantId, p_patient_id: patientId });
|
||||
if (error) return { ok: false, error: 'rpc_error', message: error.message };
|
||||
return data; // { ok, error?, message? }
|
||||
}
|
||||
|
||||
@@ -623,7 +623,9 @@ export function useRecurrence() {
|
||||
* Retorna o id da nova regra criada
|
||||
*/
|
||||
async function splitRuleAt(id, fromDateISO) {
|
||||
const tenantId = currentTenantId();
|
||||
const { data, error: err } = await supabase.rpc('split_recurrence_at', {
|
||||
p_tenant_id: tenantId,
|
||||
p_recurrence_id: id,
|
||||
p_from_date: fromDateISO
|
||||
});
|
||||
@@ -635,7 +637,9 @@ export function useRecurrence() {
|
||||
* Cancela a série a partir de uma data
|
||||
*/
|
||||
async function cancelRuleFrom(id, fromDateISO) {
|
||||
const tenantId = currentTenantId();
|
||||
const { error: err } = await supabase.rpc('cancel_recurrence_from', {
|
||||
p_tenant_id: tenantId,
|
||||
p_recurrence_id: id,
|
||||
p_from_date: fromDateISO
|
||||
});
|
||||
|
||||
@@ -163,10 +163,13 @@ export async function createManual(payload) {
|
||||
/**
|
||||
* Marca record como pago via RPC (server-side timestamps + audit).
|
||||
*/
|
||||
export async function markAsPaid(recordId, paymentMethod) {
|
||||
export async function markAsPaid(recordId, paymentMethod, { tenantId } = {}) {
|
||||
if (!recordId) throw new Error('recordId obrigatório.');
|
||||
const tid = resolveTenantId(tenantId);
|
||||
|
||||
// RPC retorna jsonb (objeto único) — `data` é o financial_record, não array.
|
||||
const { data, error } = await supabase.rpc('mark_as_paid', {
|
||||
p_tenant_id: tid,
|
||||
p_financial_record_id: recordId,
|
||||
p_payment_method: paymentMethod
|
||||
});
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
*/
|
||||
import { ref, computed, onMounted, onBeforeUnmount, watch, nextTick } from 'vue';
|
||||
import { supabase } from '@/lib/supabase/client';
|
||||
import { useTenantStore } from '@/stores/tenantStore';
|
||||
import { useRecentPatients } from '@/composables/useRecentPatients';
|
||||
|
||||
const props = defineProps({
|
||||
@@ -35,6 +36,8 @@ const props = defineProps({
|
||||
|
||||
const emit = defineEmits(['acao', 'paciente', 'evento', 'documento', 'intake', 'goto-date']);
|
||||
|
||||
const tenantStore = useTenantStore();
|
||||
|
||||
const rootEl = ref(null);
|
||||
const inputEl = ref(null);
|
||||
const query = ref('');
|
||||
@@ -277,7 +280,7 @@ watch(query, (v) => {
|
||||
const mySeq = ++searchSeq;
|
||||
debounceT = setTimeout(async () => {
|
||||
try {
|
||||
const { data, error } = await supabase.rpc('search_global', { p_q: q, p_limit: 6 });
|
||||
const { data, error } = await supabase.rpc('search_global', { p_tenant_id: tenantStore.activeTenantId, p_q: q, p_limit: 6 });
|
||||
if (mySeq !== searchSeq) return;
|
||||
if (error) {
|
||||
console.error('[MelissaBusca search_global]', error);
|
||||
|
||||
Reference in New Issue
Block a user