TeamVis Self-Host-Bundle v0.31.0
This commit is contained in:
@@ -0,0 +1,145 @@
|
||||
-- =====================================================================
|
||||
-- 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;
|
||||
Reference in New Issue
Block a user