Ajuste Convenios e Particular
This commit is contained in:
@@ -506,72 +506,36 @@
|
||||
:options="editScopeOptions"
|
||||
optionLabel="label"
|
||||
optionValue="value"
|
||||
:optionDisabled="o => !!o.disabled"
|
||||
class="w-full"
|
||||
size="small"
|
||||
/>
|
||||
<Message v-if="isFirstOccurrence" severity="info" :closable="false" class="mt-2">
|
||||
<span class="text-sm">
|
||||
Esta é a primeira sessão da série.
|
||||
Para alterar todas as ocorrências, use <b>Todos</b> ou <b>Todos sem exceção</b>.
|
||||
</span>
|
||||
</Message>
|
||||
</div>
|
||||
|
||||
<!-- ── CONVÊNIO (só sessão) ─────────────────────── -->
|
||||
<!-- ── FINANCEIRO (só sessão) ────────────────────────── -->
|
||||
<div v-if="isSessionEvent" class="side-card mb-3">
|
||||
<div class="side-card__title mb-2">Convênio</div>
|
||||
<div class="flex flex-col gap-2">
|
||||
<Select
|
||||
v-model="form.insurance_plan_id"
|
||||
:options="insurancePlans"
|
||||
optionLabel="name"
|
||||
optionValue="id"
|
||||
placeholder="Particular (sem convênio)"
|
||||
showClear
|
||||
class="w-full"
|
||||
size="small"
|
||||
/>
|
||||
<template v-if="hasInsurance">
|
||||
<div class="flex gap-2">
|
||||
<div class="flex-1">
|
||||
<label class="text-xs text-color-secondary mb-1 block">Nº da Guia</label>
|
||||
<InputText
|
||||
v-model="form.insurance_guide_number"
|
||||
placeholder="Ex: 123456789"
|
||||
class="w-full"
|
||||
size="small"
|
||||
/>
|
||||
</div>
|
||||
<div class="flex-1">
|
||||
<label class="text-xs text-color-secondary mb-1 block">Valor do Convênio (R$)</label>
|
||||
<InputNumber
|
||||
v-model="form.insurance_value"
|
||||
mode="currency"
|
||||
currency="BRL"
|
||||
locale="pt-BR"
|
||||
class="w-full"
|
||||
size="small"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ── RECORRÊNCIA (só sessão) ───────────────────── -->
|
||||
<div v-if="isSessionEvent" class="side-card">
|
||||
<!-- SelectButton: Gratuito / Particular / Convênio -->
|
||||
<SelectButton
|
||||
v-model="billingType"
|
||||
:options="billingTypeOptions"
|
||||
optionLabel="label"
|
||||
optionValue="value"
|
||||
class="w-full mb-3"
|
||||
/>
|
||||
|
||||
<!-- Serviços / Valor da sessão -->
|
||||
<div class="mb-3">
|
||||
|
||||
<!-- SelectButton Gratuito/Pago -->
|
||||
<SelectButton
|
||||
v-model="billingType"
|
||||
:options="billingTypeOptions"
|
||||
optionLabel="label"
|
||||
optionValue="value"
|
||||
class="mb-3 w-full"
|
||||
/>
|
||||
|
||||
<!-- Seletor de serviço (só quando Pago e há serviços cadastrados) -->
|
||||
<div v-if="billingType === 'pago' && services.length" class="flex gap-2 mb-2">
|
||||
<!-- PARTICULAR: seletor de serviço -->
|
||||
<template v-if="billingType === 'particular'">
|
||||
<div v-if="services.filter(s => s.active).length" class="flex gap-2 mb-2">
|
||||
<Select
|
||||
v-model="servicePickerSel"
|
||||
:options="services"
|
||||
:options="services.filter(s => s.active)"
|
||||
optionLabel="name"
|
||||
optionValue="id"
|
||||
placeholder="Adicionar serviço..."
|
||||
@@ -580,6 +544,87 @@
|
||||
@update:modelValue="(id) => { addItem(services.find(s => s.id === id)); servicePickerSel = null }"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- CONVÊNIO: fluxo progressivo -->
|
||||
<template v-if="billingType === 'convenio'">
|
||||
|
||||
<!-- PASSO 1 — Select do plano (sempre visível) -->
|
||||
<Select
|
||||
v-model="form.insurance_plan_id"
|
||||
:options="activePlans"
|
||||
optionLabel="name"
|
||||
optionValue="id"
|
||||
placeholder="Selecionar convênio..."
|
||||
showClear
|
||||
class="w-full mb-2"
|
||||
size="small"
|
||||
/>
|
||||
|
||||
<!-- PASSO 2 — Procedimento (visível quando plano selecionado) -->
|
||||
<template v-if="hasInsurance">
|
||||
<template v-if="planServices.length > 0">
|
||||
<label class="text-xs text-color-secondary mb-1 block">Procedimento</label>
|
||||
<Select
|
||||
v-model="selectedPlanService"
|
||||
:options="planServices"
|
||||
optionLabel="name"
|
||||
optionValue="id"
|
||||
placeholder="Selecionar procedimento..."
|
||||
showClear
|
||||
class="w-full mb-2"
|
||||
size="small"
|
||||
@update:modelValue="onProcedureSelect"
|
||||
/>
|
||||
</template>
|
||||
<div v-else class="flex items-center justify-between gap-2 mb-2 p-2 rounded-lg bg-surface-100">
|
||||
<span class="text-xs text-color-secondary">Este convênio não tem procedimentos cadastrados.</span>
|
||||
<Button
|
||||
label="Cadastrar"
|
||||
icon="pi pi-plus"
|
||||
size="small"
|
||||
severity="secondary"
|
||||
outlined
|
||||
class="rounded-full shrink-0 text-xs h-7"
|
||||
@click="goToConveniosConfig"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- PASSO 3 — Nº da Guia + Valor (visível após selecionar procedimento ou sem procedimentos) -->
|
||||
<template v-if="selectedPlanService != null || planServices.length === 0">
|
||||
<div class="flex gap-2">
|
||||
<div class="flex-1">
|
||||
<label class="text-xs text-color-secondary mb-1 block">Nº da Guia</label>
|
||||
<InputText
|
||||
v-model="form.insurance_guide_number"
|
||||
placeholder="Ex: 123456789"
|
||||
class="w-full"
|
||||
size="small"
|
||||
/>
|
||||
</div>
|
||||
<div class="flex-1">
|
||||
<label class="text-xs text-color-secondary mb-1 block">Valor (R$)</label>
|
||||
<InputNumber
|
||||
v-model="form.insurance_value"
|
||||
mode="currency"
|
||||
currency="BRL"
|
||||
locale="pt-BR"
|
||||
:readonly="planServices.length > 0 && selectedPlanService != null"
|
||||
class="w-full"
|
||||
size="small"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</template>
|
||||
|
||||
</template>
|
||||
|
||||
</div>
|
||||
|
||||
<!-- ── RECORRÊNCIA (só sessão) ───────────────────── -->
|
||||
<div v-if="isSessionEvent" class="side-card">
|
||||
<div class="mb-3">
|
||||
|
||||
<!-- Lista de itens adicionados -->
|
||||
<div v-if="commitmentItems.length" class="commitment-items-list mb-2">
|
||||
@@ -1063,6 +1108,7 @@ const props = defineProps({
|
||||
initialEndISO: { type: String, default: '' },
|
||||
|
||||
ownerId: { type: String, default: '' },
|
||||
planOwnerId: { type: String, default: '' }, // owner dos convênios (clínica); fallback: ownerId
|
||||
allowOwnerEdit: { type: Boolean, default: false },
|
||||
ownerOptions: { type: Array, default: () => [] },
|
||||
|
||||
@@ -1110,12 +1156,27 @@ const currentRecurrenceDate = computed(() =>
|
||||
)
|
||||
|
||||
const editScope = ref('somente_este')
|
||||
const editScopeOptions = [
|
||||
|
||||
const isFirstOccurrence = computed(() => {
|
||||
if (!hasSerie.value) return false
|
||||
const rDate = props.eventRow?.recurrence_date || props.eventRow?.original_date
|
||||
if (!rDate) return false
|
||||
if (serieEvents.value?.length) {
|
||||
const dates = serieEvents.value
|
||||
.map(e => e.recurrence_date || e.original_date)
|
||||
.filter(Boolean)
|
||||
.sort()
|
||||
return dates[0] === rDate
|
||||
}
|
||||
return false
|
||||
})
|
||||
|
||||
const editScopeOptions = computed(() => [
|
||||
{ value: 'somente_este', label: 'Somente esta sessão' },
|
||||
{ value: 'este_e_seguintes', label: 'Esta e as seguintes' },
|
||||
{ value: 'este_e_seguintes', label: 'Esta e as seguintes', disabled: isFirstOccurrence.value },
|
||||
{ value: 'todos', label: 'Todas da série' },
|
||||
{ value: 'todos_sem_excecao', label: 'Todas sem exceção' },
|
||||
]
|
||||
])
|
||||
|
||||
// ── recorrência (criação / sessão avulsa) ──────────────────
|
||||
// 'avulsa' | 'semanal' | 'quinzenal' | 'diasEspecificos'
|
||||
@@ -1288,6 +1349,17 @@ const { services, getDefaultPrice, load: loadServices } = useServices()
|
||||
const { loadItems: _csLoadItems, saveItems: saveCommitmentItems, loadItemsOrTemplate: _csLoadItemsOrTemplate } = useCommitmentServices()
|
||||
const { loadActive: loadActiveDiscount } = usePatientDiscounts()
|
||||
const { plans: insurancePlans, load: loadInsurancePlans } = useInsurancePlans()
|
||||
const selectedPlanService = ref(null)
|
||||
|
||||
const activePlans = computed(() => insurancePlans.value.filter(p => p.active !== false))
|
||||
|
||||
const planServices = computed(() => {
|
||||
if (!form.value.insurance_plan_id) return []
|
||||
return (insurancePlans.value
|
||||
.find(p => p.id === form.value.insurance_plan_id)
|
||||
?.insurance_plan_services || [])
|
||||
.filter(s => s.active)
|
||||
})
|
||||
let _servicesLoaded = false
|
||||
|
||||
async function ensureServicesLoaded () {
|
||||
@@ -1298,7 +1370,7 @@ async function ensureServicesLoaded () {
|
||||
|
||||
function applyDefaultPrice () {
|
||||
// Pula quando pago: o preço vem dos commitmentItems, não de um default
|
||||
if (billingType.value === 'pago') return
|
||||
if (billingType.value === 'particular') return
|
||||
// Só auto-preenche se price ainda não foi definido manualmente (ou é novo evento)
|
||||
if (!isEdit.value) {
|
||||
const suggested = getDefaultPrice()
|
||||
@@ -1311,16 +1383,32 @@ const commitmentItems = ref([])
|
||||
const servicePickerSel = ref(null)
|
||||
const serieValorMode = ref('multiplicar') // 'multiplicar' | 'dividir'
|
||||
|
||||
const billingType = ref('pago') // 'gratuito' | 'pago'
|
||||
const billingType = ref('particular') // 'gratuito' | 'particular' | 'convenio'
|
||||
const _restoringConvenio = ref(false) // flag para ignorar watch durante restauração
|
||||
const billingTypeOptions = [
|
||||
{ label: 'Gratuito', value: 'gratuito' },
|
||||
{ label: 'Pago', value: 'pago' },
|
||||
{ label: 'Gratuito', value: 'gratuito' },
|
||||
{ label: 'Particular', value: 'particular' },
|
||||
{ label: 'Convênio', value: 'convenio' },
|
||||
]
|
||||
|
||||
watch(billingType, (val) => {
|
||||
if (val === 'gratuito') {
|
||||
commitmentItems.value = []
|
||||
form.value.price = 0
|
||||
commitmentItems.value = []
|
||||
form.value.price = 0
|
||||
form.value.insurance_plan_id = null
|
||||
form.value.insurance_guide_number = null
|
||||
form.value.insurance_value = null
|
||||
selectedPlanService.value = null
|
||||
}
|
||||
if (val === 'particular') {
|
||||
form.value.insurance_plan_id = null
|
||||
form.value.insurance_guide_number = null
|
||||
form.value.insurance_value = null
|
||||
selectedPlanService.value = null
|
||||
}
|
||||
if (val === 'convenio') {
|
||||
commitmentItems.value = []
|
||||
servicePickerSel.value = null
|
||||
}
|
||||
})
|
||||
|
||||
@@ -1426,16 +1514,46 @@ function onItemChange (item) {
|
||||
async function _loadCommitmentItemsForEvent (eventId) {
|
||||
const ruleId = props.eventRow?.recurrence_id ?? null
|
||||
const isCustomized = props.eventRow?.services_customized ?? false
|
||||
if (!eventId && !ruleId) { commitmentItems.value = []; billingType.value = 'gratuito'; return }
|
||||
// Lê os dados de convênio diretamente do eventRow (form.value pode ter sido limpo por watches)
|
||||
const origPlanId = props.eventRow?.insurance_plan_id ?? null
|
||||
const origGuide = props.eventRow?.insurance_guide_number ?? null
|
||||
const origInsValue = props.eventRow?.insurance_value != null ? Number(props.eventRow.insurance_value) : null
|
||||
|
||||
function applyConvenio () {
|
||||
const origPsId = props.eventRow?.insurance_plan_service_id ?? null
|
||||
_restoringConvenio.value = true
|
||||
form.value.insurance_plan_id = origPlanId
|
||||
form.value.insurance_guide_number = origGuide
|
||||
form.value.insurance_value = origInsValue
|
||||
form.value.insurance_plan_service_id = origPsId
|
||||
billingType.value = 'convenio'
|
||||
nextTick(() => {
|
||||
if (origPsId && planServices.value.find(s => s.id === origPsId)) {
|
||||
selectedPlanService.value = origPsId
|
||||
} else {
|
||||
selectedPlanService.value = null
|
||||
}
|
||||
_restoringConvenio.value = false
|
||||
})
|
||||
}
|
||||
|
||||
if (!eventId && !ruleId) {
|
||||
commitmentItems.value = []
|
||||
if (origPlanId) applyConvenio()
|
||||
else billingType.value = 'particular'
|
||||
return
|
||||
}
|
||||
try {
|
||||
commitmentItems.value = ruleId
|
||||
? await _csLoadItemsOrTemplate(eventId, ruleId, { allowEmpty: isCustomized })
|
||||
: await _csLoadItems(eventId)
|
||||
billingType.value = commitmentItems.value.length > 0 ? 'pago' : 'gratuito'
|
||||
if (origPlanId) applyConvenio()
|
||||
else billingType.value = commitmentItems.value.length > 0 ? 'particular' : 'gratuito'
|
||||
} catch (e) {
|
||||
console.warn('[AgendaEventDialog] commitment_services load error:', e?.message)
|
||||
commitmentItems.value = []
|
||||
billingType.value = 'gratuito'
|
||||
if (origPlanId) applyConvenio()
|
||||
else billingType.value = 'gratuito'
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1675,23 +1793,25 @@ watch(
|
||||
|
||||
// Pré-carrega serviços para auto-fill de preço
|
||||
ensureServicesLoaded()
|
||||
if (props.ownerId) {
|
||||
await loadInsurancePlans(props.ownerId)
|
||||
// Se já tem convênio selecionado (edição), aplica o valor padrão agora que os planos estão carregados
|
||||
const planId = form.value.insurance_plan_id
|
||||
if (planId && !form.value.insurance_value) {
|
||||
const plan = insurancePlans.value.find(p => p.id === planId)
|
||||
if (plan?.default_value) form.value.insurance_value = plan.default_value
|
||||
}
|
||||
const insuranceOwner = props.planOwnerId || props.ownerId
|
||||
if (insuranceOwner) {
|
||||
await loadInsurancePlans(insuranceOwner)
|
||||
// planServices é computed — atualiza automaticamente após insurancePlans carregar
|
||||
}
|
||||
|
||||
// Reset convênio (será restaurado por _loadCommitmentItemsForEvent se necessário)
|
||||
selectedPlanService.value = null
|
||||
_restoringConvenio.value = false
|
||||
|
||||
// Reset e carrega itens de serviço do evento (commitment_services)
|
||||
commitmentItems.value = []
|
||||
servicePickerSel.value = null
|
||||
billingType.value = 'pago'
|
||||
// Carrega serviços para eventos reais (form.value.id) ou template para ocorrências virtuais (só recurrence_id)
|
||||
// _loadCommitmentItemsForEvent determina o billingType correto (incluindo 'convenio')
|
||||
if (isEdit.value && (form.value.id || props.eventRow?.recurrence_id)) {
|
||||
_loadCommitmentItemsForEvent(form.value.id)
|
||||
} else {
|
||||
billingType.value = 'particular'
|
||||
}
|
||||
}
|
||||
)
|
||||
@@ -1718,12 +1838,28 @@ watch(
|
||||
watch(
|
||||
() => form.value.insurance_plan_id,
|
||||
(planId) => {
|
||||
if (!planId) { form.value.insurance_value = null; return }
|
||||
const plan = insurancePlans.value.find(p => p.id === planId)
|
||||
if (plan?.default_value) form.value.insurance_value = plan.default_value
|
||||
if (_restoringConvenio.value) return
|
||||
selectedPlanService.value = null
|
||||
form.value.insurance_plan_service_id = null
|
||||
if (!planId) {
|
||||
// Limpa campos de convênio ao desmarcar
|
||||
form.value.insurance_value = null
|
||||
form.value.insurance_guide_number = null
|
||||
return
|
||||
}
|
||||
// Ao selecionar convênio: exclusividade — remove serviços do caminho A
|
||||
commitmentItems.value = []
|
||||
servicePickerSel.value = null
|
||||
}
|
||||
)
|
||||
|
||||
function onProcedureSelect (psId) {
|
||||
form.value.insurance_plan_service_id = psId ?? null
|
||||
if (!psId) { form.value.insurance_value = null; return }
|
||||
const ps = planServices.value.find(s => s.id === psId)
|
||||
form.value.insurance_value = ps?.value != null ? Number(ps.value) : null
|
||||
}
|
||||
|
||||
watch(
|
||||
() => [form.value.paciente_id, form.value.dia?.toString()],
|
||||
async () => {
|
||||
@@ -1779,6 +1915,12 @@ function goToAgendamentosRecebidos () {
|
||||
router.push(`${prefix}/agendamentos-recebidos`)
|
||||
}
|
||||
|
||||
function goToConveniosConfig () {
|
||||
visible.value = false
|
||||
const prefix = route.path.startsWith('/admin') ? '/admin' : '/therapist'
|
||||
router.push(`${prefix}/configuracoes/convenios`)
|
||||
}
|
||||
|
||||
// ── duração / slots ────────────────────────────────────────
|
||||
// grouped duration options: default preset first, then com pausa, then sem pausa
|
||||
const duracaoOptions = computed(() => {
|
||||
@@ -2260,7 +2402,7 @@ const canSave = computed(() => {
|
||||
if (!form.value.startTime) return false
|
||||
if (!form.value.commitment_id) return false
|
||||
if (requiresPatient.value && !form.value.paciente_id) return false
|
||||
if (isSessionEvent.value && billingType.value === 'pago' && commitmentItems.value.length === 0) return false
|
||||
if (isSessionEvent.value && billingType.value === 'particular' && commitmentItems.value.length === 0) return false
|
||||
return true
|
||||
})
|
||||
|
||||
@@ -2294,9 +2436,10 @@ function onSave () {
|
||||
titulo_custom: form.value.titulo_custom || null,
|
||||
extra_fields: Object.keys(form.value.extra_fields || {}).length ? form.value.extra_fields : null,
|
||||
price: isSessionEvent.value ? (form.value.price ?? null) : null,
|
||||
insurance_plan_id: isSessionEvent.value ? (form.value.insurance_plan_id ?? null) : null,
|
||||
insurance_guide_number: isSessionEvent.value ? (form.value.insurance_guide_number ?? null) : null,
|
||||
insurance_value: isSessionEvent.value ? (form.value.insurance_value ?? null) : null,
|
||||
insurance_plan_id: isSessionEvent.value ? (form.value.insurance_plan_id ?? null) : null,
|
||||
insurance_guide_number: isSessionEvent.value ? (form.value.insurance_guide_number ?? null) : null,
|
||||
insurance_value: isSessionEvent.value ? (form.value.insurance_value ?? null) : null,
|
||||
insurance_plan_service_id: isSessionEvent.value ? (form.value.insurance_plan_service_id ?? null) : null,
|
||||
}
|
||||
|
||||
// recorrência — só quando é sessão e não avulsa
|
||||
@@ -2375,7 +2518,7 @@ function onDelete () {
|
||||
: 'Esta sessão faz parte de uma série. O que deseja remover?',
|
||||
icon: isTodos ? 'pi pi-trash' : 'pi pi-exclamation-triangle',
|
||||
acceptClass: 'p-button-danger',
|
||||
acceptLabel: isTodos ? 'Sim, encerrar série' : (editScopeOptions.find(o => o.value === editScope.value)?.label || 'Excluir'),
|
||||
acceptLabel: isTodos ? 'Sim, encerrar série' : (editScopeOptions.value.find(o => o.value === editScope.value)?.label || 'Excluir'),
|
||||
rejectLabel: 'Cancelar',
|
||||
accept: () => emit('delete', {
|
||||
id: form.value.id,
|
||||
@@ -2478,9 +2621,10 @@ function resetForm () {
|
||||
conflito: null,
|
||||
extra_fields: r?.extra_fields && typeof r.extra_fields === 'object' ? { ...r.extra_fields } : {},
|
||||
price: r?.price != null ? Number(r.price) : null,
|
||||
insurance_plan_id: r?.insurance_plan_id ?? null,
|
||||
insurance_guide_number: r?.insurance_guide_number ?? null,
|
||||
insurance_value: r?.insurance_value != null ? Number(r.insurance_value) : null,
|
||||
insurance_plan_id: r?.insurance_plan_id ?? null,
|
||||
insurance_guide_number: r?.insurance_guide_number ?? null,
|
||||
insurance_value: r?.insurance_value != null ? Number(r.insurance_value) : null,
|
||||
insurance_plan_service_id: r?.insurance_plan_service_id ?? null,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user