🗄️ 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
This commit is contained in:
235
db/migrations/001_initial_schema.sql
Normal file
235
db/migrations/001_initial_schema.sql
Normal file
@@ -0,0 +1,235 @@
|
|||||||
|
-- SeCu Database Schema
|
||||||
|
-- Version: 1.0.0
|
||||||
|
-- Date: 2026-02-20
|
||||||
|
|
||||||
|
-- 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();
|
||||||
18
db/migrations/002_seed_modules.sql
Normal file
18
db/migrations/002_seed_modules.sql
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
-- SeCu Module Seed Data
|
||||||
|
-- Version: 1.0.0
|
||||||
|
|
||||||
|
-- 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, '{}');
|
||||||
453
docs/database-schema.md
Normal file
453
docs/database-schema.md
Normal file
@@ -0,0 +1,453 @@
|
|||||||
|
# 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*
|
||||||
Reference in New Issue
Block a user