agenda Fase D: adapter Clinica usa agendaBilling.service
AgendaClinicaPage espelha Fase C: useAgendaStatusChange composable
+ AgendaStatusChangeConfirmDialog plugado.
onUpdateSeriesEvent reescrito:
- Materializa virtual se preciso (via createClinic com status='agendado'
+ tenantId)
- updateClinic({ status }) no DB
- applyStatusChange(eventoId, row, novoStatus) ramifica via dialog
quando preciso
- loadClinicRange() refetch apos applied
Mesma feature parity de Melissa pra status change na Clinica:
multa, taxa cancelamento tardio, consumir saldo, gerar cobranca
pacote saldo, reverse transition trava — tudo via agendaBilling.service.
Fase C (Rail) + Fase D (Clinica) fechadas. Os 3 layouts (Melissa/
Rail/Clinica) agora compartilham o billing core do agendaBilling.
service via composable useAgendaStatusChange.
Pendente (residual incremental):
- Indicadores visuais (3 canais) nos 3 layouts
- Antecipar/Revogar pagamento no popover de Rail (Rail nao tem
popover separado — usa AgendaEventDialog direto; precisa
refactor maior)
- Doc de ajuda
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -36,6 +36,12 @@ import { useDeterminedCommitments } from '@/features/agenda/composables/useDeter
|
||||
import { useFeriados } from '@/composables/useFeriados';
|
||||
import { useAgendaBloqueios } from '@/features/agenda/composables/useAgendaBloqueios';
|
||||
|
||||
// Fase D (replicação Clínica): adopta agendaBilling.service via composable
|
||||
// reutilizável. Cobre status change com confirm dialog + multa + reverse +
|
||||
// pacote saldo/upfront (C7-C13 de Melissa, espelho da Fase C do Rail).
|
||||
import { useAgendaStatusChange } from '@/features/agenda/composables/useAgendaStatusChange';
|
||||
import AgendaStatusChangeConfirmDialog from '@/features/agenda/components/AgendaStatusChangeConfirmDialog.vue';
|
||||
|
||||
import { mapAgendaEventosToCalendarEvents, minutesToDuration } from '@/features/agenda/services/agendaMappers';
|
||||
import { useAgendaSettings } from '@/features/agenda/composables/useAgendaSettings';
|
||||
import { useTenantStore } from '@/stores/tenantStore';
|
||||
@@ -502,6 +508,15 @@ const ownerOptions = computed(() => staffCols.value.map((p) => ({ label: p.title
|
||||
// -------------------- events --------------------
|
||||
const { loading: loadingEvents, error: eventsError, rows, loadClinicRange, createClinic, updateClinic, removeClinic } = useAgendaClinicEvents();
|
||||
|
||||
// Fase D: status change com confirm dialog + billing (Melissa pattern).
|
||||
const {
|
||||
dialogOpen: statusDialogOpen,
|
||||
dialogProps: statusDialogProps,
|
||||
onDialogConfirm: onStatusDialogConfirm,
|
||||
onDialogCancel: onStatusDialogCancel,
|
||||
applyStatusChange
|
||||
} = useAgendaStatusChange({ toast });
|
||||
|
||||
const { loadAndExpand, createRule, updateRule, cancelRule, splitRuleAt, cancelRuleFrom, upsertException } = useRecurrence();
|
||||
|
||||
const { saveRuleItems, propagateToSerie } = useCommitmentServices();
|
||||
@@ -1189,29 +1204,33 @@ function onEditSeriesOccurrence({ id, recurrence_date, inicio_em, fim_em, is_vir
|
||||
async function onUpdateSeriesEvent({ id, status, recurrence_date, inicio_em, fim_em, is_virtual }) {
|
||||
const tid = tenantId.value;
|
||||
try {
|
||||
if (id) {
|
||||
await updateClinic(id, { status }, { tenantId: tid });
|
||||
return;
|
||||
}
|
||||
if (!is_virtual || !inicio_em) return;
|
||||
|
||||
const rid = dialogEventRow.value?.recurrence_id ?? dialogEventRow.value?.serie_id ?? null;
|
||||
const rDate = recurrence_date || inicio_em?.slice(0, 10);
|
||||
|
||||
const { data: existing } = await supabase.from('agenda_eventos').select('id').eq('recurrence_id', rid).eq('recurrence_date', rDate).maybeSingle();
|
||||
|
||||
if (existing?.id) {
|
||||
await updateClinic(existing.id, { status }, { tenantId: tid });
|
||||
} else {
|
||||
const row = dialogEventRow.value || {};
|
||||
await createClinic(
|
||||
|
||||
// 1) Materializar virtual se preciso (resolve eventoId real)
|
||||
let eventoId = id;
|
||||
if (!id) {
|
||||
if (!is_virtual || !inicio_em) return;
|
||||
const rid = row.recurrence_id ?? row.serie_id ?? null;
|
||||
const rDate = recurrence_date || inicio_em?.slice(0, 10);
|
||||
const { data: existing } = await supabase
|
||||
.from('agenda_eventos')
|
||||
.select('id')
|
||||
.eq('recurrence_id', rid)
|
||||
.eq('recurrence_date', rDate)
|
||||
.maybeSingle();
|
||||
if (existing?.id) {
|
||||
eventoId = existing.id;
|
||||
} else {
|
||||
// Materializa com status='agendado'; o status final aplica
|
||||
// após applyStatusChange ramificar pelo dialog se preciso.
|
||||
const created = await createClinic(
|
||||
{
|
||||
owner_id: dialogOwnerId.value || clinicOwnerId.value,
|
||||
tenant_id: tid,
|
||||
recurrence_id: rid,
|
||||
recurrence_date: rDate,
|
||||
tipo: 'sessao',
|
||||
status,
|
||||
status: 'agendado',
|
||||
inicio_em,
|
||||
fim_em,
|
||||
visibility_scope: 'public',
|
||||
@@ -1221,6 +1240,22 @@ async function onUpdateSeriesEvent({ id, status, recurrence_date, inicio_em, fim
|
||||
},
|
||||
{ tenantId: tid }
|
||||
);
|
||||
eventoId = created?.id || null;
|
||||
}
|
||||
}
|
||||
|
||||
// 2) Atualiza status no DB
|
||||
if (eventoId) {
|
||||
await updateClinic(eventoId, { status }, { tenantId: tid });
|
||||
}
|
||||
|
||||
// 3) Fluxo de billing (load context + dialog + apply)
|
||||
const rowWithStatus = { ...row, id: eventoId, status, inicio_em, fim_em };
|
||||
const { applied } = await applyStatusChange({ eventoId, row: rowWithStatus, novoStatus: status });
|
||||
|
||||
// 4) Refetch se aplicou (UI reflete novo estado)
|
||||
if (applied && typeof loadClinicRange === 'function') {
|
||||
await loadClinicRange();
|
||||
}
|
||||
} catch (e) {
|
||||
toast.add({ severity: 'warn', summary: 'Erro', detail: e?.message || 'Falha ao atualizar status.', life: 3000 });
|
||||
@@ -2463,6 +2498,21 @@ function goRecorrencias() {
|
||||
<!-- Dialog de Bloqueio -->
|
||||
<BloqueioDialog v-model="bloqueioDialogOpen" :mode="bloqueioMode" :workRules="workRules" :settings="settings" :ownerId="clinicOwnerId" :tenantId="tenantId || ''" @saved="refetch" />
|
||||
|
||||
<!-- Fase D: confirma status change com decisões de billing
|
||||
(multa, consumir saldo, gerar cobrança, reverse transition). -->
|
||||
<AgendaStatusChangeConfirmDialog
|
||||
v-model="statusDialogOpen"
|
||||
:evento="statusDialogProps.evento"
|
||||
:novoStatus="statusDialogProps.novoStatus"
|
||||
:regraExcecao="statusDialogProps.regraExcecao"
|
||||
:billingContract="statusDialogProps.billingContract"
|
||||
:billingContractStyle="statusDialogProps.billingContractStyle"
|
||||
:pendingRecord="statusDialogProps.pendingRecord"
|
||||
:sessionPrice="statusDialogProps.sessionPrice"
|
||||
@confirm="onStatusDialogConfirm"
|
||||
@update:modelValue="(v) => !v && onStatusDialogCancel()"
|
||||
/>
|
||||
|
||||
<!-- Dialog: feriados próximos (todos os dias úteis — bloqueados e pendentes) -->
|
||||
<Dialog v-model:visible="feriadosAlertaOpen" modal :draggable="false" header="Feriados nos próximos 30 dias" :style="{ width: '520px', maxWidth: '96vw' }">
|
||||
<div class="flex flex-col gap-3">
|
||||
|
||||
Reference in New Issue
Block a user