/* * ------------------------------------------------------- * 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;