Agenda google, avisos globais, feriados + avisos globais, templates de email, configuracoes empresa, preview empresa.

This commit is contained in:
Leonardo
2026-03-18 15:47:37 -03:00
parent d6d2fe29d1
commit 29ed349cf2
21 changed files with 5366 additions and 41 deletions

View File

@@ -1,7 +1,8 @@
-- ═══════════════════════════════════════════════════════════════════════════
-- FIX: agendador_slots_disponiveis + agendador_dias_disponiveis
-- Usa agenda_online_slots como fonte de slots
-- Cruzamento com: agenda_eventos, recurrence_rules/exceptions, agendador_solicitacoes
-- Cruzamento com: agenda_eventos, recurrence_rules/exceptions,
-- agendador_solicitacoes, agenda_bloqueios (feriados/bloqueios)
-- Execute no Supabase SQL Editor
-- ═══════════════════════════════════════════════════════════════════════════
@@ -42,6 +43,22 @@ BEGIN
v_agora := now();
v_db_dow := extract(dow from p_data::timestamp)::int;
-- ── Dia inteiro bloqueado? (agenda_bloqueios sem hora) ───────────────────
-- Se sim, não há nenhum slot disponível — retorna vazio.
IF EXISTS (
SELECT 1 FROM public.agenda_bloqueios b
WHERE b.owner_id = v_owner_id
AND b.data_inicio <= p_data
AND COALESCE(b.data_fim, p_data) >= p_data
AND b.hora_inicio IS NULL -- bloqueio de dia inteiro
AND (
(NOT b.recorrente)
OR (b.recorrente AND b.dia_semana = v_db_dow)
)
) THEN
RETURN;
END IF;
FOR v_slot IN
SELECT s.time
FROM public.agenda_online_slots s
@@ -60,6 +77,23 @@ BEGIN
v_ocupado := true;
END IF;
-- ── Bloqueio de horário específico (agenda_bloqueios com hora) ───────────
IF NOT v_ocupado THEN
SELECT EXISTS (
SELECT 1 FROM public.agenda_bloqueios b
WHERE b.owner_id = v_owner_id
AND b.data_inicio <= p_data
AND COALESCE(b.data_fim, p_data) >= p_data
AND b.hora_inicio IS NOT NULL
AND b.hora_inicio < v_slot_fim
AND b.hora_fim > v_slot
AND (
(NOT b.recorrente)
OR (b.recorrente AND b.dia_semana = v_db_dow)
)
) INTO v_ocupado;
END IF;
-- ── Eventos avulsos internos (agenda_eventos) ────────────────────────────
IF NOT v_ocupado THEN
SELECT EXISTS (
@@ -73,7 +107,6 @@ BEGIN
END IF;
-- ── Recorrências ativas (recurrence_rules) ───────────────────────────────
-- Loop explícito para evitar erros de tipo no cálculo do ciclo semanal
IF NOT v_ocupado THEN
FOR v_rule IN
SELECT
@@ -92,16 +125,12 @@ BEGIN
AND r.start_time::time < v_slot_fim
AND r.end_time::time > v_slot
LOOP
-- Calcula a primeira ocorrência do dia-da-semana a partir do start_date
v_rule_start_dow := extract(dow from v_rule.start_date)::int;
v_first_occ := v_rule.start_date
+ (((v_db_dow - v_rule_start_dow + 7) % 7))::int;
v_day_diff := (p_data - v_first_occ)::int;
-- Ocorrência válida: diff >= 0 e divisível pelo ciclo semanal
IF v_day_diff >= 0 AND v_day_diff % (7 * v_rule.interval) = 0 THEN
-- Verifica se há exceção para esta data
v_ex_type := NULL;
SELECT ex.type INTO v_ex_type
FROM public.recurrence_exceptions ex
@@ -109,21 +138,19 @@ BEGIN
AND ex.original_date = p_data
LIMIT 1;
-- Sem exceção, ou exceção que não cancela → bloqueia o slot
IF v_ex_type IS NULL OR v_ex_type NOT IN (
'cancel_session', 'patient_missed',
'therapist_canceled', 'holiday_block',
'reschedule_session'
) THEN
v_ocupado := true;
EXIT; -- já basta uma regra que conflite
EXIT;
END IF;
END IF;
END LOOP;
END IF;
-- ── Recorrências remarcadas para este dia (reschedule → new_date = p_data)
-- ── Recorrências remarcadas para este dia ───────────────────────────────
IF NOT v_ocupado THEN
SELECT EXISTS (
SELECT 1
@@ -180,6 +207,7 @@ DECLARE
v_data_fim date;
v_db_dow int;
v_tem_slot boolean;
v_bloqueado boolean;
BEGIN
SELECT c.owner_id, c.antecedencia_minima_horas
INTO v_owner_id, v_antecedencia
@@ -197,6 +225,25 @@ BEGIN
WHILE v_data <= v_data_fim LOOP
v_db_dow := extract(dow from v_data::timestamp)::int;
-- ── Dia inteiro bloqueado? (agenda_bloqueios) ─────────────────────────
SELECT EXISTS (
SELECT 1 FROM public.agenda_bloqueios b
WHERE b.owner_id = v_owner_id
AND b.data_inicio <= v_data
AND COALESCE(b.data_fim, v_data) >= v_data
AND b.hora_inicio IS NULL -- bloqueio de dia inteiro
AND (
(NOT b.recorrente)
OR (b.recorrente AND b.dia_semana = v_db_dow)
)
) INTO v_bloqueado;
IF v_bloqueado THEN
v_data := v_data + 1;
CONTINUE;
END IF;
-- ── Tem slots disponíveis no dia? ─────────────────────────────────────
SELECT EXISTS (
SELECT 1 FROM public.agenda_online_slots s
WHERE s.owner_id = v_owner_id