Files
secu/docs/database-schema.md
OpenClaw 70a72f4364 🗄️ Datenbank-Schema Design
Tabellen:
- organizations (Multi-Tenancy)
- users (Chef/Disponent/Mitarbeiter)
- orders (Aufträge)
- order_assignments (Zuweisungen)
- availability (Verfügbarkeit)
- timesheets (Stundenzettel mit Foto)
- modules (Modularer Aufbau)
- organization_modules (Modul-Konfiguration)
- audit_logs (DSGVO)
- refresh_tokens (JWT Auth)

Features:
- Rollen-basierte Berechtigungen
- Multi-Tenancy Support
- Modularer Aufbau
- Audit-Logging
2026-02-20 15:01:30 +00:00

14 KiB

SeCu - Datenbank-Schema

ER-Diagramm

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

-- 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

-- 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

-- 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