-- ==================================================================== -- 0053_booking_calendar — Kalenderbuchung (Booking Phase 1, Microsoft Graph) -- ==================================================================== -- Baut auf Phase 0 (0052_appointment_requests) auf. Modell: zentrale -- Azure-AD-App mit *Application Permissions* (IT erteilt EINMAL Admin- -- Consent) + Opt-in pro Mitarbeiter. Kein Pro-MA-OAuth, keine Refresh- -- Token-Speicherung — das App-only-Token kommt per Client-Credentials, -- nur das Client-Secret liegt in der ENV. Siehe docs/booking-modul.md. -- -- DSGVO: Der Opt-in-Schalter (calendar_connections.enabled) ist die -- aktive Entscheidung des MA. Zusätzlich beschränkt eine Exchange -- Application Access Policy die App technisch auf eine Sicherheitsgruppe. -- ── Pro-MA-Verbindung + Verfügbarkeitsregeln ─────────────────────────── create table if not exists public.calendar_connections ( id uuid primary key default gen_random_uuid(), employee_id uuid not null unique references public.employees(id) on delete cascade, provider text not null default 'microsoft', -- M365-Postfach für die Graph-Calls (UPN/Mailbox, i. d. R. = Dienst-Mail). mailbox_upn text not null, -- Opt-in: nur wenn true, ruft TeamVis Graph für dieses Postfach auf. enabled boolean not null default true, -- Verfügbarkeitsregeln (Slots = Arbeitszeit − Belegt − vergebene Buchungen) timezone text not null default 'Europe/Berlin', slot_minutes integer not null default 30, buffer_minutes integer not null default 0, min_notice_hours integer not null default 24, max_advance_days integer not null default 30, workday_start time not null default '09:00', workday_end time not null default '17:00', -- ISO-Wochentage: 1=Montag … 7=Sonntag workdays integer[] not null default '{1,2,3,4,5}', -- Diagnostik: letzter erfolgreicher „Verbindung testen", letzter Fehler. last_verified_at timestamptz, last_error text, created_at timestamptz not null default now(), updated_at timestamptz not null default now() ); comment on table public.calendar_connections is 'Opt-in pro Mitarbeiter für die Microsoft-Graph-Kalenderbuchung (Phase 1).'; -- ── Bestätigte Buchungen (echter Kalendereintrag via Graph) ──────────── create table if not exists public.bookings ( id uuid primary key default gen_random_uuid(), employee_id uuid not null references public.employees(id) on delete cascade, appointment_request_id uuid references public.appointment_requests(id) on delete set null, start_at timestamptz not null, end_at timestamptz not null, time_zone text not null default 'Europe/Berlin', guest_name text not null, guest_email text, guest_phone text, company text, subject text, message text, meeting_type text, -- 'vor_ort' | 'telefon' | 'video' | null status text not null default 'confirmed', -- 'confirmed' | 'cancelled' graph_event_id text, -- Outlook-Event-ID (für Storno/Update) ical_uid text, cancel_token text not null, -- Self-Service-Storno durch den Gast consent_given boolean not null default false, source_url text, created_at timestamptz not null default now() ); create index if not exists bookings_employee_idx on public.bookings (employee_id, start_at); create index if not exists bookings_cancel_token_idx on public.bookings (cancel_token); -- ── RLS: admin/service-role only, kein anon-Grant ────────────────────── -- Frei/Belegt-Lookup und Insert laufen ausschließlich über serverseitige -- (rate-limitierte) Service-Role-Actions, wie card_leads/appointment_requests. alter table public.calendar_connections enable row level security; alter table public.bookings enable row level security; grant select, insert, update, delete on public.calendar_connections to service_role; grant select, insert, update, delete on public.bookings to service_role;