Files
myeasycms-v2/apps/web/supabase/migrations/20260413000001_sitzungsprotokolle.sql

223 lines
8.4 KiB
SQL

/*
* -------------------------------------------------------
* Sitzungsprotokolle (Meeting Protocols) Schema
* Meeting minutes, agenda items, tasks, attachments
* -------------------------------------------------------
*/
-- =====================================================
-- 1. Extend app_permissions
-- (Moved to 20260411900001_fischerei_enum_values.sql)
-- =====================================================
-- =====================================================
-- 2. Enums
-- =====================================================
CREATE TYPE public.meeting_item_status AS ENUM(
'offen',
'in_bearbeitung',
'erledigt',
'vertagt'
);
-- =====================================================
-- 3. meeting_protocols
-- =====================================================
CREATE TABLE IF NOT EXISTS public.meeting_protocols (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
account_id uuid NOT NULL REFERENCES public.accounts(id) ON DELETE CASCADE,
-- Identity
title text NOT NULL,
protocol_number text,
-- Schedule
meeting_date date NOT NULL,
meeting_time time,
end_time time,
location text,
-- Participants
chair text,
recorder text,
attendees jsonb NOT NULL DEFAULT '[]'::jsonb,
absent jsonb NOT NULL DEFAULT '[]'::jsonb,
-- Content
summary text,
-- Status workflow
status text NOT NULL DEFAULT 'draft'
CHECK (status IN ('draft', 'review', 'approved', 'archived')),
-- Follow-up
next_meeting_date date,
-- Flags
is_archived boolean NOT NULL DEFAULT false,
-- Meta
created_by uuid REFERENCES auth.users(id) ON DELETE SET NULL,
updated_by uuid REFERENCES auth.users(id) ON DELETE SET NULL,
created_at timestamptz NOT NULL DEFAULT now(),
updated_at timestamptz NOT NULL DEFAULT now()
);
CREATE INDEX ix_meeting_protocols_account ON public.meeting_protocols(account_id);
CREATE INDEX ix_meeting_protocols_date ON public.meeting_protocols(account_id, meeting_date DESC);
CREATE INDEX ix_meeting_protocols_status ON public.meeting_protocols(account_id, status);
CREATE INDEX ix_meeting_protocols_archived ON public.meeting_protocols(account_id, is_archived);
ALTER TABLE public.meeting_protocols ENABLE ROW LEVEL SECURITY;
REVOKE ALL ON public.meeting_protocols FROM authenticated, service_role;
GRANT SELECT, INSERT, UPDATE, DELETE ON public.meeting_protocols TO authenticated;
GRANT ALL ON public.meeting_protocols TO service_role;
CREATE POLICY meeting_protocols_select ON public.meeting_protocols FOR SELECT TO authenticated
USING (public.has_role_on_account(account_id));
CREATE POLICY meeting_protocols_insert ON public.meeting_protocols FOR INSERT TO authenticated
WITH CHECK (public.has_permission(auth.uid(), account_id, 'meetings.write'::public.app_permissions));
CREATE POLICY meeting_protocols_update ON public.meeting_protocols FOR UPDATE TO authenticated
USING (public.has_permission(auth.uid(), account_id, 'meetings.write'::public.app_permissions));
CREATE POLICY meeting_protocols_delete ON public.meeting_protocols FOR DELETE TO authenticated
USING (public.has_permission(auth.uid(), account_id, 'meetings.write'::public.app_permissions));
CREATE TRIGGER trg_meeting_protocols_updated_at
BEFORE UPDATE ON public.meeting_protocols
FOR EACH ROW EXECUTE FUNCTION public.update_account_settings_timestamp();
-- =====================================================
-- 4. meeting_protocol_items (agenda items / TOPs)
-- =====================================================
CREATE TABLE IF NOT EXISTS public.meeting_protocol_items (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
protocol_id uuid NOT NULL REFERENCES public.meeting_protocols(id) ON DELETE CASCADE,
-- Ordering
item_number integer NOT NULL DEFAULT 0,
sort_order integer NOT NULL DEFAULT 0,
-- Content
title text NOT NULL,
content text,
-- Classification
item_type text NOT NULL DEFAULT 'information'
CHECK (item_type IN ('information', 'discussion', 'decision', 'task')),
-- Decision tracking
decision_text text,
-- Task tracking
status public.meeting_item_status NOT NULL DEFAULT 'offen',
responsible_person text,
due_date date,
-- Meta
created_at timestamptz NOT NULL DEFAULT now(),
updated_at timestamptz NOT NULL DEFAULT now()
);
CREATE INDEX ix_meeting_protocol_items_protocol ON public.meeting_protocol_items(protocol_id);
CREATE INDEX ix_meeting_protocol_items_status ON public.meeting_protocol_items(status);
CREATE INDEX ix_meeting_protocol_items_type ON public.meeting_protocol_items(item_type);
CREATE INDEX ix_meeting_protocol_items_due ON public.meeting_protocol_items(due_date)
WHERE due_date IS NOT NULL AND status != 'erledigt';
ALTER TABLE public.meeting_protocol_items ENABLE ROW LEVEL SECURITY;
REVOKE ALL ON public.meeting_protocol_items FROM authenticated, service_role;
GRANT SELECT, INSERT, UPDATE, DELETE ON public.meeting_protocol_items TO authenticated;
GRANT ALL ON public.meeting_protocol_items TO service_role;
CREATE POLICY meeting_protocol_items_select ON public.meeting_protocol_items FOR SELECT TO authenticated
USING (EXISTS (
SELECT 1 FROM public.meeting_protocols mp
WHERE mp.id = meeting_protocol_items.protocol_id
AND public.has_role_on_account(mp.account_id)
));
CREATE POLICY meeting_protocol_items_mutate ON public.meeting_protocol_items FOR ALL TO authenticated
USING (EXISTS (
SELECT 1 FROM public.meeting_protocols mp
WHERE mp.id = meeting_protocol_items.protocol_id
AND public.has_permission(auth.uid(), mp.account_id, 'meetings.write'::public.app_permissions)
));
CREATE TRIGGER trg_meeting_protocol_items_updated_at
BEFORE UPDATE ON public.meeting_protocol_items
FOR EACH ROW EXECUTE FUNCTION public.update_account_settings_timestamp();
-- =====================================================
-- 5. meeting_protocol_attachments
-- =====================================================
CREATE TABLE IF NOT EXISTS public.meeting_protocol_attachments (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
protocol_id uuid NOT NULL REFERENCES public.meeting_protocols(id) ON DELETE CASCADE,
item_id uuid REFERENCES public.meeting_protocol_items(id) ON DELETE SET NULL,
-- File info
file_name text NOT NULL,
file_path text NOT NULL,
file_size bigint,
content_type text,
-- Meta
created_by uuid REFERENCES auth.users(id) ON DELETE SET NULL,
created_at timestamptz NOT NULL DEFAULT now()
);
CREATE INDEX ix_meeting_protocol_attachments_protocol ON public.meeting_protocol_attachments(protocol_id);
CREATE INDEX ix_meeting_protocol_attachments_item ON public.meeting_protocol_attachments(item_id)
WHERE item_id IS NOT NULL;
ALTER TABLE public.meeting_protocol_attachments ENABLE ROW LEVEL SECURITY;
REVOKE ALL ON public.meeting_protocol_attachments FROM authenticated, service_role;
GRANT SELECT, INSERT, UPDATE, DELETE ON public.meeting_protocol_attachments TO authenticated;
GRANT ALL ON public.meeting_protocol_attachments TO service_role;
CREATE POLICY meeting_protocol_attachments_select ON public.meeting_protocol_attachments FOR SELECT TO authenticated
USING (EXISTS (
SELECT 1 FROM public.meeting_protocols mp
WHERE mp.id = meeting_protocol_attachments.protocol_id
AND public.has_role_on_account(mp.account_id)
));
CREATE POLICY meeting_protocol_attachments_mutate ON public.meeting_protocol_attachments FOR ALL TO authenticated
USING (EXISTS (
SELECT 1 FROM public.meeting_protocols mp
WHERE mp.id = meeting_protocol_attachments.protocol_id
AND public.has_permission(auth.uid(), mp.account_id, 'meetings.write'::public.app_permissions)
));
-- =====================================================
-- 6. View: open_meeting_tasks
-- =====================================================
CREATE OR REPLACE VIEW public.open_meeting_tasks AS
SELECT
mpi.id AS item_id,
mpi.protocol_id,
mp.account_id,
mp.title AS protocol_title,
mp.meeting_date,
mpi.item_number,
mpi.title AS task_title,
mpi.content AS task_description,
mpi.responsible_person,
mpi.due_date,
mpi.status,
CASE
WHEN mpi.due_date < current_date AND mpi.status != 'erledigt' THEN true
ELSE false
END AS is_overdue
FROM public.meeting_protocol_items mpi
JOIN public.meeting_protocols mp ON mp.id = mpi.protocol_id
WHERE mpi.item_type = 'task'
AND mpi.status != 'erledigt';
-- Grant access to the view (RLS on underlying tables still applies)
GRANT SELECT ON public.open_meeting_tasks TO authenticated;
GRANT SELECT ON public.open_meeting_tasks TO service_role;