diff --git a/db/migrations/001_initial_schema.sql b/db/migrations/001_initial_schema.sql new file mode 100644 index 0000000..538aefc --- /dev/null +++ b/db/migrations/001_initial_schema.sql @@ -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(); diff --git a/db/migrations/002_seed_modules.sql b/db/migrations/002_seed_modules.sql new file mode 100644 index 0000000..37ede6c --- /dev/null +++ b/db/migrations/002_seed_modules.sql @@ -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, '{}'); diff --git a/docs/database-schema.md b/docs/database-schema.md new file mode 100644 index 0000000..f1625dd --- /dev/null +++ b/docs/database-schema.md @@ -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*