agenda Fase C: adapter Rail usa agendaBilling.service
AgendaTerapeutaPage (Rail) ganha o fluxo de status change do
Melissa via novo composable useAgendaStatusChange (reusable
wrapper sobre agendaBilling.service).
src/features/agenda/composables/useAgendaStatusChange.js (novo):
- Composable Tipo A pra qualquer page que precise do flow
load context -> dialog se necessario -> apply decisoes
- Mantem state do dialog + resolver promise
- Expoe applyStatusChange(eventoId, row, novoStatus)
- Resolve ownerId via supabase.auth + tenantId via tenantStore
AgendaTerapeutaPage:
- onUpdateSeriesEvent refatorado: materializa virtual se preciso ->
update status -> applyStatusChange (load ctx + dialog + apply)
- AgendaStatusChangeConfirmDialog plugado no template
Antes: Rail fazia so update(id, { status }) cru — sem multa,
sem pacote, sem reverse, sem nada de C7-C13. Era a versao
primitiva do status change.
Depois: Rail tem feature parity com Melissa pra status change.
Multa por falta, taxa de cancelamento tardio, consumir saldo do
pacote, gerar cobranca de pacote saldo, reverse transition trava
— tudo via mesmo agendaBilling.service.
Pendente Fase C: indicadores visuais (3 canais) + antecipar
pagamento (popover-specific, depende refactor maior do
AgendaEventDialog ou criar Rail popover). Fica pra iter
incremental.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,147 @@
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Agência PSI
|
||||
|--------------------------------------------------------------------------
|
||||
| Arquivo: src/features/agenda/composables/useAgendaStatusChange.js
|
||||
|
|
||||
| Composable Tipo A que orquestra o fluxo de status change da agenda
|
||||
| usando agendaBilling.service. Reusável em Melissa / Rail / Clínica.
|
||||
|
|
||||
| Uso:
|
||||
| const { applyStatusChange, dialogOpen, dialogProps, onDialogConfirm,
|
||||
| onDialogCancel } = useAgendaStatusChange({ toast });
|
||||
|
|
||||
| // No handler:
|
||||
| await applyStatusChange({ eventoId, row, novoStatus });
|
||||
|
|
||||
| // No template:
|
||||
| <AgendaStatusChangeConfirmDialog
|
||||
| v-model="dialogOpen"
|
||||
| :evento="dialogProps.evento"
|
||||
| :novoStatus="dialogProps.novoStatus"
|
||||
| :regraExcecao="dialogProps.regraExcecao"
|
||||
| :billingContract="dialogProps.billingContract"
|
||||
| :billingContractStyle="dialogProps.billingContractStyle"
|
||||
| :pendingRecord="dialogProps.pendingRecord"
|
||||
| :sessionPrice="dialogProps.sessionPrice"
|
||||
| @confirm="onDialogConfirm"
|
||||
| @update:modelValue="(v) => !v && onDialogCancel()"
|
||||
| />
|
||||
|--------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
import { ref } from 'vue';
|
||||
import { supabase } from '@/lib/supabase/client';
|
||||
import { useTenantStore } from '@/stores/tenantStore';
|
||||
import {
|
||||
loadStatusChangeContext,
|
||||
needsStatusConfirmDialog,
|
||||
applyStatusDecisions
|
||||
} from '@/features/agenda/services/agendaBilling.service';
|
||||
|
||||
/**
|
||||
* @param {object} [opts]
|
||||
* @param {object} [opts.toast] instância de useToast (PrimeVue). Opcional.
|
||||
* @returns composable com state reativo + applyStatusChange
|
||||
*/
|
||||
export function useAgendaStatusChange({ toast = null } = {}) {
|
||||
const tenantStore = useTenantStore();
|
||||
|
||||
// Dialog state — bindar no template
|
||||
const dialogOpen = ref(false);
|
||||
const dialogProps = ref({});
|
||||
let _resolveDialog = null;
|
||||
|
||||
function _openDialog(propsObj) {
|
||||
return new Promise((resolve) => {
|
||||
dialogProps.value = propsObj;
|
||||
dialogOpen.value = true;
|
||||
_resolveDialog = resolve;
|
||||
});
|
||||
}
|
||||
|
||||
function onDialogConfirm(decision) {
|
||||
if (_resolveDialog) _resolveDialog(decision);
|
||||
_resolveDialog = null;
|
||||
dialogOpen.value = false;
|
||||
}
|
||||
|
||||
function onDialogCancel() {
|
||||
if (_resolveDialog) _resolveDialog(null);
|
||||
_resolveDialog = null;
|
||||
dialogOpen.value = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Coordena: load context → mostra dialog se preciso → aplica decisões.
|
||||
*
|
||||
* @param {object} args
|
||||
* @param {string} args.eventoId uuid (null pra ocorrências virtuais ainda)
|
||||
* @param {object} args.row row do agenda_eventos (pode ser parcial)
|
||||
* @param {string} args.novoStatus 'realizado' | 'faltou' | 'cancelado' | 'agendado'
|
||||
*
|
||||
* @returns {Promise<{ applied: boolean, decision: object|null, ctx: object }>}
|
||||
* applied=true se passou pelo applyStatusDecisions.
|
||||
* decision=null se user cancelou o dialog.
|
||||
*/
|
||||
async function applyStatusChange({ eventoId, row, novoStatus }) {
|
||||
const ownerId = (await supabase.auth.getUser()).data?.user?.id || null;
|
||||
const tenantId = tenantStore.activeTenantId || tenantStore.tenantId || tenantStore.tenant?.id || null;
|
||||
|
||||
// 1) Carrega contexto
|
||||
const ctx = await loadStatusChangeContext({
|
||||
supabase,
|
||||
row,
|
||||
eventoId,
|
||||
status: novoStatus,
|
||||
ownerId,
|
||||
tenantId
|
||||
});
|
||||
|
||||
// 2) Dialog se preciso
|
||||
let decision = null;
|
||||
if (needsStatusConfirmDialog(novoStatus, ctx)) {
|
||||
decision = await _openDialog({
|
||||
evento: row,
|
||||
novoStatus,
|
||||
regraExcecao: ctx.regraExcecao,
|
||||
billingContract: ctx.billingContract,
|
||||
billingContractStyle: ctx.billingContract?.charging_style || null,
|
||||
pendingRecord: ctx.pendingRecord,
|
||||
sessionPrice: row?.price ?? null
|
||||
});
|
||||
if (!decision) {
|
||||
// user cancelou
|
||||
return { applied: false, decision: null, ctx };
|
||||
}
|
||||
} else {
|
||||
// Sem dialog — default decision vazia (só aplicar status change básico)
|
||||
decision = {};
|
||||
}
|
||||
|
||||
// 3) Aplica decisões
|
||||
await applyStatusDecisions({
|
||||
supabase,
|
||||
toast,
|
||||
eventoId,
|
||||
row,
|
||||
novoStatus,
|
||||
ctx,
|
||||
decision,
|
||||
ownerId,
|
||||
tenantId
|
||||
});
|
||||
|
||||
return { applied: true, decision, ctx };
|
||||
}
|
||||
|
||||
return {
|
||||
// dialog state — pra template
|
||||
dialogOpen,
|
||||
dialogProps,
|
||||
onDialogConfirm,
|
||||
onDialogCancel,
|
||||
// main action
|
||||
applyStatusChange
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user