283 lines
8.9 KiB
PL/PgSQL
283 lines
8.9 KiB
PL/PgSQL
/*
|
|
* -------------------------------------------------------
|
|
* Shared Templates Across Hierarchy
|
|
*
|
|
* 1. Add shared_with_hierarchy flag to newsletter_templates
|
|
* 2. Create document_templates table with hierarchy sharing
|
|
* 3. Template cloning function for child orgs
|
|
* 4. Hierarchy-aware RLS for template reads
|
|
* -------------------------------------------------------
|
|
*/
|
|
|
|
-- =====================================================
|
|
-- 1. Newsletter Templates: add sharing flag
|
|
-- =====================================================
|
|
|
|
ALTER TABLE public.newsletter_templates
|
|
ADD COLUMN IF NOT EXISTS shared_with_hierarchy boolean NOT NULL DEFAULT false;
|
|
|
|
-- Allow child orgs to read parent's shared templates
|
|
DO $$
|
|
BEGIN
|
|
IF NOT EXISTS (
|
|
SELECT 1 FROM pg_policies
|
|
WHERE schemaname = 'public' AND tablename = 'newsletter_templates'
|
|
AND policyname = 'newsletter_templates_hierarchy_read'
|
|
) THEN
|
|
EXECUTE 'CREATE POLICY newsletter_templates_hierarchy_read
|
|
ON public.newsletter_templates
|
|
FOR SELECT TO authenticated
|
|
USING (
|
|
shared_with_hierarchy = true
|
|
AND public.has_role_on_account_or_ancestor(account_id)
|
|
)';
|
|
END IF;
|
|
END $$;
|
|
|
|
-- =====================================================
|
|
-- 2. Document Templates table
|
|
-- =====================================================
|
|
|
|
CREATE TABLE IF NOT EXISTS public.document_templates (
|
|
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
account_id uuid NOT NULL REFERENCES public.accounts(id) ON DELETE CASCADE,
|
|
name text NOT NULL,
|
|
description text,
|
|
template_type text NOT NULL DEFAULT 'generic'
|
|
CHECK (template_type IN (
|
|
'generic', 'member_card', 'invoice', 'receipt',
|
|
'certificate', 'letter', 'label', 'report'
|
|
)),
|
|
-- Template content: HTML with variable placeholders like {{member.first_name}}
|
|
body_html text NOT NULL DEFAULT '',
|
|
-- Available variables for this template
|
|
variables jsonb NOT NULL DEFAULT '[]'::jsonb,
|
|
-- Page settings
|
|
page_format text NOT NULL DEFAULT 'A4'
|
|
CHECK (page_format IN ('A4', 'A5', 'A6', 'letter', 'label')),
|
|
orientation text NOT NULL DEFAULT 'portrait'
|
|
CHECK (orientation IN ('portrait', 'landscape')),
|
|
-- Hierarchy sharing
|
|
shared_with_hierarchy boolean NOT NULL DEFAULT false,
|
|
-- Meta
|
|
is_default boolean NOT NULL DEFAULT false,
|
|
sort_order integer NOT NULL DEFAULT 0,
|
|
created_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 IF NOT EXISTS ix_document_templates_account
|
|
ON public.document_templates(account_id);
|
|
|
|
CREATE INDEX IF NOT EXISTS ix_document_templates_type
|
|
ON public.document_templates(account_id, template_type);
|
|
|
|
ALTER TABLE public.document_templates ENABLE ROW LEVEL SECURITY;
|
|
|
|
REVOKE ALL ON public.document_templates FROM authenticated, service_role;
|
|
GRANT SELECT, INSERT, UPDATE, DELETE ON public.document_templates TO authenticated;
|
|
GRANT ALL ON public.document_templates TO service_role;
|
|
|
|
-- Own account read/write
|
|
CREATE POLICY document_templates_select ON public.document_templates
|
|
FOR SELECT TO authenticated
|
|
USING (public.has_role_on_account(account_id));
|
|
|
|
CREATE POLICY document_templates_mutate ON public.document_templates
|
|
FOR ALL TO authenticated
|
|
USING (public.has_permission(auth.uid(), account_id, 'documents.generate'::public.app_permissions));
|
|
|
|
-- Hierarchy: child orgs can read parent's shared templates
|
|
DO $$
|
|
BEGIN
|
|
IF NOT EXISTS (
|
|
SELECT 1 FROM pg_policies
|
|
WHERE schemaname = 'public' AND tablename = 'document_templates'
|
|
AND policyname = 'document_templates_hierarchy_read'
|
|
) THEN
|
|
EXECUTE 'CREATE POLICY document_templates_hierarchy_read
|
|
ON public.document_templates
|
|
FOR SELECT TO authenticated
|
|
USING (
|
|
shared_with_hierarchy = true
|
|
AND public.has_role_on_account_or_ancestor(account_id)
|
|
)';
|
|
END IF;
|
|
END $$;
|
|
|
|
CREATE TRIGGER trg_document_templates_updated_at
|
|
BEFORE UPDATE ON public.document_templates
|
|
FOR EACH ROW EXECUTE FUNCTION public.update_account_settings_timestamp();
|
|
|
|
-- =====================================================
|
|
-- 3. Template cloning function
|
|
-- =====================================================
|
|
|
|
-- Clone a shared template into a child org's own templates
|
|
CREATE OR REPLACE FUNCTION public.clone_template(
|
|
p_template_type text, -- 'newsletter' or 'document'
|
|
p_template_id uuid,
|
|
p_target_account_id uuid,
|
|
p_new_name text DEFAULT NULL
|
|
)
|
|
RETURNS uuid
|
|
LANGUAGE plpgsql
|
|
SECURITY DEFINER
|
|
SET search_path = ''
|
|
AS $$
|
|
DECLARE
|
|
v_new_id uuid;
|
|
v_caller_id uuid;
|
|
v_source_account_id uuid;
|
|
BEGIN
|
|
v_caller_id := (SELECT auth.uid());
|
|
|
|
-- Verify caller has a role on the target account
|
|
IF NOT public.has_role_on_account(p_target_account_id) THEN
|
|
RAISE EXCEPTION 'Keine Berechtigung für die Zielorganisation';
|
|
END IF;
|
|
|
|
IF p_template_type = 'newsletter' THEN
|
|
-- Get source account for verification
|
|
SELECT account_id INTO v_source_account_id
|
|
FROM public.newsletter_templates WHERE id = p_template_id;
|
|
|
|
IF v_source_account_id IS NULL THEN
|
|
RAISE EXCEPTION 'Vorlage nicht gefunden';
|
|
END IF;
|
|
|
|
-- Must be shared or from own account
|
|
IF v_source_account_id != p_target_account_id THEN
|
|
IF NOT EXISTS (
|
|
SELECT 1 FROM public.newsletter_templates
|
|
WHERE id = p_template_id AND shared_with_hierarchy = true
|
|
) THEN
|
|
RAISE EXCEPTION 'Vorlage ist nicht für die Hierarchie freigegeben';
|
|
END IF;
|
|
END IF;
|
|
|
|
INSERT INTO public.newsletter_templates (
|
|
account_id, name, subject, body_html, body_text, variables
|
|
)
|
|
SELECT
|
|
p_target_account_id,
|
|
COALESCE(p_new_name, name || ' (Kopie)'),
|
|
subject, body_html, body_text, variables
|
|
FROM public.newsletter_templates
|
|
WHERE id = p_template_id
|
|
RETURNING id INTO v_new_id;
|
|
|
|
ELSIF p_template_type = 'document' THEN
|
|
SELECT account_id INTO v_source_account_id
|
|
FROM public.document_templates WHERE id = p_template_id;
|
|
|
|
IF v_source_account_id IS NULL THEN
|
|
RAISE EXCEPTION 'Vorlage nicht gefunden';
|
|
END IF;
|
|
|
|
IF v_source_account_id != p_target_account_id THEN
|
|
IF NOT EXISTS (
|
|
SELECT 1 FROM public.document_templates
|
|
WHERE id = p_template_id AND shared_with_hierarchy = true
|
|
) THEN
|
|
RAISE EXCEPTION 'Vorlage ist nicht für die Hierarchie freigegeben';
|
|
END IF;
|
|
END IF;
|
|
|
|
INSERT INTO public.document_templates (
|
|
account_id, name, description, template_type,
|
|
body_html, variables, page_format, orientation, created_by
|
|
)
|
|
SELECT
|
|
p_target_account_id,
|
|
COALESCE(p_new_name, name || ' (Kopie)'),
|
|
description, template_type,
|
|
body_html, variables, page_format, orientation, v_caller_id
|
|
FROM public.document_templates
|
|
WHERE id = p_template_id
|
|
RETURNING id INTO v_new_id;
|
|
|
|
ELSE
|
|
RAISE EXCEPTION 'Ungültiger Vorlagentyp: %', p_template_type;
|
|
END IF;
|
|
|
|
RETURN v_new_id;
|
|
END;
|
|
$$;
|
|
|
|
GRANT EXECUTE ON FUNCTION public.clone_template(text, uuid, uuid, text)
|
|
TO authenticated, service_role;
|
|
|
|
-- =====================================================
|
|
-- 4. List shared templates across hierarchy
|
|
-- =====================================================
|
|
|
|
CREATE OR REPLACE FUNCTION public.list_hierarchy_shared_templates(
|
|
root_account_id uuid,
|
|
p_template_type text DEFAULT NULL -- 'newsletter', 'document', or NULL for both
|
|
)
|
|
RETURNS TABLE (
|
|
id uuid,
|
|
account_id uuid,
|
|
account_name varchar,
|
|
template_source text, -- 'newsletter' or 'document'
|
|
name text,
|
|
description text,
|
|
template_type text,
|
|
shared_with_hierarchy boolean,
|
|
created_at timestamptz
|
|
)
|
|
LANGUAGE plpgsql
|
|
SECURITY DEFINER
|
|
SET search_path = ''
|
|
AS $$
|
|
BEGIN
|
|
IF NOT public.has_role_on_account(root_account_id) THEN
|
|
RETURN;
|
|
END IF;
|
|
|
|
RETURN QUERY
|
|
-- Newsletter templates
|
|
SELECT
|
|
nt.id,
|
|
nt.account_id,
|
|
a.name AS account_name,
|
|
'newsletter'::text AS template_source,
|
|
nt.name,
|
|
NULL::text AS description,
|
|
'newsletter'::text AS template_type,
|
|
nt.shared_with_hierarchy,
|
|
nt.created_at
|
|
FROM public.newsletter_templates nt
|
|
JOIN public.accounts a ON a.id = nt.account_id
|
|
WHERE nt.account_id IN (SELECT d FROM public.get_account_descendants(root_account_id) d)
|
|
AND nt.shared_with_hierarchy = true
|
|
AND (p_template_type IS NULL OR p_template_type = 'newsletter')
|
|
|
|
UNION ALL
|
|
|
|
-- Document templates
|
|
SELECT
|
|
dt.id,
|
|
dt.account_id,
|
|
a.name AS account_name,
|
|
'document'::text AS template_source,
|
|
dt.name,
|
|
dt.description,
|
|
dt.template_type,
|
|
dt.shared_with_hierarchy,
|
|
dt.created_at
|
|
FROM public.document_templates dt
|
|
JOIN public.accounts a ON a.id = dt.account_id
|
|
WHERE dt.account_id IN (SELECT d FROM public.get_account_descendants(root_account_id) d)
|
|
AND dt.shared_with_hierarchy = true
|
|
AND (p_template_type IS NULL OR p_template_type = 'document')
|
|
|
|
ORDER BY created_at DESC;
|
|
END;
|
|
$$;
|
|
|
|
GRANT EXECUTE ON FUNCTION public.list_hierarchy_shared_templates(uuid, text)
|
|
TO authenticated, service_role;
|