-- Follow-up to 20260812105003 (deferred from the final whole-branch review -- before merge). Postgres migrations are append-only history, so this -- replaces the trigger function and the partial unique index rather than -- editing the already-applied 20260812000002/20260812000003 files. -- -- Problem: 20260812105003 widened bookings.status to allow 'completed', and -- the admin panel gained a "Mark completed" action, but sync_booked_slot() -- (20260812000003) and bookings_active_slot_idx (20260812000002) still only -- treat status = 'confirmed' as "slot occupied". As a result: -- 1. confirmed -> completed deletes the booked_slots mirror row for that -- slot, exactly as if the booking had been cancelled. -- 2. The partial unique index (where status = 'confirmed') no longer -- blocks a second confirmed booking for a now-completed slot. -- A completed appointment did happen and must continue to occupy its slot; -- only 'cancelled' should free it. -- -- Fix: every place sync_booked_slot() tested status = 'confirmed' now tests -- status in ('confirmed', 'completed'), and the partial unique index is -- rebuilt with the same predicate. create or replace function public.sync_booked_slot() returns trigger language plpgsql security definer set search_path = '' as $$ begin if tg_op = 'DELETE' then if old.status in ('confirmed', 'completed') then delete from public.booked_slots where date = old.date and time = old.time and not exists ( select 1 from public.bookings b where b.date = old.date and b.time = old.time and b.status in ('confirmed', 'completed') ); end if; return old; end if; if tg_op = 'UPDATE' then -- Covers both a status change away from 'confirmed'/'completed' -- (cancellation) and a reschedule of a still-occupying booking to a -- different (date, time): either way, the OLD slot may no longer have -- any confirmed/completed booking holding it and its mirror row needs -- to go. if old.status in ('confirmed', 'completed') and (old.date, old.time, old.status) is distinct from (new.date, new.time, new.status) then delete from public.booked_slots where date = old.date and time = old.time and not exists ( select 1 from public.bookings b where b.date = old.date and b.time = old.time and b.status in ('confirmed', 'completed') ); end if; if new.status in ('confirmed', 'completed') then insert into public.booked_slots (date, time) values (new.date, new.time) on conflict do nothing; end if; return new; end if; -- INSERT if new.status in ('confirmed', 'completed') then insert into public.booked_slots (date, time) values (new.date, new.time) on conflict do nothing; end if; return new; end; $$; drop index if exists public.bookings_active_slot_idx; create unique index bookings_active_slot_idx on public.bookings (date, time) where status in ('confirmed', 'completed');