* feat(booking-templates): per-company opt-in hiding of system templates Users cannot delete or hide the 26 standard konteringspaket, which clutter the settings panel and every template picker. Deletion stays off the table (shared global rows); instead a company can now hide individual system templates for itself only. - New booking_template_hidden table (insert=hide, delete=unhide), RLS gated on active company + write role; nothing hidden by default - POST/DELETE /api/settings/booking-templates/[id]/hide (system templates only; company/team templates keep their real delete path) - List route decorates rows with per-company is_hidden; pickers filter them out; the settings panel shows hidden ones in a collapsed restore section so hiding is never silent - Classified in full-archive-export exclusions (UI preference, not rakenskapsinformation) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PU1KN431c9gp5zKvFaa1NL * fix(booking-templates): idempotent re-hide, system-only RLS insert, hidden filter in bulk-book Skeptic + CodeRabbit findings on #2004, one pass: - hide upsert now passes ignoreDuplicates (DO NOTHING): the table has no UPDATE policy on purpose, so the DO UPDATE conflict arm turned a concurrent re-hide into an RLS 42501/500; pg test pins the conflict shape - bth_insert policy additionally requires the referenced template to be an active system template (migration is unmerged, edited in place); negative pg test for company templates - BulkBookDialog excludes templates hidden by the company (was reading the table directly and ignoring hides) - panel shows the failure toast when the hide/unhide fetch itself rejects - picker category chips built from the hidden-filtered list Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PU1KN431c9gp5zKvFaa1NL --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
65 lines
2.8 KiB
SQL
65 lines
2.8 KiB
SQL
-- =============================================================================
|
|
-- Booking Template Hidden (per-company opt-in hiding of system templates)
|
|
-- =============================================================================
|
|
--
|
|
-- A company can hide system templates (standardmallar) it never uses so they
|
|
-- stop cluttering the settings panel and pickers. Stored separately from
|
|
-- booking_template_library because system templates are shared globally
|
|
-- (is_system = TRUE, company_id NULL): hiding must be a per-company choice,
|
|
-- never a mutation of the shared row. Nothing is hidden by default; every row
|
|
-- here is an explicit action by a write-role member of that company, and it
|
|
-- only affects that company.
|
|
--
|
|
-- One row per (template_id, company_id). Insert to hide, delete to unhide.
|
|
|
|
CREATE TABLE IF NOT EXISTS public.booking_template_hidden (
|
|
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
|
template_id UUID NOT NULL REFERENCES public.booking_template_library(id) ON DELETE CASCADE,
|
|
company_id UUID NOT NULL REFERENCES public.companies(id) ON DELETE CASCADE,
|
|
hidden_by UUID REFERENCES auth.users(id) ON DELETE SET NULL,
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
|
|
UNIQUE (template_id, company_id)
|
|
);
|
|
|
|
-- RLS
|
|
ALTER TABLE public.booking_template_hidden ENABLE ROW LEVEL SECURITY;
|
|
|
|
-- Members of a company can see which templates it hides.
|
|
DROP POLICY IF EXISTS "bth_select" ON public.booking_template_hidden;
|
|
CREATE POLICY "bth_select" ON public.booking_template_hidden
|
|
FOR SELECT USING (
|
|
company_id IN (SELECT public.user_company_ids())
|
|
);
|
|
|
|
-- Hiding/unhiding is a write action on the ACTIVE company only, gated on the
|
|
-- non-viewer role like the other settings writes (see 20260702093000). Only
|
|
-- active SYSTEM templates can be hidden: company/team templates have a real
|
|
-- delete path, so a hide row for them must not exist even via direct
|
|
-- PostgREST calls (the API route checks the same thing).
|
|
DROP POLICY IF EXISTS "bth_insert" ON public.booking_template_hidden;
|
|
CREATE POLICY "bth_insert" ON public.booking_template_hidden
|
|
FOR INSERT WITH CHECK (
|
|
company_id = current_active_company_id()
|
|
AND current_user_can_write()
|
|
AND EXISTS (
|
|
SELECT 1 FROM public.booking_template_library t
|
|
WHERE t.id = template_id AND t.is_system AND t.is_active
|
|
)
|
|
);
|
|
|
|
DROP POLICY IF EXISTS "bth_delete" ON public.booking_template_hidden;
|
|
CREATE POLICY "bth_delete" ON public.booking_template_hidden
|
|
FOR DELETE USING (
|
|
company_id = current_active_company_id() AND current_user_can_write()
|
|
);
|
|
|
|
-- No UPDATE policy on purpose: a hide row is insert-or-delete only.
|
|
|
|
-- Lookup index for the list route: all hidden template ids for a company.
|
|
CREATE INDEX IF NOT EXISTS idx_bth_company
|
|
ON public.booking_template_hidden (company_id);
|
|
|
|
-- Schema reload for PostgREST
|
|
NOTIFY pgrst, 'reload schema';
|