# SeCu - Datenbank-Schema ## ER-Diagramm ```mermaid erDiagram organizations ||--o{ users : has users ||--o{ users : manages users ||--o{ orders : creates users ||--o{ orders : assigned_to users ||--o{ availability : reports users ||--o{ timesheets : submits orders ||--o{ timesheets : has orders ||--o{ order_assignments : has users ||--o{ order_assignments : assigned modules ||--o{ organization_modules : configured_in organizations ||--o{ organization_modules : has organizations { uuid id PK varchar name varchar slug UK jsonb settings timestamp created_at timestamp updated_at } users { uuid id PK uuid org_id FK varchar email UK varchar password_hash varchar role "chef|disponent|mitarbeiter" varchar first_name varchar last_name varchar phone varchar avatar_url uuid created_by FK uuid managed_by FK "Disponent der den MA verwaltet" boolean active timestamp last_login timestamp created_at timestamp updated_at } orders { uuid id PK uuid org_id FK integer number "Auto-increment pro Org" varchar title text description varchar location varchar address varchar client_name varchar client_contact varchar status "draft|published|in_progress|completed|cancelled" timestamp start_time timestamp end_time integer required_staff text special_instructions uuid created_by FK timestamp created_at timestamp updated_at } order_assignments { uuid id PK uuid order_id FK uuid user_id FK varchar status "pending|confirmed|declined|completed" text note timestamp confirmed_at timestamp created_at } availability { uuid id PK uuid user_id FK date date boolean available varchar time_from "Optional: 08:00" varchar time_to "Optional: 18:00" text note timestamp created_at timestamp updated_at } timesheets { uuid id PK uuid user_id FK uuid order_id FK date work_date time start_time time end_time decimal hours_worked varchar photo_url varchar status "pending|approved|rejected" uuid approved_by FK text rejection_reason timestamp approved_at timestamp created_at timestamp updated_at } modules { uuid id PK varchar name UK varchar display_name text description boolean is_core "Core modules can't be disabled" jsonb default_config timestamp created_at } organization_modules { uuid id PK uuid org_id FK uuid module_id FK boolean enabled jsonb config timestamp enabled_at timestamp updated_at } audit_logs { uuid id PK uuid org_id FK uuid user_id FK varchar action varchar entity_type uuid entity_id jsonb old_values jsonb new_values varchar ip_address timestamp created_at } refresh_tokens { uuid id PK uuid user_id FK varchar token_hash UK timestamp expires_at timestamp created_at } ``` ## SQL Migrations ### 001_initial_schema.sql ```sql -- Enable UUID extension CREATE EXTENSION IF NOT EXISTS "uuid-ossp"; -- ============================================ -- ORGANIZATIONS (Multi-Tenancy) -- ============================================ CREATE TABLE organizations ( id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), name VARCHAR(255) NOT NULL, slug VARCHAR(100) UNIQUE NOT NULL, settings JSONB DEFAULT '{}', created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW() ); CREATE INDEX idx_organizations_slug ON organizations(slug); -- ============================================ -- USERS -- ============================================ CREATE TYPE user_role AS ENUM ('chef', 'disponent', 'mitarbeiter'); CREATE TABLE users ( id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), org_id UUID NOT NULL REFERENCES organizations(id) ON DELETE CASCADE, email VARCHAR(255) NOT NULL, password_hash VARCHAR(255) NOT NULL, role user_role NOT NULL DEFAULT 'mitarbeiter', first_name VARCHAR(100) NOT NULL, last_name VARCHAR(100) NOT NULL, phone VARCHAR(50), avatar_url VARCHAR(500), created_by UUID REFERENCES users(id) ON DELETE SET NULL, managed_by UUID REFERENCES users(id) ON DELETE SET NULL, active BOOLEAN DEFAULT true, last_login TIMESTAMP WITH TIME ZONE, created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), UNIQUE(org_id, email) ); CREATE INDEX idx_users_org ON users(org_id); CREATE INDEX idx_users_role ON users(org_id, role); CREATE INDEX idx_users_managed_by ON users(managed_by); -- ============================================ -- ORDERS (Aufträge) -- ============================================ CREATE TYPE order_status AS ENUM ('draft', 'published', 'in_progress', 'completed', 'cancelled'); CREATE TABLE orders ( id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), org_id UUID NOT NULL REFERENCES organizations(id) ON DELETE CASCADE, number SERIAL, title VARCHAR(255) NOT NULL, description TEXT, location VARCHAR(255), address TEXT, client_name VARCHAR(255), client_contact VARCHAR(255), status order_status DEFAULT 'draft', start_time TIMESTAMP WITH TIME ZONE, end_time TIMESTAMP WITH TIME ZONE, required_staff INTEGER DEFAULT 1, special_instructions TEXT, created_by UUID NOT NULL REFERENCES users(id) ON DELETE RESTRICT, created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW() ); CREATE INDEX idx_orders_org ON orders(org_id); CREATE INDEX idx_orders_status ON orders(org_id, status); CREATE INDEX idx_orders_dates ON orders(org_id, start_time, end_time); -- ============================================ -- ORDER ASSIGNMENTS (Zuweisungen) -- ============================================ CREATE TYPE assignment_status AS ENUM ('pending', 'confirmed', 'declined', 'completed'); CREATE TABLE order_assignments ( id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), order_id UUID NOT NULL REFERENCES orders(id) ON DELETE CASCADE, user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE, status assignment_status DEFAULT 'pending', note TEXT, confirmed_at TIMESTAMP WITH TIME ZONE, created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), UNIQUE(order_id, user_id) ); CREATE INDEX idx_assignments_order ON order_assignments(order_id); CREATE INDEX idx_assignments_user ON order_assignments(user_id); -- ============================================ -- AVAILABILITY (Verfügbarkeit) -- ============================================ CREATE TABLE availability ( id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE, date DATE NOT NULL, available BOOLEAN NOT NULL DEFAULT true, time_from TIME, time_to TIME, note TEXT, created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), UNIQUE(user_id, date) ); CREATE INDEX idx_availability_user_date ON availability(user_id, date); -- ============================================ -- TIMESHEETS (Stundenzettel) -- ============================================ CREATE TYPE timesheet_status AS ENUM ('pending', 'approved', 'rejected'); CREATE TABLE timesheets ( id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE, order_id UUID REFERENCES orders(id) ON DELETE SET NULL, work_date DATE NOT NULL, start_time TIME, end_time TIME, hours_worked DECIMAL(5,2), photo_url VARCHAR(500), status timesheet_status DEFAULT 'pending', approved_by UUID REFERENCES users(id) ON DELETE SET NULL, rejection_reason TEXT, approved_at TIMESTAMP WITH TIME ZONE, created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW() ); CREATE INDEX idx_timesheets_user ON timesheets(user_id); CREATE INDEX idx_timesheets_order ON timesheets(order_id); CREATE INDEX idx_timesheets_status ON timesheets(status); CREATE INDEX idx_timesheets_date ON timesheets(work_date); -- ============================================ -- MODULES (Modularer Aufbau) -- ============================================ CREATE TABLE modules ( id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), name VARCHAR(100) UNIQUE NOT NULL, display_name VARCHAR(255) NOT NULL, description TEXT, is_core BOOLEAN DEFAULT false, default_config JSONB DEFAULT '{}', created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW() ); CREATE TABLE organization_modules ( id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), org_id UUID NOT NULL REFERENCES organizations(id) ON DELETE CASCADE, module_id UUID NOT NULL REFERENCES modules(id) ON DELETE CASCADE, enabled BOOLEAN DEFAULT true, config JSONB DEFAULT '{}', enabled_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), UNIQUE(org_id, module_id) ); CREATE INDEX idx_org_modules ON organization_modules(org_id); -- ============================================ -- AUDIT LOGS -- ============================================ CREATE TABLE audit_logs ( id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), org_id UUID REFERENCES organizations(id) ON DELETE CASCADE, user_id UUID REFERENCES users(id) ON DELETE SET NULL, action VARCHAR(100) NOT NULL, entity_type VARCHAR(100), entity_id UUID, old_values JSONB, new_values JSONB, ip_address INET, created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW() ); CREATE INDEX idx_audit_org ON audit_logs(org_id); CREATE INDEX idx_audit_user ON audit_logs(user_id); CREATE INDEX idx_audit_entity ON audit_logs(entity_type, entity_id); CREATE INDEX idx_audit_date ON audit_logs(created_at); -- ============================================ -- REFRESH TOKENS -- ============================================ CREATE TABLE refresh_tokens ( id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE, token_hash VARCHAR(255) UNIQUE NOT NULL, expires_at TIMESTAMP WITH TIME ZONE NOT NULL, created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW() ); CREATE INDEX idx_refresh_tokens_user ON refresh_tokens(user_id); CREATE INDEX idx_refresh_tokens_expires ON refresh_tokens(expires_at); -- ============================================ -- UPDATED_AT TRIGGER -- ============================================ CREATE OR REPLACE FUNCTION update_updated_at() RETURNS TRIGGER AS $$ BEGIN NEW.updated_at = NOW(); RETURN NEW; END; $$ LANGUAGE plpgsql; CREATE TRIGGER update_organizations_updated_at BEFORE UPDATE ON organizations FOR EACH ROW EXECUTE FUNCTION update_updated_at(); CREATE TRIGGER update_users_updated_at BEFORE UPDATE ON users FOR EACH ROW EXECUTE FUNCTION update_updated_at(); CREATE TRIGGER update_orders_updated_at BEFORE UPDATE ON orders FOR EACH ROW EXECUTE FUNCTION update_updated_at(); CREATE TRIGGER update_availability_updated_at BEFORE UPDATE ON availability FOR EACH ROW EXECUTE FUNCTION update_updated_at(); CREATE TRIGGER update_timesheets_updated_at BEFORE UPDATE ON timesheets FOR EACH ROW EXECUTE FUNCTION update_updated_at(); CREATE TRIGGER update_org_modules_updated_at BEFORE UPDATE ON organization_modules FOR EACH ROW EXECUTE FUNCTION update_updated_at(); ``` ### 002_seed_modules.sql ```sql -- Core Modules (können nicht deaktiviert werden) INSERT INTO modules (name, display_name, description, is_core, default_config) VALUES ('core', 'Basis-System', 'Authentifizierung und Benutzerverwaltung', true, '{}'), ('orders', 'Auftragsverwaltung', 'Erstellen und Verwalten von Aufträgen', true, '{}'); -- Optionale Module INSERT INTO modules (name, display_name, description, is_core, default_config) VALUES ('timesheets', 'Stundenzettel', 'Zeiterfassung mit Foto-Upload', false, '{"require_photo": true, "auto_calculate_hours": true}'), ('availability', 'Verfügbarkeit', 'Mitarbeiter-Verfügbarkeitsplanung', false, '{"allow_partial_day": true}'), ('notifications', 'Benachrichtigungen', 'Push/E-Mail Benachrichtigungen', false, '{"email_enabled": true, "push_enabled": false}'), ('reports', 'Berichte', 'Auswertungen und Statistiken', false, '{}'), ('developer', 'Entwickler-Panel', 'Fernverwaltung und Modul-Management', false, '{}'); ``` ### 003_seed_demo.sql ```sql -- Demo Organization INSERT INTO organizations (id, name, slug) VALUES ('11111111-1111-1111-1111-111111111111', 'Demo Sicherheit GmbH', 'demo'); -- Demo Chef User (Password: Admin123!) INSERT INTO users (id, org_id, email, password_hash, role, first_name, last_name, phone) VALUES ('22222222-2222-2222-2222-222222222222', '11111111-1111-1111-1111-111111111111', 'chef@demo.de', '$argon2id$v=19$m=65536,t=3,p=4$DEMO_HASH_REPLACE_ME', 'chef', 'Max', 'Mustermann', '+49 170 1234567'); -- Enable all modules for demo org INSERT INTO organization_modules (org_id, module_id, enabled) SELECT '11111111-1111-1111-1111-111111111111', id, true FROM modules; ``` ## Berechtigungsmatrix | Aktion | Chef | Disponent | Mitarbeiter | |--------|------|-----------|-------------| | **Users** | | | | | Disponenten anlegen | ✅ | ❌ | ❌ | | Mitarbeiter anlegen | ✅ | ✅ (eigene) | ❌ | | Alle User sehen | ✅ | ❌ | ❌ | | Eigene Mitarbeiter sehen | ✅ | ✅ | ❌ | | **Orders** | | | | | Aufträge erstellen | ✅ | ✅ | ❌ | | Aufträge bearbeiten | ✅ | ✅ (eigene) | ❌ | | Aufträge löschen | ✅ | ✅ (eigene) | ❌ | | Alle Aufträge sehen | ✅ | ✅ | ❌ | | Eigene Aufträge sehen | ✅ | ✅ | ✅ | | **Availability** | | | | | Verfügbarkeit melden | ✅ | ✅ | ✅ | | Alle Verfügbarkeiten sehen | ✅ | ✅ | ❌ | | **Timesheets** | | | | | Stundenzettel hochladen | ✅ | ✅ | ✅ | | Stundenzettel genehmigen | ✅ | ✅ | ❌ | | Alle Stundenzettel sehen | ✅ | ✅ | ❌ | | **Modules** | | | | | Module verwalten | ✅ (Dev) | ❌ | ❌ | --- *Schema Version: 1.0.0* *Erstellt: 2026-02-20*