Files
teamvis-selfhost/supabase/migrations/0048_job_descriptions.sql
T
2026-06-25 16:43:22 +02:00

146 lines
7.7 KiB
SQL

-- =====================================================================
-- 0048 — Modul „Stellenbeschreibungen"
-- =====================================================================
-- KI- + textbaustein-gestützte, compliance-konforme Stellenbeschreibungen.
-- Hängt an den bestehenden Stellen (positions) des Organigramm-Moduls;
-- der compliance-relevante Teil wird datengetrieben aus
-- compliance_role_bindings + den Framework-YAMLs gespeist (nicht von der KI).
--
-- Drei Tabellen:
-- job_description_blocks — wiederverwendbare Textbausteine
-- job_descriptions — 1 Beschreibung pro Stelle (aktueller Stand)
-- job_description_versions — append-only Historie bei jeder Freigabe
--
-- Alle Tabellen sind admin-only (RLS an, KEINE anon-Policy → Default deny);
-- gelesen/geschrieben wird ausschließlich per Service-Role.
-- ── Textbausteine ────────────────────────────────────────────────────
create table if not exists public.job_description_blocks (
id uuid primary key default gen_random_uuid(),
slug text not null unique,
category text not null default 'allgemein',
title text not null,
body text not null default '',
-- Steuert die automatische Vorauswahl im Editor.
is_default boolean not null default false,
-- Herkunft für aus Compliance-Frameworks abgeleitete Bausteine
-- (rein informativ; der verbindliche Compliance-Abschnitt kommt live
-- aus den Bindings, nicht aus diesen Bausteinen).
source_framework_id text,
source_role_id text,
sort_order integer not null default 0,
created_at timestamptz not null default now(),
updated_at timestamptz not null default now()
);
create index if not exists job_description_blocks_category_idx
on public.job_description_blocks (category, sort_order);
-- ── Stellenbeschreibungen (1 pro Stelle) ─────────────────────────────
create table if not exists public.job_descriptions (
id uuid primary key default gen_random_uuid(),
position_id uuid not null unique
references public.positions(id) on delete cascade,
title text not null default '',
-- Strukturierte Abschnitte: { zweck, einordnung, aufgaben[],
-- befugnisse[], anforderungen[], vertretung, ... } als JSON.
content_json jsonb not null default '{}'::jsonb,
status text not null default 'draft'
check (status in ('draft', 'review', 'approved')),
generated_by_ai boolean not null default false,
model_used text,
version integer not null default 1,
approved_by text,
approved_at timestamptz,
created_at timestamptz not null default now(),
updated_at timestamptz not null default now()
);
create index if not exists job_descriptions_position_idx
on public.job_descriptions (position_id);
-- ── Versions-Historie (Snapshot bei „Freigeben") ─────────────────────
create table if not exists public.job_description_versions (
id uuid primary key default gen_random_uuid(),
job_description_id uuid not null
references public.job_descriptions(id) on delete cascade,
version integer not null,
title text not null default '',
content_json jsonb not null default '{}'::jsonb,
created_by text,
created_at timestamptz not null default now()
);
create index if not exists job_description_versions_jd_idx
on public.job_description_versions (job_description_id, version desc);
-- ── updated_at-Trigger (set_updated_at existiert seit 0023) ───────────
drop trigger if exists trg_job_description_blocks_updated_at
on public.job_description_blocks;
create trigger trg_job_description_blocks_updated_at
before update on public.job_description_blocks
for each row execute function public.set_updated_at();
drop trigger if exists trg_job_descriptions_updated_at
on public.job_descriptions;
create trigger trg_job_descriptions_updated_at
before update on public.job_descriptions
for each row execute function public.set_updated_at();
-- ── RLS: admin-only (Default deny, kein anon-Grant) ──────────────────
alter table public.job_description_blocks enable row level security;
alter table public.job_descriptions enable row level security;
alter table public.job_description_versions enable row level security;
-- Service-Role (umgeht RLS) braucht trotzdem Tabellen-Grants. Auf
-- Self-Host-Instanzen sind Default-Privileges nicht garantiert — daher
-- explizit. Bewusst NICHT an anon/authenticated (admin-only).
grant select, insert, update, delete on
public.job_description_blocks,
public.job_descriptions,
public.job_description_versions
to service_role;
-- ── Modul-Default: „job_descriptions" in enabled_modules aufnehmen ────
-- Bestandsinstanzen, die das Modul (noch) nicht kennen, bleiben unberührt
-- — der Operator schaltet es unter /admin/funktionen frei. Hier wird nur
-- der Spalten-Default für ganz neue Instanzen erweitert.
do $$
begin
if exists (
select 1 from information_schema.columns
where table_schema = 'public' and table_name = 'site_settings'
and column_name = 'enabled_modules'
) then
alter table public.site_settings
alter column enabled_modules
set default array['business_cards']::text[];
end if;
end $$;
-- ── Seed: generische Standard-Textbausteine ──────────────────────────
-- Out-of-the-box-Startset. Idempotent über den slug. Operatoren können
-- sie unter /admin/stellenbeschreibungen/bausteine anpassen oder löschen.
insert into public.job_description_blocks
(slug, category, title, body, is_default, sort_order)
values
('allgemeine-pflichten', 'Allgemein', 'Allgemeine Pflichten',
'Wahrnehmung der übertragenen Aufgaben mit der gebotenen Sorgfalt; Einhaltung der betrieblichen Anweisungen, Richtlinien und gesetzlichen Vorgaben; wirtschaftlicher und schonender Umgang mit Betriebsmitteln.',
true, 0),
('zusammenarbeit', 'Allgemein', 'Zusammenarbeit & Kommunikation',
'Konstruktive Zusammenarbeit mit vor- und nachgelagerten Bereichen; rechtzeitige Information der Vorgesetzten über wesentliche Vorgänge; serviceorientiertes Auftreten gegenüber Kund:innen und Bürger:innen.',
true, 1),
('arbeitssicherheit', 'Arbeitsschutz', 'Arbeitssicherheit & Gesundheitsschutz',
'Einhaltung der Arbeitsschutz- und Unfallverhütungsvorschriften (DGUV); Nutzung der vorgeschriebenen persönlichen Schutzausrüstung; unverzügliche Meldung von Gefährdungen, Beinaheunfällen und Mängeln.',
true, 2),
('datenschutz', 'Datenschutz', 'Datenschutz-Grundpflichten',
'Vertraulicher Umgang mit personenbezogenen Daten gemäß DSGVO und BDSG; Datenverarbeitung ausschließlich im Rahmen der zugewiesenen Aufgaben; Wahrung des Datengeheimnisses auch über das Beschäftigungsverhältnis hinaus.',
true, 3),
('informationssicherheit', 'Informationssicherheit', 'Informationssicherheit',
'Beachtung der Informationssicherheits-Richtlinien (ISMS nach ISO/IEC 27001); sorgsamer Umgang mit Zugangsdaten und Informationswerten; Meldung von Sicherheitsvorfällen an die zuständige Stelle.',
false, 4),
('vertretungsregelung', 'Allgemein', 'Vertretungsregelung',
'Im Verhinderungsfall wird die Stelle durch die benannte Vertretung wahrgenommen. Eine geordnete Übergabe laufender Vorgänge ist sicherzustellen.',
false, 5)
on conflict (slug) do nothing;