-- 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();