feat: add issue kanban/attachments/deadlines, dashboard widget DnD, and checklisten system

This commit is contained in:
Matthias Hochmeister
2026-03-28 15:19:41 +01:00
parent a1cda5be51
commit 0c2ea829aa
42 changed files with 4804 additions and 201 deletions

View File

@@ -0,0 +1,27 @@
-- Migration 066: Issue due dates + file attachments
-- Adds faellig_am column to issues and creates issue_dateien table.
-- ═══════════════════════════════════════════════════════════════════════════
-- 1. Add due date column to issues
-- ═══════════════════════════════════════════════════════════════════════════
ALTER TABLE issues ADD COLUMN IF NOT EXISTS faellig_am TIMESTAMPTZ;
CREATE INDEX IF NOT EXISTS idx_issues_faellig_am ON issues(faellig_am) WHERE faellig_am IS NOT NULL;
-- ═══════════════════════════════════════════════════════════════════════════
-- 2. Issue file attachments
-- ═══════════════════════════════════════════════════════════════════════════
CREATE TABLE IF NOT EXISTS issue_dateien (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
issue_id INT NOT NULL REFERENCES issues(id) ON DELETE CASCADE,
dateiname VARCHAR(500) NOT NULL,
dateipfad VARCHAR(1000) NOT NULL,
dateityp VARCHAR(100),
dateigroesse BIGINT,
hochgeladen_von UUID REFERENCES users(id) ON DELETE SET NULL,
hochgeladen_am TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE INDEX IF NOT EXISTS idx_issue_dateien_issue_id ON issue_dateien(issue_id);

View File

@@ -0,0 +1,46 @@
-- Migration 067: Fahrzeug-Typen (Vehicle Types)
-- Dynamic vehicle type table with many-to-many junction to fahrzeuge.
-- Seeds initial types from existing fahrzeuge.typ_schluessel values.
-- ═══════════════════════════════════════════════════════════════════════════
-- 1. Fahrzeug-Typen
-- ═══════════════════════════════════════════════════════════════════════════
CREATE TABLE IF NOT EXISTS fahrzeug_typen (
id SERIAL PRIMARY KEY,
name VARCHAR(100) NOT NULL UNIQUE,
beschreibung TEXT,
icon VARCHAR(50),
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
-- ═══════════════════════════════════════════════════════════════════════════
-- 2. Junction table (many-to-many)
-- ═══════════════════════════════════════════════════════════════════════════
CREATE TABLE IF NOT EXISTS fahrzeug_fahrzeug_typen (
fahrzeug_id UUID NOT NULL REFERENCES fahrzeuge(id) ON DELETE CASCADE,
fahrzeug_typ_id INT NOT NULL REFERENCES fahrzeug_typen(id) ON DELETE CASCADE,
PRIMARY KEY (fahrzeug_id, fahrzeug_typ_id)
);
CREATE INDEX IF NOT EXISTS idx_fft_fahrzeug_id ON fahrzeug_fahrzeug_typen(fahrzeug_id);
CREATE INDEX IF NOT EXISTS idx_fft_typ_id ON fahrzeug_fahrzeug_typen(fahrzeug_typ_id);
-- ═══════════════════════════════════════════════════════════════════════════
-- 3. Seed types from existing typ_schluessel values
-- ═══════════════════════════════════════════════════════════════════════════
INSERT INTO fahrzeug_typen (name)
SELECT DISTINCT typ_schluessel
FROM fahrzeuge
WHERE typ_schluessel IS NOT NULL AND typ_schluessel != ''
ON CONFLICT (name) DO NOTHING;
-- Populate junction table from existing assignments
INSERT INTO fahrzeug_fahrzeug_typen (fahrzeug_id, fahrzeug_typ_id)
SELECT f.id, ft.id
FROM fahrzeuge f
JOIN fahrzeug_typen ft ON ft.name = f.typ_schluessel
WHERE f.typ_schluessel IS NOT NULL AND f.typ_schluessel != ''
ON CONFLICT DO NOTHING;

View File

@@ -0,0 +1,113 @@
-- Migration 068: Checklisten (Checklist system)
-- Templates, vehicle-specific items, execution records, and due date tracking.
-- Depends on: 067_fahrzeug_typen.sql (fahrzeug_typen table)
-- ═══════════════════════════════════════════════════════════════════════════
-- 1. Checklist-Vorlagen (Templates)
-- ═══════════════════════════════════════════════════════════════════════════
CREATE TABLE IF NOT EXISTS checklist_vorlagen (
id SERIAL PRIMARY KEY,
name VARCHAR(200) NOT NULL,
fahrzeug_typ_id INT REFERENCES fahrzeug_typen(id) ON DELETE SET NULL,
intervall VARCHAR(20) CHECK (intervall IN ('weekly','monthly','yearly','custom')),
intervall_tage INT,
beschreibung TEXT,
aktiv BOOLEAN NOT NULL DEFAULT TRUE,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
-- ═══════════════════════════════════════════════════════════════════════════
-- 2. Vorlage Items (Template line items)
-- ═══════════════════════════════════════════════════════════════════════════
CREATE TABLE IF NOT EXISTS checklist_vorlage_items (
id SERIAL PRIMARY KEY,
vorlage_id INT NOT NULL REFERENCES checklist_vorlagen(id) ON DELETE CASCADE,
bezeichnung VARCHAR(500) NOT NULL,
beschreibung TEXT,
pflicht BOOLEAN NOT NULL DEFAULT TRUE,
sort_order INT NOT NULL DEFAULT 0
);
CREATE INDEX IF NOT EXISTS idx_cvi_vorlage_id ON checklist_vorlage_items(vorlage_id);
-- ═══════════════════════════════════════════════════════════════════════════
-- 3. Fahrzeug-spezifische Checklist Items
-- ═══════════════════════════════════════════════════════════════════════════
CREATE TABLE IF NOT EXISTS fahrzeug_checklist_items (
id SERIAL PRIMARY KEY,
fahrzeug_id UUID NOT NULL REFERENCES fahrzeuge(id) ON DELETE CASCADE,
bezeichnung VARCHAR(500) NOT NULL,
beschreibung TEXT,
pflicht BOOLEAN NOT NULL DEFAULT TRUE,
sort_order INT NOT NULL DEFAULT 0,
aktiv BOOLEAN NOT NULL DEFAULT TRUE
);
CREATE INDEX IF NOT EXISTS idx_fci_fahrzeug_id ON fahrzeug_checklist_items(fahrzeug_id);
-- ═══════════════════════════════════════════════════════════════════════════
-- 4. Checklist-Ausführungen (Execution records)
-- ═══════════════════════════════════════════════════════════════════════════
CREATE TABLE IF NOT EXISTS checklist_ausfuehrungen (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
fahrzeug_id UUID NOT NULL REFERENCES fahrzeuge(id) ON DELETE CASCADE,
vorlage_id INT REFERENCES checklist_vorlagen(id) ON DELETE SET NULL,
status VARCHAR(30) NOT NULL DEFAULT 'offen'
CHECK (status IN ('offen','abgeschlossen','unvollstaendig','freigegeben')),
ausgefuehrt_von UUID REFERENCES users(id) ON DELETE SET NULL,
ausgefuehrt_am TIMESTAMPTZ,
freigegeben_von UUID REFERENCES users(id) ON DELETE SET NULL,
freigegeben_am TIMESTAMPTZ,
notizen TEXT,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE INDEX IF NOT EXISTS idx_ca_fahrzeug_id ON checklist_ausfuehrungen(fahrzeug_id);
CREATE INDEX IF NOT EXISTS idx_ca_vorlage_id ON checklist_ausfuehrungen(vorlage_id);
CREATE INDEX IF NOT EXISTS idx_ca_status ON checklist_ausfuehrungen(status);
-- ═══════════════════════════════════════════════════════════════════════════
-- 5. Ausführung Items (Execution line items / answers)
-- ═══════════════════════════════════════════════════════════════════════════
CREATE TABLE IF NOT EXISTS checklist_ausfuehrung_items (
id SERIAL PRIMARY KEY,
ausfuehrung_id UUID NOT NULL REFERENCES checklist_ausfuehrungen(id) ON DELETE CASCADE,
vorlage_item_id INT REFERENCES checklist_vorlage_items(id) ON DELETE SET NULL,
fahrzeug_item_id INT REFERENCES fahrzeug_checklist_items(id) ON DELETE SET NULL,
bezeichnung VARCHAR(500),
ergebnis VARCHAR(20) CHECK (ergebnis IN ('ok','nok','na')),
kommentar TEXT,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE INDEX IF NOT EXISTS idx_cai_ausfuehrung_id ON checklist_ausfuehrung_items(ausfuehrung_id);
-- ═══════════════════════════════════════════════════════════════════════════
-- 6. Fälligkeiten (Due date tracking per vehicle+template)
-- ═══════════════════════════════════════════════════════════════════════════
CREATE TABLE IF NOT EXISTS checklist_faelligkeit (
fahrzeug_id UUID NOT NULL REFERENCES fahrzeuge(id) ON DELETE CASCADE,
vorlage_id INT NOT NULL REFERENCES checklist_vorlagen(id) ON DELETE CASCADE,
naechste_faellig_am DATE NOT NULL,
letzte_ausfuehrung_id UUID REFERENCES checklist_ausfuehrungen(id) ON DELETE SET NULL,
PRIMARY KEY (fahrzeug_id, vorlage_id)
);
-- ═══════════════════════════════════════════════════════════════════════════
-- 7. Auto-update updated_at trigger for checklist_vorlagen
-- ═══════════════════════════════════════════════════════════════════════════
DO $$
BEGIN
IF NOT EXISTS (SELECT 1 FROM pg_trigger WHERE tgname = 'trg_checklist_vorlagen_updated') THEN
CREATE TRIGGER trg_checklist_vorlagen_updated BEFORE UPDATE ON checklist_vorlagen
FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
END IF;
END $$;

View File

@@ -0,0 +1,78 @@
-- Migration 069: Checklisten permissions
-- Adds checklisten feature group and permissions, seeds group_permissions.
-- ═══════════════════════════════════════════════════════════════════════════
-- 1. Feature group
-- ═══════════════════════════════════════════════════════════════════════════
INSERT INTO feature_groups (id, label, sort_order) VALUES
('checklisten', 'Checklisten', 15)
ON CONFLICT (id) DO NOTHING;
-- ═══════════════════════════════════════════════════════════════════════════
-- 2. Permissions
-- ═══════════════════════════════════════════════════════════════════════════
INSERT INTO permissions (id, feature_group_id, label, description, sort_order) VALUES
('checklisten:view', 'checklisten', 'Ansehen', 'Checklisten und Ausführungen einsehen', 1),
('checklisten:execute', 'checklisten', 'Ausfüllen', 'Checklisten ausfüllen und abschließen', 2),
('checklisten:approve', 'checklisten', 'Freigeben', 'Checklisten nach Prüfung freigeben', 3),
('checklisten:manage_templates', 'checklisten', 'Vorlagen verwalten', 'Vorlagen und Fahrzeugtypen erstellen und bearbeiten', 4),
('checklisten:widget', 'checklisten', 'Widget', 'Checklisten-Widget im Dashboard anzeigen', 5)
ON CONFLICT (id) DO NOTHING;
-- ═══════════════════════════════════════════════════════════════════════════
-- 3. Seed group permissions
-- ═══════════════════════════════════════════════════════════════════════════
-- dashboard_admin has hardwired full access (not seeded).
-- Kommando: all permissions
INSERT INTO group_permissions (authentik_group, permission_id) VALUES
('dashboard_kommando', 'checklisten:view'),
('dashboard_kommando', 'checklisten:execute'),
('dashboard_kommando', 'checklisten:approve'),
('dashboard_kommando', 'checklisten:manage_templates'),
('dashboard_kommando', 'checklisten:widget')
ON CONFLICT DO NOTHING;
-- Fahrmeister: view, execute, approve, widget (vehicle specialist)
INSERT INTO group_permissions (authentik_group, permission_id) VALUES
('dashboard_fahrmeister', 'checklisten:view'),
('dashboard_fahrmeister', 'checklisten:execute'),
('dashboard_fahrmeister', 'checklisten:approve'),
('dashboard_fahrmeister', 'checklisten:widget')
ON CONFLICT DO NOTHING;
-- Zeugmeister: view, execute, approve, widget (equipment specialist)
INSERT INTO group_permissions (authentik_group, permission_id) VALUES
('dashboard_zeugmeister', 'checklisten:view'),
('dashboard_zeugmeister', 'checklisten:execute'),
('dashboard_zeugmeister', 'checklisten:approve'),
('dashboard_zeugmeister', 'checklisten:widget')
ON CONFLICT DO NOTHING;
-- Chargen: view, execute, widget
INSERT INTO group_permissions (authentik_group, permission_id) VALUES
('dashboard_chargen', 'checklisten:view'),
('dashboard_chargen', 'checklisten:execute'),
('dashboard_chargen', 'checklisten:widget')
ON CONFLICT DO NOTHING;
-- Moderator: view, widget
INSERT INTO group_permissions (authentik_group, permission_id) VALUES
('dashboard_moderator', 'checklisten:view'),
('dashboard_moderator', 'checklisten:widget')
ON CONFLICT DO NOTHING;
-- Atemschutz: view, execute, widget
INSERT INTO group_permissions (authentik_group, permission_id) VALUES
('dashboard_atemschutz', 'checklisten:view'),
('dashboard_atemschutz', 'checklisten:execute'),
('dashboard_atemschutz', 'checklisten:widget')
ON CONFLICT DO NOTHING;
-- Mitglied: view, widget
INSERT INTO group_permissions (authentik_group, permission_id) VALUES
('dashboard_mitglied', 'checklisten:view'),
('dashboard_mitglied', 'checklisten:widget')
ON CONFLICT DO NOTHING;