OpenNIT

Quelltext des aktuellen Standes

Fassung 1.1.1

001_initial_schema.sql

497 KB

Nur die ersten 5000 Zeilen. Vollständig steht die Datei im Download.

-- Initiales Datenbankschema (konsolidiert aus allen Migrationen)

-- ============================================================
-- IT-Dokumentationssystem - Vollstaendiges Datenbankschema
-- Version: 2.0.0
--
-- Enthaelt ALLE Tabellen fuer eine saubere Neuinstallation:
-- Kern-System, Bestell-Board, IT-Services, Adressbuch,
-- Seitenverknuepfungen, E-Mail-Benachrichtigungen, User-Settings.
--
-- SICHERHEITSHINWEISE:
-- - Revisionssichere Audit-Logs mit Hash-Chain
-- - Seitenversionen sind unveraenderbar (Trigger-geschuetzt)
-- - Audit-Log ist append-only (Trigger-geschuetzt)
-- ============================================================

SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;
SET SQL_MODE = 'STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_ENGINE_SUBSTITUTION';

-- ============================================================
-- BENUTZERVERWALTUNG
-- ============================================================

CREATE TABLE IF NOT EXISTS `users` (
    `id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
    `entra_object_id` CHAR(36) NULL COMMENT 'Azure AD Object ID (GUID) - NULL fuer lokale Benutzer',
    `email` VARCHAR(255) NOT NULL,
    `display_name` VARCHAR(255) NOT NULL,
    `password_hash` VARCHAR(255) NULL COMMENT 'Argon2ID Hash fuer lokalen Login - NULL fuer Azure AD',
    `is_active` BOOLEAN NOT NULL DEFAULT TRUE,
    `is_admin` BOOLEAN NOT NULL DEFAULT FALSE COMMENT 'Direkte Admin-Flag fuer initiale Einrichtung',
    `created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
    `last_login` DATETIME NULL,
    PRIMARY KEY (`id`),
    UNIQUE KEY `uk_users_email` (`email`),
    INDEX `idx_users_entra_object_id` (`entra_object_id`),
    INDEX `idx_users_active` (`is_active`),
    INDEX `idx_users_admin` (`is_admin`),
    INDEX `idx_users_last_login` (`last_login`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
COMMENT='Benutzer aus Microsoft Entra ID oder lokale Benutzer';

-- Rollen
CREATE TABLE IF NOT EXISTS `roles` (
    `id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
    `name` VARCHAR(100) NOT NULL,
    `description` TEXT NOT NULL COMMENT 'Audit-taugliche Beschreibung',
    `is_admin` BOOLEAN NOT NULL DEFAULT FALSE COMMENT 'Globale Admin-Rolle',
    `created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
    `created_by` BIGINT UNSIGNED NULL,
    PRIMARY KEY (`id`),
    UNIQUE KEY `uk_roles_name` (`name`),
    INDEX `idx_roles_admin` (`is_admin`),
    CONSTRAINT `fk_roles_created_by` FOREIGN KEY (`created_by`)
        REFERENCES `users` (`id`) ON DELETE SET NULL ON UPDATE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
COMMENT='Rollen fuer Berechtigungsbuendelung';

-- Capabilities (einzelne Berechtigungen)
CREATE TABLE IF NOT EXISTS `capabilities` (
    `id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
    `name` VARCHAR(50) NOT NULL,
    `description` TEXT NOT NULL COMMENT 'Menschenlesbare Beschreibung fuer Audit',
    `category` VARCHAR(50) NOT NULL,
    `risk_level` ENUM('low', 'medium', 'high', 'critical') NOT NULL,
    `created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
    PRIMARY KEY (`id`),
    UNIQUE KEY `uk_capabilities_name` (`name`),
    INDEX `idx_capabilities_category` (`category`),
    INDEX `idx_capabilities_risk` (`risk_level`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
COMMENT='Atomare Berechtigungen (Capabilities)';

-- Rollen-Capabilities Zuordnung
CREATE TABLE IF NOT EXISTS `role_capabilities` (
    `id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
    `role_id` BIGINT UNSIGNED NOT NULL,
    `capability_id` BIGINT UNSIGNED NOT NULL,
    `created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
    `created_by` BIGINT UNSIGNED NULL,
    PRIMARY KEY (`id`),
    UNIQUE KEY `uk_role_capabilities` (`role_id`, `capability_id`),
    CONSTRAINT `fk_role_capabilities_role` FOREIGN KEY (`role_id`)
        REFERENCES `roles` (`id`) ON DELETE CASCADE ON UPDATE CASCADE,
    CONSTRAINT `fk_role_capabilities_capability` FOREIGN KEY (`capability_id`)
        REFERENCES `capabilities` (`id`) ON DELETE CASCADE ON UPDATE CASCADE,
    CONSTRAINT `fk_role_capabilities_created_by` FOREIGN KEY (`created_by`)
        REFERENCES `users` (`id`) ON DELETE SET NULL ON UPDATE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
COMMENT='Zuordnung Capabilities zu Rollen';

-- ============================================================
-- KERN-CAPABILITIES (Doku/Dateien/Export/Workflow/Admin)
-- ============================================================
-- Diese Rechte wurden historisch AUSSCHLIESSLICH ueber den Seed
-- (database/seeds/001_initial_data.sql) angelegt – den der Migrations-Runner
-- aber nie ausfuehrt. Dadurch konnten sie auf Installationen fehlen, die nicht
-- frisch ueber install.php aufgesetzt wurden (Symptom: „Doku" unsichtbar,
-- /topics → „Zugriff verweigert", keine Themengebiete anlegbar). Sie werden
-- daher hier idempotent (INSERT IGNORE) mitgefuehrt, damit 001 fuer sich allein
-- vollstaendig ist und nicht mehr nur am Seed bzw. an Migration 019 haengt.
INSERT IGNORE INTO `capabilities` (`name`, `description`, `category`, `risk_level`) VALUES
    ('read_page',               'Berechtigung zum Lesen von Seiteninhalten und Metadaten',            'page',     'low'),
    ('create_page',             'Berechtigung zum Erstellen neuer Dokumentationsseiten',              'page',     'medium'),
    ('edit_page',               'Berechtigung zum Bearbeiten bestehender Seiteninhalte',              'page',     'medium'),
    ('delete_page',             'Berechtigung zum Archivieren/Löschen von Seiten',                    'page',     'high'),
    ('restore_version',         'Berechtigung zum Wiederherstellen alter Seitenversionen',            'page',     'medium'),
    ('view_versions',           'Berechtigung zum Einsehen der Versionshistorie',                     'page',     'low'),
    ('upload_file',             'Berechtigung zum Hochladen von Dateianhängen',                       'file',     'medium'),
    ('download_file',           'Berechtigung zum Herunterladen von Dateianhängen',                   'file',     'low'),
    ('delete_file',             'Berechtigung zum Entfernen von Dateianhängen',                       'file',     'high'),
    ('export_pdf',              'Berechtigung zum Exportieren einzelner Seiten als PDF',              'export',   'low'),
    ('export_zip',              'Berechtigung zum Exportieren von Bereichen als ZIP-Archiv',          'export',   'medium'),
    ('export_zip_encrypted',    'Berechtigung zum Erstellen verschlüsselter ZIP-Exporte',            'export',   'medium'),
    ('approve_changes',         'Berechtigung zum Freigeben von Änderungen',                          'workflow', 'high'),
    ('manage_responsibilities', 'Berechtigung zum Verwalten von Verantwortlichkeiten und Prüfzyklen', 'workflow', 'high'),
    ('view_audit_log',          'Berechtigung zum Einsehen des Audit-Logs',                           'admin',    'high'),
    ('manage_topic_permissions','Berechtigung zum Verwalten von Berechtigungen innerhalb eines Themengebiets', 'admin', 'high'),
    ('manage_users',            'Berechtigung zur globalen Benutzerverwaltung',                       'admin',    'critical'),
    ('manage_roles',            'Berechtigung zur Verwaltung von Rollen und deren Capabilities',      'admin',    'critical'),
    ('manage_all_permissions',  'Berechtigung zur globalen Rechteverwaltung',                         'admin',    'critical'),
    ('system_admin',            'Vollstaendige Systemadministrationsrechte',                          'admin',    'critical');

-- ============================================================
-- THEMENGEBIETE (TOPICS)
-- ============================================================

CREATE TABLE IF NOT EXISTS `topics` (
    `id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
    `parent_id` BIGINT UNSIGNED NULL COMMENT 'NULL = Root-Topic',
    `name` VARCHAR(255) NOT NULL,
    `slug` VARCHAR(100) NOT NULL,
    `path` VARCHAR(1000) NOT NULL COMMENT 'Materialisierter Pfad z.B. /1/5/12/',
    `depth` TINYINT UNSIGNED NOT NULL DEFAULT 0,
    `sort_order` INT NOT NULL DEFAULT 0,
    `description` TEXT NULL,
    `image_path` VARCHAR(500) NULL COMMENT 'Pfad zum Topic-Bild',
    `created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
    `created_by` BIGINT UNSIGNED NULL,
    `updated_at` DATETIME NULL ON UPDATE CURRENT_TIMESTAMP,
    `is_archived` BOOLEAN NOT NULL DEFAULT FALSE,
    PRIMARY KEY (`id`),
    UNIQUE KEY `uk_topics_slug_parent` (`parent_id`, `slug`),
    INDEX `idx_topics_path` (`path`(255)),
    INDEX `idx_topics_parent` (`parent_id`),
    INDEX `idx_topics_sort` (`sort_order`),
    INDEX `idx_topics_archived` (`is_archived`),
    CONSTRAINT `fk_topics_parent` FOREIGN KEY (`parent_id`)
        REFERENCES `topics` (`id`) ON DELETE RESTRICT ON UPDATE CASCADE,
    CONSTRAINT `fk_topics_created_by` FOREIGN KEY (`created_by`)
        REFERENCES `users` (`id`) ON DELETE SET NULL ON UPDATE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
COMMENT='Hierarchische Themengebiete';

-- ============================================================
-- BERECHTIGUNGSZUWEISUNGEN
-- ============================================================

CREATE TABLE IF NOT EXISTS `user_role_topics` (
    `id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
    `user_id` BIGINT UNSIGNED NOT NULL,
    `role_id` BIGINT UNSIGNED NOT NULL,
    `topic_id` BIGINT UNSIGNED NULL COMMENT 'NULL = globale Zuweisung',
    `granted_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
    `granted_by` BIGINT UNSIGNED NULL COMMENT 'NULL nur fuer System/Installer-Zuweisungen',
    `valid_until` DATE NULL COMMENT 'Ablaufdatum (optional)',
    `grant_reason` TEXT NOT NULL COMMENT 'Begruendung fuer Audit',
    PRIMARY KEY (`id`),
    UNIQUE KEY `uk_user_role_topics` (`user_id`, `role_id`, `topic_id`),
    INDEX `idx_urt_user` (`user_id`),
    INDEX `idx_urt_role` (`role_id`),
    INDEX `idx_urt_topic` (`topic_id`),
    INDEX `idx_urt_valid` (`valid_until`),
    INDEX `idx_urt_granted_at` (`granted_at`),
    CONSTRAINT `fk_urt_user` FOREIGN KEY (`user_id`)
        REFERENCES `users` (`id`) ON DELETE CASCADE ON UPDATE CASCADE,
    CONSTRAINT `fk_urt_role` FOREIGN KEY (`role_id`)
        REFERENCES `roles` (`id`) ON DELETE CASCADE ON UPDATE CASCADE,
    CONSTRAINT `fk_urt_topic` FOREIGN KEY (`topic_id`)
        REFERENCES `topics` (`id`) ON DELETE CASCADE ON UPDATE CASCADE,
    CONSTRAINT `fk_urt_granted_by` FOREIGN KEY (`granted_by`)
        REFERENCES `users` (`id`) ON DELETE SET NULL ON UPDATE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
COMMENT='Explizite Rechtezuweisung pro Benutzer/Rolle/Topic';

-- ============================================================
-- SEITEN UND VERSIONEN
-- ============================================================

CREATE TABLE IF NOT EXISTS `pages` (
    `id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
    `topic_id` BIGINT UNSIGNED NOT NULL,
    `parent_page_id` BIGINT UNSIGNED NULL COMMENT 'Fuer Unterseiten',
    `title` VARCHAR(500) NOT NULL,
    `slug` VARCHAR(100) NOT NULL,
    `path` VARCHAR(1000) NOT NULL COMMENT 'Materialisierter Pfad',
    `current_version_id` BIGINT UNSIGNED NULL COMMENT 'Aktuelle Version',
    `created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
    `created_by` BIGINT UNSIGNED NULL,
    `updated_at` DATETIME NULL ON UPDATE CURRENT_TIMESTAMP,
    `is_archived` BOOLEAN NOT NULL DEFAULT FALSE,
    `review_status` ENUM('aktuell', 'prüfpflichtig', 'überfällig') NOT NULL DEFAULT 'aktuell',
    `ansprechpartner_type` ENUM('contact','company') NULL DEFAULT NULL,
    `ansprechpartner_id` BIGINT UNSIGNED NULL DEFAULT NULL,
    PRIMARY KEY (`id`),
    UNIQUE KEY `uk_pages_slug_topic` (`topic_id`, `parent_page_id`, `slug`),
    INDEX `idx_pages_path` (`path`(255)),
    INDEX `idx_pages_topic` (`topic_id`),
    INDEX `idx_pages_parent` (`parent_page_id`),
    INDEX `idx_pages_archived` (`is_archived`),
    INDEX `idx_pages_review_status` (`review_status`),
    INDEX `idx_pages_ansprechpartner` (`ansprechpartner_type`, `ansprechpartner_id`),
    FULLTEXT INDEX `ft_pages_title` (`title`),
    CONSTRAINT `fk_pages_topic` FOREIGN KEY (`topic_id`)
        REFERENCES `topics` (`id`) ON DELETE RESTRICT ON UPDATE CASCADE,
    CONSTRAINT `fk_pages_parent` FOREIGN KEY (`parent_page_id`)
        REFERENCES `pages` (`id`) ON DELETE SET NULL ON UPDATE CASCADE,
    CONSTRAINT `fk_pages_created_by` FOREIGN KEY (`created_by`)
        REFERENCES `users` (`id`) ON DELETE SET NULL ON UPDATE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
COMMENT='Dokumentationsseiten';

-- Seitenversionen (IMMUTABLE - keine UPDATE/DELETE erlaubt)
CREATE TABLE IF NOT EXISTS `page_versions` (
    `id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
    `page_id` BIGINT UNSIGNED NOT NULL,
    `version_number` INT UNSIGNED NOT NULL,
    `content_html` LONGTEXT NOT NULL,
    `content_plaintext` LONGTEXT NOT NULL COMMENT 'Fuer Volltextsuche',
    `change_reason` TEXT NULL COMMENT 'Aenderungsgrund',
    `created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
    `created_by` BIGINT UNSIGNED NOT NULL,
    `content_hash` CHAR(64) NOT NULL COMMENT 'SHA-256 des Inhalts',
    `previous_version_id` BIGINT UNSIGNED NULL,
    PRIMARY KEY (`id`),
    UNIQUE KEY `uk_page_versions_number` (`page_id`, `version_number`),
    INDEX `idx_versions_page` (`page_id`),
    INDEX `idx_versions_created` (`created_at`),
    INDEX `idx_versions_author` (`created_by`),
    FULLTEXT INDEX `ft_versions_content` (`content_plaintext`),
    CONSTRAINT `fk_versions_page` FOREIGN KEY (`page_id`)
        REFERENCES `pages` (`id`) ON DELETE CASCADE ON UPDATE CASCADE,
    CONSTRAINT `fk_versions_author` FOREIGN KEY (`created_by`)
        REFERENCES `users` (`id`) ON DELETE RESTRICT ON UPDATE CASCADE,
    CONSTRAINT `fk_versions_previous` FOREIGN KEY (`previous_version_id`)
        REFERENCES `page_versions` (`id`) ON DELETE SET NULL ON UPDATE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
COMMENT='Unveraenderbare Seitenversionen';

-- Fremdschluessel fuer current_version_id (nach Erstellung von page_versions)
ALTER TABLE `pages`
ADD CONSTRAINT `fk_pages_current_version` FOREIGN KEY (`current_version_id`)
    REFERENCES `page_versions` (`id`) ON DELETE SET NULL ON UPDATE CASCADE;

-- Seitenverknuepfungen (bidirektional)
CREATE TABLE IF NOT EXISTS `page_links` (
    `id` BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    `page_id` BIGINT UNSIGNED NOT NULL,
    `linked_page_id` BIGINT UNSIGNED NOT NULL,
    `created_by` BIGINT UNSIGNED NULL,
    `created_at` DATETIME DEFAULT CURRENT_TIMESTAMP,
    CONSTRAINT `fk_page_links_page` FOREIGN KEY (`page_id`) REFERENCES `pages`(`id`) ON DELETE CASCADE,
    CONSTRAINT `fk_page_links_linked` FOREIGN KEY (`linked_page_id`) REFERENCES `pages`(`id`) ON DELETE CASCADE,
    CONSTRAINT `fk_page_links_created_by` FOREIGN KEY (`created_by`) REFERENCES `users`(`id`) ON DELETE SET NULL,
    UNIQUE KEY `uq_page_link` (`page_id`, `linked_page_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
COMMENT='Bidirektionale Seitenverknuepfungen';

-- ============================================================
-- VERANTWORTLICHKEITEN
-- ============================================================

CREATE TABLE IF NOT EXISTS `page_responsibilities` (
    `id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
    `page_id` BIGINT UNSIGNED NOT NULL,
    `technical_owner` BIGINT UNSIGNED NULL COMMENT 'Technisch Verantwortlicher',
    `business_owner` BIGINT UNSIGNED NULL COMMENT 'Fachlich Verantwortlicher',
    `last_review_date` DATE NULL,
    `review_interval_days` INT UNSIGNED NOT NULL DEFAULT 365,
    `next_review_date` DATE GENERATED ALWAYS AS (
        DATE_ADD(`last_review_date`, INTERVAL `review_interval_days` DAY)
    ) STORED,
    `created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
    `updated_at` DATETIME NULL ON UPDATE CURRENT_TIMESTAMP,
    `updated_by` BIGINT UNSIGNED NULL,
    PRIMARY KEY (`id`),
    UNIQUE KEY `uk_responsibilities_page` (`page_id`),
    INDEX `idx_resp_tech_owner` (`technical_owner`),
    INDEX `idx_resp_business_owner` (`business_owner`),
    INDEX `idx_resp_next_review` (`next_review_date`),
    CONSTRAINT `fk_resp_page` FOREIGN KEY (`page_id`)
        REFERENCES `pages` (`id`) ON DELETE CASCADE ON UPDATE CASCADE,
    CONSTRAINT `fk_resp_tech_owner` FOREIGN KEY (`technical_owner`)
        REFERENCES `users` (`id`) ON DELETE SET NULL ON UPDATE CASCADE,
    CONSTRAINT `fk_resp_business_owner` FOREIGN KEY (`business_owner`)
        REFERENCES `users` (`id`) ON DELETE SET NULL ON UPDATE CASCADE,
    CONSTRAINT `fk_resp_updated_by` FOREIGN KEY (`updated_by`)
        REFERENCES `users` (`id`) ON DELETE SET NULL ON UPDATE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
COMMENT='Verantwortlichkeiten und Pruefzyklen';

-- ============================================================
-- DATEIANHÄNGE
-- ============================================================

CREATE TABLE IF NOT EXISTS `page_files` (
    `id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
    `page_id` BIGINT UNSIGNED NOT NULL,
    `version_id` BIGINT UNSIGNED NULL COMMENT 'Version bei Upload',
    `original_filename` VARCHAR(255) NOT NULL,
    `stored_filename` CHAR(36) NOT NULL COMMENT 'UUID fuer Speicherung',
    `file_path` VARCHAR(500) NOT NULL COMMENT 'Relativer Pfad im Storage',
    `file_size` BIGINT UNSIGNED NOT NULL,
    `mime_type` VARCHAR(100) NOT NULL,
    `file_hash` CHAR(64) NOT NULL COMMENT 'SHA-256',
    `uploaded_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
    `uploaded_by` BIGINT UNSIGNED NOT NULL,
    `is_deleted` BOOLEAN NOT NULL DEFAULT FALSE,
    `deleted_at` DATETIME NULL,
    `deleted_by` BIGINT UNSIGNED NULL,
    PRIMARY KEY (`id`),
    INDEX `idx_files_page` (`page_id`),
    INDEX `idx_files_version` (`version_id`),
    INDEX `idx_files_deleted` (`is_deleted`),
    INDEX `idx_files_hash` (`file_hash`),
    FULLTEXT INDEX `ft_files_name` (`original_filename`),
    CONSTRAINT `fk_files_page` FOREIGN KEY (`page_id`)
        REFERENCES `pages` (`id`) ON DELETE CASCADE ON UPDATE CASCADE,
    CONSTRAINT `fk_files_version` FOREIGN KEY (`version_id`)
        REFERENCES `page_versions` (`id`) ON DELETE SET NULL ON UPDATE CASCADE,
    CONSTRAINT `fk_files_uploaded_by` FOREIGN KEY (`uploaded_by`)
        REFERENCES `users` (`id`) ON DELETE RESTRICT ON UPDATE CASCADE,
    CONSTRAINT `fk_files_deleted_by` FOREIGN KEY (`deleted_by`)
        REFERENCES `users` (`id`) ON DELETE SET NULL ON UPDATE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
COMMENT='Dateianhänge zu Seiten';

-- ============================================================
-- BESTELL-BOARD (ORDERS)
-- ============================================================

CREATE TABLE IF NOT EXISTS `orders` (
    `id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
    `title` VARCHAR(500) NOT NULL COMMENT 'Titel der Bestellung',
    `info` TEXT NOT NULL COMMENT 'Referenz / Info (Kostenstelle, Ticket-ID, etc.)',
    `status` ENUM('open', 'delivered', 'closed') NOT NULL DEFAULT 'open',
    `is_done` BOOLEAN NOT NULL DEFAULT FALSE COMMENT 'Alle Komponenten erledigt?',
    `created_by` BIGINT UNSIGNED NULL COMMENT 'Ersteller (User-ID)',
    `delivered_by` BIGINT UNSIGNED NULL COMMENT 'Ausgeliefert von (User-ID)',
    `delivered_at` DATETIME NULL COMMENT 'Zeitpunkt der Auslieferung',
    `delivery_method` VARCHAR(100) NULL COMMENT 'Lieferart (Hauspost, Abgeholt, Freitext)',
    `delivery_note` TEXT NULL COMMENT 'Notiz zur Lieferung',
    `created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
    `updated_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
    PRIMARY KEY (`id`),
    INDEX `idx_orders_status` (`status`),
    INDEX `idx_orders_is_done` (`is_done`),
    INDEX `idx_orders_created_by` (`created_by`),
    INDEX `idx_orders_created_at` (`created_at`),
    CONSTRAINT `fk_orders_created_by` FOREIGN KEY (`created_by`) REFERENCES `users` (`id`) ON DELETE SET NULL,
    CONSTRAINT `fk_orders_delivered_by` FOREIGN KEY (`delivered_by`) REFERENCES `users` (`id`) ON DELETE SET NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
COMMENT='Bestellungen des Bestell-Boards';

CREATE TABLE IF NOT EXISTS `order_components` (
    `id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
    `order_id` BIGINT UNSIGNED NOT NULL COMMENT 'Zugehoerige Bestellung',
    `name` VARCHAR(500) NOT NULL COMMENT 'Komponentenname',
    `is_done` BOOLEAN NOT NULL DEFAULT FALSE COMMENT 'Erledigt?',
    `done_by` BIGINT UNSIGNED NULL COMMENT 'Erledigt von (User-ID)',
    `done_at` DATETIME NULL COMMENT 'Zeitpunkt der Erledigung',
    `created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
    PRIMARY KEY (`id`),
    INDEX `idx_order_components_order_id` (`order_id`),
    INDEX `idx_order_components_is_done` (`is_done`),
    CONSTRAINT `fk_order_components_order_id` FOREIGN KEY (`order_id`) REFERENCES `orders` (`id`) ON DELETE CASCADE,
    CONSTRAINT `fk_order_components_done_by` FOREIGN KEY (`done_by`) REFERENCES `users` (`id`) ON DELETE SET NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
COMMENT='Komponenten einer Bestellung';

CREATE TABLE IF NOT EXISTS `order_info_changes` (
    `id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
    `order_id` BIGINT UNSIGNED NOT NULL COMMENT 'Zugehoerige Bestellung',
    `old_info` TEXT NULL COMMENT 'Vorheriger Info-Text',
    `new_info` TEXT NOT NULL COMMENT 'Neuer Info-Text',
    `changed_by` BIGINT UNSIGNED NOT NULL COMMENT 'Geaendert von (User-ID)',
    `changed_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
    PRIMARY KEY (`id`),
    INDEX `idx_order_info_changes_order_id` (`order_id`),
    CONSTRAINT `fk_order_info_changes_order_id` FOREIGN KEY (`order_id`) REFERENCES `orders` (`id`) ON DELETE CASCADE,
    CONSTRAINT `fk_order_info_changes_changed_by` FOREIGN KEY (`changed_by`) REFERENCES `users` (`id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
COMMENT='Aenderungsverlauf der Bestellungs-Info';

-- ============================================================
-- IT-SERVICES (STARTBOARD)
-- ============================================================

CREATE TABLE IF NOT EXISTS `service_categories` (
    `id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
    `name` VARCHAR(255) NOT NULL COMMENT 'Kategoriename',
    `color` VARCHAR(7) NULL COMMENT 'Hex-Farbe z.B. #38bdf8',
    `sort_order` INT NOT NULL DEFAULT 0,
    `created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
    PRIMARY KEY (`id`),
    UNIQUE KEY `uk_service_categories_name` (`name`),
    INDEX `idx_service_categories_sort` (`sort_order`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
COMMENT='Kategorien fuer IT-Services';

CREATE TABLE IF NOT EXISTS `services` (
    `id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
    `name` VARCHAR(500) NOT NULL COMMENT 'Service-Name',
    `url` VARCHAR(2000) NOT NULL COMMENT 'Service-URL',
    `icon` VARCHAR(500) NULL COMMENT 'Dateiname in uploads/service-icons/',
    `category_id` BIGINT UNSIGNED NULL COMMENT 'Zugehoerige Kategorie',
    `reachability` ENUM('internal', 'external') NULL COMMENT 'Erreichbarkeit: intern oder extern',
    `sort_order` INT NOT NULL DEFAULT 0,
    `created_by` BIGINT UNSIGNED NULL COMMENT 'Ersteller (User-ID)',
    `created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
    `updated_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
    PRIMARY KEY (`id`),
    INDEX `idx_services_category` (`category_id`),
    INDEX `idx_services_sort` (`sort_order`),
    CONSTRAINT `fk_services_category` FOREIGN KEY (`category_id`) REFERENCES `service_categories` (`id`) ON DELETE SET NULL,
    CONSTRAINT `fk_services_created_by` FOREIGN KEY (`created_by`) REFERENCES `users` (`id`) ON DELETE SET NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
COMMENT='IT-Service-Links';

CREATE TABLE IF NOT EXISTS `user_pinned_services` (
    `id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
    `user_id` BIGINT UNSIGNED NOT NULL COMMENT 'Benutzer',
    `service_id` BIGINT UNSIGNED NOT NULL COMMENT 'Angepinnter Service',
    `sort_order` INT NOT NULL DEFAULT 0 COMMENT 'Reihenfolge der Pins',
    `pinned_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
    PRIMARY KEY (`id`),
    UNIQUE KEY `uk_user_service` (`user_id`, `service_id`),
    INDEX `idx_pinned_user` (`user_id`),
    CONSTRAINT `fk_pins_user` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE CASCADE,
    CONSTRAINT `fk_pins_service` FOREIGN KEY (`service_id`) REFERENCES `services` (`id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
COMMENT='Angepinnte Services pro Benutzer';

-- ============================================================
-- ADRESSBUCH (FIRMEN & KONTAKTE)
-- ============================================================

CREATE TABLE IF NOT EXISTS `companies` (
    `id` BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    `name` VARCHAR(255) NOT NULL,
    `customer_number` VARCHAR(100) NULL,
    `address` TEXT NULL,
    `phone` VARCHAR(50) NULL,
    `email` VARCHAR(255) NULL,
    `website` VARCHAR(255) NULL,
    `notes` TEXT NULL,
    `image_path` VARCHAR(255) NULL,
    `created_by` BIGINT UNSIGNED NULL,
    `created_at` DATETIME DEFAULT CURRENT_TIMESTAMP,
    `updated_at` DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
    INDEX `idx_companies_name` (`name`),
    INDEX `idx_companies_customer_number` (`customer_number`),
    CONSTRAINT `fk_companies_created_by` FOREIGN KEY (`created_by`) REFERENCES `users`(`id`) ON DELETE SET NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
COMMENT='Firmen im Adressbuch';

CREATE TABLE IF NOT EXISTS `contacts` (
    `id` BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    `company_id` BIGINT UNSIGNED NOT NULL,
    `first_name` VARCHAR(100) NOT NULL,
    `last_name` VARCHAR(100) NOT NULL,
    `position` VARCHAR(255) NULL,
    `phone` VARCHAR(50) NULL,
    `mobile` VARCHAR(50) NULL,
    `email` VARCHAR(255) NULL,
    `notes` TEXT NULL,
    `image_path` VARCHAR(255) NULL,
    `created_by` BIGINT UNSIGNED NULL,
    `created_at` DATETIME DEFAULT CURRENT_TIMESTAMP,
    `updated_at` DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
    INDEX `idx_contacts_company` (`company_id`),
    INDEX `idx_contacts_name` (`last_name`, `first_name`),
    CONSTRAINT `fk_contacts_company` FOREIGN KEY (`company_id`) REFERENCES `companies`(`id`) ON DELETE CASCADE,
    CONSTRAINT `fk_contacts_created_by` FOREIGN KEY (`created_by`) REFERENCES `users`(`id`) ON DELETE SET NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
COMMENT='Kontakte im Adressbuch';

-- ============================================================
-- BENUTZEREINSTELLUNGEN
-- ============================================================

CREATE TABLE IF NOT EXISTS `user_settings` (
    `id` BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    `user_id` BIGINT UNSIGNED NOT NULL,
    `setting_key` VARCHAR(100) NOT NULL,
    `setting_value` VARCHAR(500) NULL,
    `updated_at` DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
    UNIQUE KEY `uk_user_setting` (`user_id`, `setting_key`),
    CONSTRAINT `fk_user_settings_user` FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
COMMENT='Benutzereinstellungen (Key-Value)';

-- ============================================================
-- E-MAIL-BENACHRICHTIGUNGEN
-- ============================================================

CREATE TABLE IF NOT EXISTS `email_templates` (
    `id` BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    `template_key` VARCHAR(100) NOT NULL UNIQUE,
    `subject` VARCHAR(255) NOT NULL,
    `body_html` TEXT NOT NULL,
    `description` VARCHAR(255) DEFAULT NULL,
    `updated_by` BIGINT UNSIGNED NULL,
    `updated_at` DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
    CONSTRAINT `fk_email_templates_updated_by` FOREIGN KEY (`updated_by`) REFERENCES `users`(`id`) ON DELETE SET NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
COMMENT='E-Mail-Vorlagen';

CREATE TABLE IF NOT EXISTS `notification_queue` (
    `id` BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    `user_id` BIGINT UNSIGNED NOT NULL,
    `template_key` VARCHAR(100) NOT NULL,
    `variables_json` TEXT NOT NULL,
    `status` ENUM('pending', 'sent', 'failed') DEFAULT 'pending',
    `error_message` TEXT DEFAULT NULL,
    `created_at` DATETIME DEFAULT CURRENT_TIMESTAMP,
    `sent_at` DATETIME DEFAULT NULL,
    CONSTRAINT `fk_notification_queue_user` FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
COMMENT='Benachrichtigungs-Warteschlange';

-- ============================================================
-- AUDIT LOG (APPEND-ONLY, HASH-CHAIN)
-- ============================================================

CREATE TABLE IF NOT EXISTS `audit_log` (
    `id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
    `event_id` CHAR(36) NOT NULL COMMENT 'UUID fuer Event',
    `event_timestamp` DATETIME(6) NOT NULL COMMENT 'Mikrosekunden-Praezision',
    `user_id` BIGINT UNSIGNED NULL COMMENT 'NULL fuer System-Events',
    `session_id` VARCHAR(128) NULL,
    `ip_address` VARCHAR(45) NULL COMMENT 'IPv4/IPv6',
    `user_agent` VARCHAR(500) NULL,
    `action_category` ENUM(
        'auth',
        'permission',
        'content',
        'file',
        'export',
        'admin',
        'security',
        'contact',
        'order',
        'service'
    ) NOT NULL,
    `action_type` VARCHAR(50) NOT NULL COMMENT 'z.B. login, create_page, upload_file',
    `object_type` VARCHAR(50) NULL COMMENT 'z.B. page, topic, user',
    `object_id` BIGINT UNSIGNED NULL,
    `object_name` VARCHAR(500) NULL COMMENT 'Menschenlesbarer Name',
    `details_json` JSON NULL COMMENT 'Zusaetzliche Details',
    `old_value_json` JSON NULL COMMENT 'Wert vor Aenderung',
    `new_value_json` JSON NULL COMMENT 'Wert nach Aenderung',
    `result` ENUM('success', 'failure', 'denied') NOT NULL,
    `risk_level` ENUM('low', 'medium', 'high', 'critical') NOT NULL DEFAULT 'low',
    `checksum` CHAR(64) NOT NULL COMMENT 'SHA-256 Hash-Chain',
    PRIMARY KEY (`id`),
    UNIQUE KEY `uk_audit_event_id` (`event_id`),
    INDEX `idx_audit_timestamp` (`event_timestamp`),
    INDEX `idx_audit_user` (`user_id`, `event_timestamp`),
    INDEX `idx_audit_object` (`object_type`, `object_id`),
    INDEX `idx_audit_action` (`action_category`, `action_type`),
    INDEX `idx_audit_result` (`result`),
    INDEX `idx_audit_risk` (`risk_level`, `event_timestamp`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
COMMENT='Revisionssicheres Audit-Log mit Hash-Chain';

-- ============================================================
-- SESSIONS (fuer Audit-Tracking)
-- ============================================================

CREATE TABLE IF NOT EXISTS `user_sessions` (
    `id` VARCHAR(128) NOT NULL,
    `user_id` BIGINT UNSIGNED NOT NULL,
    `ip_address` VARCHAR(45) NOT NULL,
    `user_agent` VARCHAR(500) NULL,
    `created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
    `last_activity` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
    `expires_at` DATETIME NOT NULL,
    `is_valid` BOOLEAN NOT NULL DEFAULT TRUE,
    PRIMARY KEY (`id`),
    INDEX `idx_sessions_user` (`user_id`),
    INDEX `idx_sessions_expires` (`expires_at`),
    INDEX `idx_sessions_valid` (`is_valid`),
    CONSTRAINT `fk_sessions_user` FOREIGN KEY (`user_id`)
        REFERENCES `users` (`id`) ON DELETE CASCADE ON UPDATE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
COMMENT='Aktive Benutzersessions';

-- ============================================================
-- RATE LIMITING
-- ============================================================

CREATE TABLE IF NOT EXISTS `rate_limits` (
    `id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
    `identifier` VARCHAR(255) NOT NULL COMMENT 'IP oder User-ID',
    `endpoint` VARCHAR(100) NOT NULL,
    `requests` INT UNSIGNED NOT NULL DEFAULT 1,
    `window_start` DATETIME NOT NULL,
    PRIMARY KEY (`id`),
    UNIQUE KEY `uk_rate_limits` (`identifier`, `endpoint`, `window_start`),
    INDEX `idx_rate_limits_window` (`window_start`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
COMMENT='Rate Limiting Tracking';

-- ============================================================
-- VIEWS FUER AUDIT-REPORTS
-- ============================================================

CREATE OR REPLACE VIEW `v_permission_matrix` AS
SELECT
    u.id AS user_id,
    u.email AS user_email,
    u.display_name AS user_name,
    u.is_active AS user_active,
    r.id AS role_id,
    r.name AS role_name,
    r.description AS role_description,
    r.is_admin AS is_global_admin,
    t.id AS topic_id,
    t.name AS topic_name,
    t.path AS topic_path,
    CASE
        WHEN urt.topic_id IS NULL THEN 'GLOBAL'
        ELSE 'TOPIC-SPECIFIC'
    END AS permission_scope,
    c.id AS capability_id,
    c.name AS capability_name,
    c.description AS capability_description,
    c.category AS capability_category,
    c.risk_level AS capability_risk_level,
    urt.granted_at,
    urt.valid_until,
    urt.grant_reason,
    grantor.email AS granted_by_email,
    CASE
        WHEN urt.valid_until IS NOT NULL AND urt.valid_until < CURDATE()
        THEN 'EXPIRED'
        WHEN u.is_active = FALSE
        THEN 'USER_INACTIVE'
        ELSE 'ACTIVE'
    END AS permission_status
FROM users u
INNER JOIN user_role_topics urt ON u.id = urt.user_id
INNER JOIN roles r ON urt.role_id = r.id
INNER JOIN role_capabilities rc ON r.id = rc.role_id
INNER JOIN capabilities c ON rc.capability_id = c.id
LEFT JOIN topics t ON urt.topic_id = t.id
LEFT JOIN users grantor ON urt.granted_by = grantor.id
ORDER BY
    u.email,
    t.path,
    c.category,
    c.name;

CREATE OR REPLACE VIEW `v_review_status` AS
SELECT
    p.id AS page_id,
    p.title AS page_title,
    p.path AS page_path,
    t.name AS topic_name,
    pr.last_review_date,
    pr.review_interval_days,
    pr.next_review_date,
    tech.display_name AS technical_owner_name,
    tech.email AS technical_owner_email,
    biz.display_name AS business_owner_name,
    biz.email AS business_owner_email,
    CASE
        WHEN pr.next_review_date IS NULL THEN 'prüfpflichtig'
        WHEN pr.next_review_date < CURDATE() THEN 'überfällig'
        WHEN pr.next_review_date <= DATE_ADD(CURDATE(), INTERVAL 30 DAY) THEN 'prüfpflichtig'
        ELSE 'aktuell'
    END AS calculated_status,
    DATEDIFF(pr.next_review_date, CURDATE()) AS days_until_review
FROM pages p
LEFT JOIN page_responsibilities pr ON p.id = pr.page_id
LEFT JOIN topics t ON p.topic_id = t.id
LEFT JOIN users tech ON pr.technical_owner = tech.id
LEFT JOIN users biz ON pr.business_owner = biz.id
WHERE p.is_archived = FALSE;

CREATE OR REPLACE VIEW `v_audit_log_detailed` AS
SELECT
    al.id,
    al.event_id,
    al.event_timestamp,
    al.user_id,
    u.email AS user_email,
    u.display_name AS user_name,
    al.session_id,
    al.ip_address,
    al.user_agent,
    al.action_category,
    al.action_type,
    al.object_type,
    al.object_id,
    al.object_name,
    al.details_json,
    al.old_value_json,
    al.new_value_json,
    al.result,
    al.risk_level,
    al.checksum
FROM audit_log al
LEFT JOIN users u ON al.user_id = u.id;

-- ============================================================
-- TRIGGER FUER DATENINTEGRITAET
--
-- Hinweis: Einzeilige SIGNAL-Syntax (ohne BEGIN...END/DELIMITER)
-- damit sie sowohl im mysql-Client als auch via PDO funktionieren.
-- Topic-Trigger wurden entfernt — Pfadberechnung erfolgt im PHP-Code.
-- DROP TRIGGER IF EXISTS vor jedem CREATE macht die Migration idempotent.
-- Hinweis: Auf Shared-Hosting mit aktivem Binary-Log und ohne SUPER-Privileg
-- schlaegt CREATE TRIGGER mit Fehler 1419 fehl. Das wird im UpdateManager
-- als nicht-fatal behandelt (Trigger existieren dann bereits).
-- ============================================================

-- Revisionssicherheit: page_versions
DROP TRIGGER IF EXISTS `tr_page_versions_no_update`;
CREATE TRIGGER `tr_page_versions_no_update`
BEFORE UPDATE ON `page_versions`
FOR EACH ROW
    SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'UPDATE auf page_versions ist nicht erlaubt (Revisionssicherheit)';

DROP TRIGGER IF EXISTS `tr_page_versions_no_delete`;
CREATE TRIGGER `tr_page_versions_no_delete`
BEFORE DELETE ON `page_versions`
FOR EACH ROW
    SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'DELETE auf page_versions ist nicht erlaubt (Revisionssicherheit)';

-- Revisionssicherheit: audit_log
DROP TRIGGER IF EXISTS `tr_audit_log_no_update`;
CREATE TRIGGER `tr_audit_log_no_update`
BEFORE UPDATE ON `audit_log`
FOR EACH ROW
    SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'UPDATE auf audit_log ist nicht erlaubt (Revisionssicherheit)';

DROP TRIGGER IF EXISTS `tr_audit_log_no_delete`;
CREATE TRIGGER `tr_audit_log_no_delete`
BEFORE DELETE ON `audit_log`
FOR EACH ROW
    SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'DELETE auf audit_log ist nicht erlaubt (Revisionssicherheit)';

SET FOREIGN_KEY_CHECKS = 1;

-- Migration: 002_fix_topic_triggers.sql
-- ============================================================
-- Migration: Topic-Trigger korrigieren
--
-- Der BEFORE INSERT Trigger versucht NEW.id zu verwenden,
-- was bei AUTO_INCREMENT noch nicht existiert.
-- Die Pfad-Berechnung wird stattdessen im PHP-Code gehandhabt.
-- ============================================================

-- Trigger entfernen (falls vorhanden)
DROP TRIGGER IF EXISTS `tr_topics_before_insert`;
DROP TRIGGER IF EXISTS `tr_topics_after_insert`;

-- Hinweis: Die Pfad-Berechnung erfolgt nun im PHP-Code (TopicManager)

-- Migration: 003_add_topic_image.sql
-- ============================================================
-- Migration: Topic-Bilder hinzufügen
-- Version: 1.0.3
-- ============================================================

-- Spalte für Bild-Pfad hinzufügen
ALTER TABLE `topics`
ADD COLUMN `image_path` VARCHAR(500) NULL COMMENT 'Pfad zum Topic-Bild' AFTER `description`;

-- Migration: 004_orders_system.sql
-- ============================================================
-- Migration: Bestell-Board (Order Tracking System)
-- Version: 1.0.4
-- ============================================================

SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;

-- ============================================================
-- Tabelle: orders (Bestellungen)
-- ============================================================
CREATE TABLE IF NOT EXISTS `orders` (
    `id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
    `title` VARCHAR(500) NOT NULL COMMENT 'Titel der Bestellung',
    `info` TEXT NOT NULL COMMENT 'Referenz / Info (Kostenstelle, Ticket-ID, etc.)',
    `status` ENUM('open', 'delivered', 'closed') NOT NULL DEFAULT 'open' COMMENT 'Status: open=Offen, delivered=Bereit zur Auslieferung, closed=Archiviert',
    `is_done` BOOLEAN NOT NULL DEFAULT FALSE COMMENT 'Alle Komponenten erledigt?',
    `created_by` BIGINT UNSIGNED NULL COMMENT 'Ersteller (User-ID)',
    `delivered_by` BIGINT UNSIGNED NULL COMMENT 'Ausgeliefert von (User-ID)',
    `delivered_at` DATETIME NULL COMMENT 'Zeitpunkt der Auslieferung',
    `delivery_method` VARCHAR(100) NULL COMMENT 'Lieferart (Hauspost, Abgeholt, Freitext)',
    `delivery_note` TEXT NULL COMMENT 'Notiz zur Lieferung',
    `created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
    `updated_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
    PRIMARY KEY (`id`),
    INDEX `idx_orders_status` (`status`),
    INDEX `idx_orders_is_done` (`is_done`),
    INDEX `idx_orders_created_by` (`created_by`),
    INDEX `idx_orders_created_at` (`created_at`),
    CONSTRAINT `fk_orders_created_by` FOREIGN KEY (`created_by`) REFERENCES `users` (`id`) ON DELETE SET NULL,
    CONSTRAINT `fk_orders_delivered_by` FOREIGN KEY (`delivered_by`) REFERENCES `users` (`id`) ON DELETE SET NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
COMMENT='Bestellungen des Bestell-Boards';

-- ============================================================
-- Tabelle: order_components (Bestellkomponenten)
-- ============================================================
CREATE TABLE IF NOT EXISTS `order_components` (
    `id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
    `order_id` BIGINT UNSIGNED NOT NULL COMMENT 'Zugehoerige Bestellung',
    `name` VARCHAR(500) NOT NULL COMMENT 'Komponentenname',
    `is_done` BOOLEAN NOT NULL DEFAULT FALSE COMMENT 'Erledigt?',
    `done_by` BIGINT UNSIGNED NULL COMMENT 'Erledigt von (User-ID)',
    `done_at` DATETIME NULL COMMENT 'Zeitpunkt der Erledigung',
    `created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
    PRIMARY KEY (`id`),
    INDEX `idx_order_components_order_id` (`order_id`),
    INDEX `idx_order_components_is_done` (`is_done`),
    CONSTRAINT `fk_order_components_order_id` FOREIGN KEY (`order_id`) REFERENCES `orders` (`id`) ON DELETE CASCADE,
    CONSTRAINT `fk_order_components_done_by` FOREIGN KEY (`done_by`) REFERENCES `users` (`id`) ON DELETE SET NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
COMMENT='Komponenten einer Bestellung';

-- ============================================================
-- Tabelle: order_info_changes (Aenderungsverlauf der Info)
-- ============================================================
CREATE TABLE IF NOT EXISTS `order_info_changes` (
    `id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
    `order_id` BIGINT UNSIGNED NOT NULL COMMENT 'Zugehoerige Bestellung',
    `old_info` TEXT NULL COMMENT 'Vorheriger Info-Text',
    `new_info` TEXT NOT NULL COMMENT 'Neuer Info-Text',
    `changed_by` BIGINT UNSIGNED NOT NULL COMMENT 'Geaendert von (User-ID)',
    `changed_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
    PRIMARY KEY (`id`),
    INDEX `idx_order_info_changes_order_id` (`order_id`),
    CONSTRAINT `fk_order_info_changes_order_id` FOREIGN KEY (`order_id`) REFERENCES `orders` (`id`) ON DELETE CASCADE,
    CONSTRAINT `fk_order_info_changes_changed_by` FOREIGN KEY (`changed_by`) REFERENCES `users` (`id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
COMMENT='Aenderungsverlauf der Bestellungs-Info';

-- ============================================================
-- ENUM-Erweiterung: Kategorie 'order' fuer Capabilities
-- ============================================================
ALTER TABLE `capabilities`
    MODIFY COLUMN `category` VARCHAR(50) NOT NULL;

-- ============================================================
-- Neue Capability: manage_orders
-- ============================================================
INSERT INTO `capabilities` (`name`, `description`, `category`, `risk_level`)
VALUES ('manage_orders', 'Bestellungen verwalten (Bestell-Board)', 'order', 'medium');

SET FOREIGN_KEY_CHECKS = 1;

-- Migration: 005_services_system.sql
-- ============================================================
-- Migration: IT-Services System (Startboard Integration)
-- Version: 1.0.5
-- ============================================================

SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;

-- ============================================================
-- Tabelle: service_categories (Kategorien fuer IT-Services)
-- ============================================================
CREATE TABLE IF NOT EXISTS `service_categories` (
    `id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
    `name` VARCHAR(255) NOT NULL COMMENT 'Kategoriename',
    `color` VARCHAR(7) NULL COMMENT 'Hex-Farbe z.B. #38bdf8',
    `sort_order` INT NOT NULL DEFAULT 0,
    `created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
    PRIMARY KEY (`id`),
    UNIQUE KEY `uk_service_categories_name` (`name`),
    INDEX `idx_service_categories_sort` (`sort_order`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
COMMENT='Kategorien fuer IT-Services';

-- ============================================================
-- Tabelle: services (IT-Service-Links)
-- ============================================================
CREATE TABLE IF NOT EXISTS `services` (
    `id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
    `name` VARCHAR(500) NOT NULL COMMENT 'Service-Name',
    `url` VARCHAR(2000) NOT NULL COMMENT 'Service-URL',
    `icon` VARCHAR(500) NULL COMMENT 'Dateiname in uploads/service-icons/',
    `category_id` BIGINT UNSIGNED NULL COMMENT 'Zugehoerige Kategorie',
    `reachability` ENUM('internal', 'external') NULL COMMENT 'Erreichbarkeit: intern oder extern',
    `sort_order` INT NOT NULL DEFAULT 0,
    `created_by` BIGINT UNSIGNED NULL COMMENT 'Ersteller (User-ID)',
    `created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
    `updated_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
    PRIMARY KEY (`id`),
    INDEX `idx_services_category` (`category_id`),
    INDEX `idx_services_sort` (`sort_order`),
    CONSTRAINT `fk_services_category` FOREIGN KEY (`category_id`) REFERENCES `service_categories` (`id`) ON DELETE SET NULL,
    CONSTRAINT `fk_services_created_by` FOREIGN KEY (`created_by`) REFERENCES `users` (`id`) ON DELETE SET NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
COMMENT='IT-Service-Links';

-- ============================================================
-- Tabelle: user_pinned_services (User-Pins/Favoriten)
-- ============================================================
CREATE TABLE IF NOT EXISTS `user_pinned_services` (
    `id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
    `user_id` BIGINT UNSIGNED NOT NULL COMMENT 'Benutzer',
    `service_id` BIGINT UNSIGNED NOT NULL COMMENT 'Angepinnter Service',
    `sort_order` INT NOT NULL DEFAULT 0 COMMENT 'Reihenfolge der Pins',
    `pinned_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
    PRIMARY KEY (`id`),
    UNIQUE KEY `uk_user_service` (`user_id`, `service_id`),
    INDEX `idx_pinned_user` (`user_id`),
    CONSTRAINT `fk_pins_user` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE CASCADE,
    CONSTRAINT `fk_pins_service` FOREIGN KEY (`service_id`) REFERENCES `services` (`id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
COMMENT='Angepinnte Services pro Benutzer';

-- ============================================================
-- ENUM-Erweiterung: Kategorie 'service' fuer Capabilities
-- ============================================================
ALTER TABLE `capabilities`
    MODIFY COLUMN `category` VARCHAR(50) NOT NULL;

-- ============================================================
-- Neue Capabilities: view_services, manage_services
-- ============================================================
INSERT INTO `capabilities` (`name`, `description`, `category`, `risk_level`) VALUES
('view_services', 'IT-Services ansehen und nutzen', 'service', 'low'),
('manage_services', 'IT-Services und Kategorien verwalten (anlegen, bearbeiten, loeschen)', 'service', 'medium');

SET FOREIGN_KEY_CHECKS = 1;

-- Migration: 006_contacts_system.sql
-- ============================================================
-- Migration 006: Adressbuch-System (Firmen & Kontakte)
-- ============================================================

-- Firmen
CREATE TABLE IF NOT EXISTS `companies` (
    `id` BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    `name` VARCHAR(255) NOT NULL,
    `address` TEXT NULL,
    `phone` VARCHAR(50) NULL,
    `email` VARCHAR(255) NULL,
    `website` VARCHAR(255) NULL,
    `notes` TEXT NULL,
    `created_by` BIGINT UNSIGNED NULL,
    `created_at` DATETIME DEFAULT CURRENT_TIMESTAMP,
    `updated_at` DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
    INDEX `idx_companies_name` (`name`),
    CONSTRAINT `fk_companies_created_by` FOREIGN KEY (`created_by`) REFERENCES `users`(`id`) ON DELETE SET NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

-- Kontakte (gehoeren zu Firmen)
CREATE TABLE IF NOT EXISTS `contacts` (
    `id` BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    `company_id` BIGINT UNSIGNED NOT NULL,
    `first_name` VARCHAR(100) NOT NULL,
    `last_name` VARCHAR(100) NOT NULL,
    `position` VARCHAR(255) NULL,
    `phone` VARCHAR(50) NULL,
    `mobile` VARCHAR(50) NULL,
    `email` VARCHAR(255) NULL,
    `notes` TEXT NULL,
    `created_by` BIGINT UNSIGNED NULL,
    `created_at` DATETIME DEFAULT CURRENT_TIMESTAMP,
    `updated_at` DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
    INDEX `idx_contacts_company` (`company_id`),
    INDEX `idx_contacts_name` (`last_name`, `first_name`),
    CONSTRAINT `fk_contacts_company` FOREIGN KEY (`company_id`) REFERENCES `companies`(`id`) ON DELETE CASCADE,
    CONSTRAINT `fk_contacts_created_by` FOREIGN KEY (`created_by`) REFERENCES `users`(`id`) ON DELETE SET NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

-- Capabilities-ENUM erweitern
ALTER TABLE `capabilities`
    MODIFY COLUMN `category` VARCHAR(50) NOT NULL;

-- Berechtigungen
INSERT INTO `capabilities` (`name`, `description`, `category`, `risk_level`) VALUES
('view_contacts', 'Adressbuch ansehen', 'contact', 'low'),
('manage_contacts', 'Kontakte und Firmen verwalten', 'contact', 'medium');

-- Migration: 007_contacts_images.sql
-- ============================================================
-- Migration 007: Bild-Upload fuer Firmen und Kontakte
-- ============================================================

ALTER TABLE `companies` ADD COLUMN `image_path` VARCHAR(255) NULL AFTER `notes`;
ALTER TABLE `contacts` ADD COLUMN `image_path` VARCHAR(255) NULL AFTER `notes`;

-- Migration: 008_audit_contact_category.sql
-- ============================================================
-- Migration 008: Audit-Log action_category um 'contact' erweitern
-- Hinweis: Enthaelt alle bekannten Werte (inkl. spaeterer Migrationen),
-- damit ein Rerun-All keine bestehenden Eintraege abschneidet (Data truncated 1265).
-- ============================================================

ALTER TABLE `audit_log`
    MODIFY COLUMN `action_category` ENUM(
        'auth',
        'permission',
        'content',
        'file',
        'export',
        'admin',
        'security',
        'contact',
        'order',
        'service',
        'contract',
        'loan',
        'ipam',
        'vault',
        'tickets',
        'ssl',
        'kanban'
    ) NOT NULL;

-- Migration: 009_fix_upload_urls.sql
-- ============================================================
-- Migration 009: Upload-URLs in page_versions reparieren
--
-- Problem: DOMDocument hat beim HTML-Purifying URLs manipuliert:
-- /uploads/ wurde zu ../uploads/, ./uploads/ oder uploads/
-- Dadurch werden Bilder relativ zur Seiten-URL aufgeloest
-- und nicht gefunden.
--
-- Fix: Alle Varianten zu /uploads/ normalisieren.
-- ============================================================

-- ../uploads/ -> /uploads/ (haeufigster Fall durch DOMDocument)
UPDATE `page_versions`
SET `content_html` = REPLACE(`content_html`, 'src="../uploads/', 'src="/uploads/')
WHERE `content_html` LIKE '%src="../uploads/%';

UPDATE `page_versions`
SET `content_html` = REPLACE(`content_html`, "src='../uploads/", "src='/uploads/")
WHERE `content_html` LIKE "%src='../uploads/%";

UPDATE `page_versions`
SET `content_html` = REPLACE(`content_html`, 'href="../uploads/', 'href="/uploads/')
WHERE `content_html` LIKE '%href="../uploads/%';

UPDATE `page_versions`
SET `content_html` = REPLACE(`content_html`, "href='../uploads/", "href='/uploads/")
WHERE `content_html` LIKE "%href='../uploads/%";

-- ../../uploads/ -> /uploads/ (doppelt verschachtelte Pfade)
UPDATE `page_versions`
SET `content_html` = REPLACE(`content_html`, 'src="../../uploads/', 'src="/uploads/')
WHERE `content_html` LIKE '%src="../../uploads/%';

UPDATE `page_versions`
SET `content_html` = REPLACE(`content_html`, "src='../../uploads/", "src='/uploads/")
WHERE `content_html` LIKE "%src='../../uploads/%";

UPDATE `page_versions`
SET `content_html` = REPLACE(`content_html`, 'href="../../uploads/', 'href="/uploads/')
WHERE `content_html` LIKE '%href="../../uploads/%';

UPDATE `page_versions`
SET `content_html` = REPLACE(`content_html`, "href='../../uploads/", "href='/uploads/")
WHERE `content_html` LIKE "%href='../../uploads/%";

-- ./uploads/ -> /uploads/
UPDATE `page_versions`
SET `content_html` = REPLACE(`content_html`, 'src="./uploads/', 'src="/uploads/')
WHERE `content_html` LIKE '%src="./uploads/%';

UPDATE `page_versions`
SET `content_html` = REPLACE(`content_html`, "src='./uploads/", "src='/uploads/")
WHERE `content_html` LIKE "%src='./uploads/%";

UPDATE `page_versions`
SET `content_html` = REPLACE(`content_html`, 'href="./uploads/', 'href="/uploads/')
WHERE `content_html` LIKE '%href="./uploads/%';

UPDATE `page_versions`
SET `content_html` = REPLACE(`content_html`, "href='./uploads/", "href='/uploads/")
WHERE `content_html` LIKE "%href='./uploads/%";

-- uploads/ (ohne Pfad-Prefix) -> /uploads/
UPDATE `page_versions`
SET `content_html` = REPLACE(`content_html`, 'src="uploads/', 'src="/uploads/')
WHERE `content_html` LIKE '%src="uploads/%';

UPDATE `page_versions`
SET `content_html` = REPLACE(`content_html`, "src='uploads/", "src='/uploads/")
WHERE `content_html` LIKE "%src='uploads/%";

UPDATE `page_versions`
SET `content_html` = REPLACE(`content_html`, 'href="uploads/', 'href="/uploads/')
WHERE `content_html` LIKE '%href="uploads/%';

UPDATE `page_versions`
SET `content_html` = REPLACE(`content_html`, "href='uploads/", "href='/uploads/")
WHERE `content_html` LIKE "%href='uploads/%";

-- Doppelte Slashes korrigieren (//uploads/ -> /uploads/)
UPDATE `page_versions`
SET `content_html` = REPLACE(`content_html`, 'src="//uploads/', 'src="/uploads/')
WHERE `content_html` LIKE '%src="//uploads/%';

UPDATE `page_versions`
SET `content_html` = REPLACE(`content_html`, "src='//uploads/", "src='/uploads/")
WHERE `content_html` LIKE "%src='//uploads/%";

UPDATE `page_versions`
SET `content_html` = REPLACE(`content_html`, 'href="//uploads/', 'href="/uploads/')
WHERE `content_html` LIKE '%href="//uploads/%';

UPDATE `page_versions`
SET `content_html` = REPLACE(`content_html`, "href='//uploads/", "href='/uploads/")
WHERE `content_html` LIKE "%href='//uploads/%";

-- Migration: 010_audit_order_service_category.sql
-- ============================================================
-- Migration 010: Audit-Log action_category um 'order' und 'service' erweitern
-- Hinweis: Enthaelt alle bekannten Werte (inkl. spaeterer Migrationen),
-- damit ein Rerun-All keine bestehenden Eintraege abschneidet (Data truncated 1265).
-- ============================================================

ALTER TABLE `audit_log`
    MODIFY COLUMN `action_category` ENUM(
        'auth',
        'permission',
        'content',
        'file',
        'export',
        'admin',
        'security',
        'contact',
        'order',
        'service',
        'contract',
        'loan',
        'ipam',
        'vault',
        'tickets',
        'ssl',
        'kanban'
    ) NOT NULL;

-- Migration: 011_fix_capability_categories.sql
-- ============================================================
-- Migration 011: Capability-Kategorien explizit setzen
--
-- Stellt sicher, dass manage_orders, view_services und
-- manage_services in den richtigen Kategorien sind.
-- ============================================================

-- Sicherstellen dass das ENUM alle Kategorien hat
ALTER TABLE `capabilities`
    MODIFY COLUMN `category` VARCHAR(50) NOT NULL;

-- manage_orders -> Kategorie 'order'
UPDATE `capabilities` SET `category` = 'order' WHERE `name` = 'manage_orders';

-- view_services und manage_services -> Kategorie 'service'
UPDATE `capabilities` SET `category` = 'service' WHERE `name` = 'view_services';
UPDATE `capabilities` SET `category` = 'service' WHERE `name` = 'manage_services';

-- view_contacts und manage_contacts -> Kategorie 'contact'
UPDATE `capabilities` SET `category` = 'contact' WHERE `name` = 'view_contacts';
UPDATE `capabilities` SET `category` = 'contact' WHERE `name` = 'manage_contacts';

-- Migration: 012_user_settings.sql
-- ============================================================
-- Migration 012: Benutzereinstellungen
-- ============================================================

CREATE TABLE IF NOT EXISTS `user_settings` (
    `id` BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    `user_id` BIGINT UNSIGNED NOT NULL,
    `setting_key` VARCHAR(100) NOT NULL,
    `setting_value` VARCHAR(500) NULL,
    `updated_at` DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
    UNIQUE KEY `uk_user_setting` (`user_id`, `setting_key`),
    CONSTRAINT `fk_user_settings_user` FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

-- Migration: 013_company_customer_number.sql
-- ============================================================
-- Migration 013: Kundennummer fuer Firmen
-- ============================================================

ALTER TABLE `companies` ADD COLUMN `customer_number` VARCHAR(100) NULL AFTER `name`;
CREATE INDEX `idx_companies_customer_number` ON `companies` (`customer_number`);

-- Migration: 014_page_links.sql
-- Seitenverknüpfungen (bidirektional)
CREATE TABLE IF NOT EXISTS page_links (
    id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    page_id BIGINT UNSIGNED NOT NULL,
    linked_page_id BIGINT UNSIGNED NOT NULL,
    created_by BIGINT UNSIGNED,
    created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
    CONSTRAINT fk_page_links_page FOREIGN KEY (page_id) REFERENCES pages(id) ON DELETE CASCADE,
    CONSTRAINT fk_page_links_linked FOREIGN KEY (linked_page_id) REFERENCES pages(id) ON DELETE CASCADE,
    CONSTRAINT fk_page_links_created_by FOREIGN KEY (created_by) REFERENCES users(id) ON DELETE SET NULL,
    UNIQUE KEY uq_page_link (page_id, linked_page_id)
) ENGINE=InnoDB;

-- Migration: 015_email_notifications.sql
-- E-Mail-Templates
CREATE TABLE IF NOT EXISTS email_templates (
    id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    template_key VARCHAR(100) NOT NULL UNIQUE,
    subject VARCHAR(255) NOT NULL,
    body_html TEXT NOT NULL,
    description VARCHAR(255) DEFAULT NULL,
    updated_by BIGINT UNSIGNED,
    updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
    CONSTRAINT fk_email_templates_updated_by FOREIGN KEY (updated_by) REFERENCES users(id) ON DELETE SET NULL
) ENGINE=InnoDB;

-- Standard-Templates einfügen (INSERT IGNORE: bestehende angepasste Vorlagen werden nicht überschrieben)
INSERT IGNORE INTO email_templates (template_key, subject, body_html, description) VALUES
('page_changed', 'Seite geändert: {{page_title}}',
 '<p>Hallo {{user_name}},</p><p>die Seite <strong>{{page_title}}</strong> im Themengebiet <strong>{{topic_name}}</strong> wurde von <strong>{{editor_name}}</strong> bearbeitet.</p><p>{{change_reason}}</p><p><a href="{{page_url}}">Seite ansehen</a></p><p>Freundliche Gr&uuml;&szlig;e<br>{{app_name}}</p>',
 'Wird gesendet wenn eine Seite geändert wird, für die man verantwortlich ist'),
('review_due', 'Review fällig: {{page_title}}',
 '<p>Hallo {{user_name}},</p><p>f&uuml;r die Seite <strong>{{page_title}}</strong> im Themengebiet <strong>{{topic_name}}</strong> ist heute ein Review f&auml;llig.</p><p><a href="{{page_url}}">Seite pr&uuml;fen</a></p><p>Freundliche Gr&uuml;&szlig;e<br>{{app_name}}</p>',
 'Wird gesendet wenn ein Review fällig ist');

-- Benachrichtigungs-Queue
CREATE TABLE IF NOT EXISTS notification_queue (
    id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    user_id BIGINT UNSIGNED NOT NULL,
    template_key VARCHAR(100) NOT NULL,
    variables_json TEXT NOT NULL,
    status ENUM('pending', 'sent', 'failed') DEFAULT 'pending',
    error_message TEXT DEFAULT NULL,
    created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
    sent_at DATETIME DEFAULT NULL,
    CONSTRAINT fk_notification_queue_user FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
) ENGINE=InnoDB;

-- SMTP-Einstellungen werden in runtime.php gespeichert (nicht in der DB)

-- Migration: 016_page_ansprechpartner.sql
-- Ansprechpartner (Kontaktperson) fuer Seiten
-- Jede Seite kann entweder eine Firma ODER einen Kontakt als Ansprechpartner haben

ALTER TABLE pages
    ADD COLUMN ansprechpartner_type ENUM('contact','company') NULL DEFAULT NULL,
    ADD COLUMN ansprechpartner_id BIGINT UNSIGNED NULL DEFAULT NULL;

ALTER TABLE pages
    ADD INDEX idx_pages_ansprechpartner (ansprechpartner_type, ansprechpartner_id);

-- Migration: 017_user_notifications.sql
-- Migration: In-App Benachrichtigungen
-- Erstellt am: 2026-02-28

CREATE TABLE IF NOT EXISTS user_notifications (
    id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
    user_id BIGINT UNSIGNED NOT NULL,
    type VARCHAR(50) NOT NULL DEFAULT 'info',
    title VARCHAR(255) NOT NULL,
    message TEXT NOT NULL,
    link VARCHAR(500) DEFAULT NULL,
    is_read BOOLEAN NOT NULL DEFAULT FALSE,
    created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
    read_at DATETIME DEFAULT NULL,
    PRIMARY KEY (id),
    INDEX idx_notif_user_unread (user_id, is_read, created_at),
    INDEX idx_notif_user_created (user_id, created_at),
    CONSTRAINT fk_notif_user FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

-- Migration: 018_notification_templates_and_tags.sql
-- Migration: Notification-Vorlagen + Tags-System
-- Erstellt am: 2026-02-28

-- ============================================================
-- In-App Notification Vorlagen
-- ============================================================

CREATE TABLE IF NOT EXISTS notification_templates (
    id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
    template_key VARCHAR(100) NOT NULL UNIQUE,
    title_template VARCHAR(255) NOT NULL,
    message_template TEXT NOT NULL,
    icon VARCHAR(20) NOT NULL DEFAULT 'info',
    type VARCHAR(50) NOT NULL DEFAULT 'info',
    description VARCHAR(255) DEFAULT NULL,
    is_enabled BOOLEAN NOT NULL DEFAULT TRUE,
    updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
    PRIMARY KEY (id),
    INDEX idx_notif_tpl_key (template_key)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

-- Standard-Vorlagen einfuegen
INSERT INTO notification_templates (template_key, title_template, message_template, icon, type, description) VALUES
('page_changed', 'Seite bearbeitet: {{page_title}}', '{{editor_name}} hat die Seite "{{page_title}}" bearbeitet.{{change_reason_suffix}}', '📝', 'page_changed', 'Benachrichtigung wenn eine Seite bearbeitet wird, fuer die man verantwortlich ist'),
('review_due', 'Review faellig: {{page_title}}', 'Die Seite "{{page_title}}" in "{{topic_name}}" muss heute geprueft werden.', '⚠', 'review_due', 'Benachrichtigung wenn ein Review-Termin faellig ist'),
('page_created', 'Neue Seite: {{page_title}}', '{{creator_name}} hat eine neue Seite "{{page_title}}" in "{{topic_name}}" erstellt.', '📄', 'page_created', 'Benachrichtigung wenn eine neue Seite in einem eigenen Themengebiet erstellt wird'),
('page_archived', 'Seite archiviert: {{page_title}}', '{{user_name}} hat die Seite "{{page_title}}" archiviert.', '🗃', 'page_archived', 'Benachrichtigung wenn eine Seite archiviert wird, fuer die man verantwortlich ist'),
('responsibility_assigned', 'Neue Verantwortlichkeit: {{page_title}}', 'Sie wurden als {{role}} fuer die Seite "{{page_title}}" eingetragen.', '👤', 'responsibility_assigned', 'Benachrichtigung wenn man als Verantwortlicher fuer eine Seite eingetragen wird'),
('review_overdue', 'Review ueberfaellig: {{page_title}}', 'Die Seite "{{page_title}}" hat den Review-Termin ueberschritten ({{review_date}}).', '🔴', 'review_overdue', 'Benachrichtigung wenn ein Review-Termin ueberfaellig ist'),
('order_status_changed', 'Bestellung aktualisiert: {{order_title}}', 'Die Bestellung "{{order_title}}" hat einen neuen Status: {{new_status}}.', '📦', 'order_status_changed', 'Benachrichtigung wenn sich der Status einer Bestellung aendert'),
('system_announcement', '{{title}}', '{{message}}', 'ℹ', 'system', 'System-Ankuendigungen vom Administrator')
ON DUPLICATE KEY UPDATE template_key = template_key;

-- ============================================================
-- Tags-System
-- ============================================================

CREATE TABLE IF NOT EXISTS tags (
    id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
    name VARCHAR(100) NOT NULL,
    slug VARCHAR(120) NOT NULL,
    color VARCHAR(7) NOT NULL DEFAULT '#6b7280',
    created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
    PRIMARY KEY (id),
    UNIQUE KEY uk_tag_name (name),
    UNIQUE KEY uk_tag_slug (slug),
    INDEX idx_tag_name (name)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

CREATE TABLE IF NOT EXISTS page_tags (
    page_id BIGINT UNSIGNED NOT NULL,
    tag_id BIGINT UNSIGNED NOT NULL,
    created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
    PRIMARY KEY (page_id, tag_id),
    INDEX idx_pt_tag (tag_id),
    CONSTRAINT fk_pt_page FOREIGN KEY (page_id) REFERENCES pages(id) ON DELETE CASCADE,
    CONSTRAINT fk_pt_tag FOREIGN KEY (tag_id) REFERENCES tags(id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

-- Standard-Tags einfuegen
INSERT INTO tags (name, slug, color) VALUES
('Wichtig', 'wichtig', '#ef4444'),
('In Arbeit', 'in-arbeit', '#f59e0b'),
('Veraltet', 'veraltet', '#9ca3af'),
('Best Practice', 'best-practice', '#22c55e'),
('SOP', 'sop', '#8b5cf6'),
('Runbook', 'runbook', '#3b82f6'),
('Architektur', 'architektur', '#06b6d4'),
('Sicherheit', 'sicherheit', '#dc2626'),
('Netzwerk', 'netzwerk', '#0891b2'),
('Monitoring', 'monitoring', '#7c3aed'),
('Backup', 'backup', '#059669'),
('FAQ', 'faq', '#d97706')
ON DUPLICATE KEY UPDATE name = name;

-- Migration: 019_page_templates.sql
-- Migration: Seiten-Vorlagen System
-- Erstellt am: 2026-02-28

CREATE TABLE IF NOT EXISTS page_templates (
    id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
    name VARCHAR(255) NOT NULL,
    slug VARCHAR(255) NOT NULL,
    description TEXT DEFAULT NULL,
    content_html LONGTEXT NOT NULL,
    category VARCHAR(100) NOT NULL DEFAULT 'Allgemein',
    icon VARCHAR(20) NOT NULL DEFAULT '📄',
    sort_order INT NOT NULL DEFAULT 0,
    is_active BOOLEAN NOT NULL DEFAULT TRUE,
    created_by BIGINT UNSIGNED DEFAULT NULL,
    created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
    updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
    PRIMARY KEY (id),
    UNIQUE KEY uk_pt_slug (slug),
    INDEX idx_pt_category (category),
    INDEX idx_pt_active (is_active)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

-- Standard-Vorlagen einfuegen
INSERT INTO page_templates (name, slug, description, content_html, category, icon, sort_order) VALUES
(
    'Server-Dokumentation',
    'server-dokumentation',
    'Vorlage fuer die Dokumentation eines Servers (physisch oder virtuell)',
    '<h2>Server&uuml;bersicht</h2>
<table style="width:100%;border-collapse:collapse;">
<thead><tr><th style="border:1px solid #d1d5db;padding:0.75rem;background:#f3f4f6;text-align:left;">Eigenschaft</th><th style="border:1px solid #d1d5db;padding:0.75rem;background:#f3f4f6;text-align:left;">Wert</th></tr></thead>
<tbody>
<tr><td style="border:1px solid #d1d5db;padding:0.75rem;">Hostname</td><td style="border:1px solid #d1d5db;padding:0.75rem;"></td></tr>
<tr><td style="border:1px solid #d1d5db;padding:0.75rem;">IP-Adresse(n)</td><td style="border:1px solid #d1d5db;padding:0.75rem;"></td></tr>
<tr><td style="border:1px solid #d1d5db;padding:0.75rem;">Betriebssystem</td><td style="border:1px solid #d1d5db;padding:0.75rem;"></td></tr>
<tr><td style="border:1px solid #d1d5db;padding:0.75rem;">Typ (physisch/VM/Container)</td><td style="border:1px solid #d1d5db;padding:0.75rem;"></td></tr>
<tr><td style="border:1px solid #d1d5db;padding:0.75rem;">Standort / Cluster</td><td style="border:1px solid #d1d5db;padding:0.75rem;"></td></tr>
<tr><td style="border:1px solid #d1d5db;padding:0.75rem;">CPU / RAM / Disk</td><td style="border:1px solid #d1d5db;padding:0.75rem;"></td></tr>
<tr><td style="border:1px solid #d1d5db;padding:0.75rem;">Inbetriebnahme</td><td style="border:1px solid #d1d5db;padding:0.75rem;"></td></tr>
</tbody>
</table>

<h2>Zweck &amp; Dienste</h2>
<p>Beschreibung des Hauptzwecks und der laufenden Dienste:</p>
<ul><li>Dienst 1 (Port)</li><li>Dienst 2 (Port)</li></ul>

<h2>Netzwerk</h2>
<table style="width:100%;border-collapse:collapse;">
<thead><tr><th style="border:1px solid #d1d5db;padding:0.75rem;background:#f3f4f6;">Interface</th><th style="border:1px solid #d1d5db;padding:0.75rem;background:#f3f4f6;">IP/Subnetz</th><th style="border:1px solid #d1d5db;padding:0.75rem;background:#f3f4f6;">VLAN</th><th style="border:1px solid #d1d5db;padding:0.75rem;background:#f3f4f6;">Zweck</th></tr></thead>
<tbody><tr><td style="border:1px solid #d1d5db;padding:0.75rem;">eth0</td><td style="border:1px solid #d1d5db;padding:0.75rem;"></td><td style="border:1px solid #d1d5db;padding:0.75rem;"></td><td style="border:1px solid #d1d5db;padding:0.75rem;"></td></tr></tbody>
</table>

<h2>Backup &amp; Wiederherstellung</h2>
<p><strong>Backup-Methode:</strong> </p>
<p><strong>Backup-Zeitplan:</strong> </p>
<p><strong>Aufbewahrung:</strong> </p>
<p><strong>Wiederherstellungsprozedur:</strong> </p>

<h2>Monitoring</h2>
<p><strong>Monitoring-Tool:</strong> </p>
<p><strong>Alerting:</strong> </p>

<h2>Wartung &amp; Updates</h2>
<p><strong>Patch-Zyklus:</strong> </p>
<p><strong>Wartungsfenster:</strong> </p>

<h2>Notizen</h2>
<p>Sonstige Hinweise und Bemerkungen.</p>',
    'Infrastruktur',
    '🖥',
    1
),
(
    'Software-Dokumentation',
    'software-dokumentation',
    'Vorlage fuer die Dokumentation einer Software oder Anwendung',
    '<h2>Allgemein</h2>
<table style="width:100%;border-collapse:collapse;">
<thead><tr><th style="border:1px solid #d1d5db;padding:0.75rem;background:#f3f4f6;text-align:left;">Eigenschaft</th><th style="border:1px solid #d1d5db;padding:0.75rem;background:#f3f4f6;text-align:left;">Wert</th></tr></thead>
<tbody>
<tr><td style="border:1px solid #d1d5db;padding:0.75rem;">Name</td><td style="border:1px solid #d1d5db;padding:0.75rem;"></td></tr>
<tr><td style="border:1px solid #d1d5db;padding:0.75rem;">Version</td><td style="border:1px solid #d1d5db;padding:0.75rem;"></td></tr>
<tr><td style="border:1px solid #d1d5db;padding:0.75rem;">Hersteller</td><td style="border:1px solid #d1d5db;padding:0.75rem;"></td></tr>
<tr><td style="border:1px solid #d1d5db;padding:0.75rem;">Lizenztyp</td><td style="border:1px solid #d1d5db;padding:0.75rem;"></td></tr>
<tr><td style="border:1px solid #d1d5db;padding:0.75rem;">Lizenz bis</td><td style="border:1px solid #d1d5db;padding:0.75rem;"></td></tr>
<tr><td style="border:1px solid #d1d5db;padding:0.75rem;">URL / Link</td><td style="border:1px solid #d1d5db;padding:0.75rem;"></td></tr>
</tbody>
</table>

<h2>Beschreibung</h2>
<p>Wof&uuml;r wird die Software eingesetzt? Welche Gesch&auml;ftsprozesse unterst&uuml;tzt sie?</p>

<h2>Installation &amp; Konfiguration</h2>
<h3>Systemanforderungen</h3>
<ul><li>Betriebssystem:</li><li>Speicher:</li><li>Abh&auml;ngigkeiten:</li></ul>

<h3>Installationsschritte</h3>
<ol><li>Schritt 1</li><li>Schritt 2</li></ol>

<h3>Konfiguration</h3>
<p>Wichtige Konfigurationsdateien und Einstellungen:</p>
<pre><code># Beispiel-Konfiguration</code></pre>

<h2>Benutzer &amp; Berechtigungen</h2>
<p>Welche Benutzergruppen nutzen die Software und welche Berechtigungen haben sie?</p>

<h2>Schnittstellen</h2>
<p>Welche Systeme sind angebunden? Welche APIs werden genutzt?</p>

<h2>Bekannte Probleme</h2>
<ul><li>Problem und Workaround</li></ul>

<h2>Support &amp; Kontakt</h2>
<p><strong>Interner Ansprechpartner:</strong> </p>
<p><strong>Hersteller-Support:</strong> </p>',
    'Software',
    '💿',
    2
),
(
    'Netzwerk-Dokumentation',
    'netzwerk-dokumentation',
    'Vorlage fuer die Dokumentation von Netzwerkkomponenten (Switches, Router, Firewalls)',
    '<h2>&Uuml;bersicht</h2>
<table style="width:100%;border-collapse:collapse;">
<thead><tr><th style="border:1px solid #d1d5db;padding:0.75rem;background:#f3f4f6;text-align:left;">Eigenschaft</th><th style="border:1px solid #d1d5db;padding:0.75rem;background:#f3f4f6;text-align:left;">Wert</th></tr></thead>
<tbody>
<tr><td style="border:1px solid #d1d5db;padding:0.75rem;">Ger&auml;tename</td><td style="border:1px solid #d1d5db;padding:0.75rem;"></td></tr>
<tr><td style="border:1px solid #d1d5db;padding:0.75rem;">Typ (Switch/Router/Firewall/AP)</td><td style="border:1px solid #d1d5db;padding:0.75rem;"></td></tr>
<tr><td style="border:1px solid #d1d5db;padding:0.75rem;">Hersteller &amp; Modell</td><td style="border:1px solid #d1d5db;padding:0.75rem;"></td></tr>
<tr><td style="border:1px solid #d1d5db;padding:0.75rem;">Firmware-Version</td><td style="border:1px solid #d1d5db;padding:0.75rem;"></td></tr>
<tr><td style="border:1px solid #d1d5db;padding:0.75rem;">Management-IP</td><td style="border:1px solid #d1d5db;padding:0.75rem;"></td></tr>
<tr><td style="border:1px solid #d1d5db;padding:0.75rem;">Standort / Rack</td><td style="border:1px solid #d1d5db;padding:0.75rem;"></td></tr>
<tr><td style="border:1px solid #d1d5db;padding:0.75rem;">Seriennummer</td><td style="border:1px solid #d1d5db;padding:0.75rem;"></td></tr>
</tbody>
</table>

<h2>VLAN-Konfiguration</h2>
<table style="width:100%;border-collapse:collapse;">
<thead><tr><th style="border:1px solid #d1d5db;padding:0.75rem;background:#f3f4f6;">VLAN ID</th><th style="border:1px solid #d1d5db;padding:0.75rem;background:#f3f4f6;">Name</th><th style="border:1px solid #d1d5db;padding:0.75rem;background:#f3f4f6;">Subnetz</th><th style="border:1px solid #d1d5db;padding:0.75rem;background:#f3f4f6;">Beschreibung</th></tr></thead>
<tbody><tr><td style="border:1px solid #d1d5db;padding:0.75rem;">1</td><td style="border:1px solid #d1d5db;padding:0.75rem;">Default</td><td style="border:1px solid #d1d5db;padding:0.75rem;"></td><td style="border:1px solid #d1d5db;padding:0.75rem;"></td></tr></tbody>
</table>

<h2>Port-Belegung</h2>
<table style="width:100%;border-collapse:collapse;">
<thead><tr><th style="border:1px solid #d1d5db;padding:0.75rem;background:#f3f4f6;">Port</th><th style="border:1px solid #d1d5db;padding:0.75rem;background:#f3f4f6;">Verbindung zu</th><th style="border:1px solid #d1d5db;padding:0.75rem;background:#f3f4f6;">VLAN</th><th style="border:1px solid #d1d5db;padding:0.75rem;background:#f3f4f6;">Modus</th></tr></thead>
<tbody><tr><td style="border:1px solid #d1d5db;padding:0.75rem;">1</td><td style="border:1px solid #d1d5db;padding:0.75rem;"></td><td style="border:1px solid #d1d5db;padding:0.75rem;"></td><td style="border:1px solid #d1d5db;padding:0.75rem;">Access/Trunk</td></tr></tbody>
</table>

<h2>Routing</h2>
<p>Statische Routen und Routing-Protokolle:</p>

<h2>Firewall-Regeln</h2>
<p>Relevante Regeln und Policies:</p>

<h2>Notizen</h2>
<p>Sonstige Hinweise.</p>',
    'Infrastruktur',
    '🌐',
    3
),
(
    'Standard-Arbeitsanweisung (SOP)',
    'standard-arbeitsanweisung',
    'Vorlage fuer Standard Operating Procedures (SOPs)',
    '<h2>Zweck</h2>
<p>Beschreiben Sie den Zweck dieses Verfahrens.</p>

<h2>Geltungsbereich</h2>
<p>F&uuml;r wen gilt diese Anweisung? Welche Systeme/Prozesse sind betroffen?</p>

<h2>Voraussetzungen</h2>
<ul>
<li>Berechtigung / Zugang zu:</li>
<li>Werkzeug / Software:</li>
<li>Dokumentation:</li>
</ul>

<h2>Durchf&uuml;hrung</h2>
<h3>Schritt 1: Vorbereitung</h3>
<p>Beschreibung der Vorbereitung.</p>

<h3>Schritt 2: Hauptaktion</h3>
<ol>
<li>Teilschritt 1</li>
<li>Teilschritt 2</li>
<li>Teilschritt 3</li>
</ol>

<h3>Schritt 3: Nachbereitung</h3>
<p>Beschreibung der Nachbereitung und Verifizierung.</p>

<h2>Fehlerbehandlung</h2>
<table style="width:100%;border-collapse:collapse;">
<thead><tr><th style="border:1px solid #d1d5db;padding:0.75rem;background:#f3f4f6;">Problem</th><th style="border:1px solid #d1d5db;padding:0.75rem;background:#f3f4f6;">L&ouml;sung</th></tr></thead>
<tbody><tr><td style="border:1px solid #d1d5db;padding:0.75rem;">Fehler X tritt auf</td><td style="border:1px solid #d1d5db;padding:0.75rem;">Aktion Y durchf&uuml;hren</td></tr></tbody>
</table>

<h2>Revisions-Historie</h2>
<table style="width:100%;border-collapse:collapse;">
<thead><tr><th style="border:1px solid #d1d5db;padding:0.75rem;background:#f3f4f6;">Datum</th><th style="border:1px solid #d1d5db;padding:0.75rem;background:#f3f4f6;">Version</th><th style="border:1px solid #d1d5db;padding:0.75rem;background:#f3f4f6;">&Auml;nderung</th><th style="border:1px solid #d1d5db;padding:0.75rem;background:#f3f4f6;">Autor</th></tr></thead>
<tbody><tr><td style="border:1px solid #d1d5db;padding:0.75rem;"></td><td style="border:1px solid #d1d5db;padding:0.75rem;">1.0</td><td style="border:1px solid #d1d5db;padding:0.75rem;">Erstversion</td><td style="border:1px solid #d1d5db;padding:0.75rem;"></td></tr></tbody>
</table>',
    'Prozesse',
    '📋',
    4
),
(
    'Runbook / Notfallplan',
    'runbook-notfallplan',
    'Vorlage fuer Runbooks und Notfallplaene bei Stoerungen',
    '<blockquote><p><strong>Wichtig:</strong> Dieses Runbook beschreibt das Vorgehen bei einem Ausfall oder einer St&ouml;rung. Bitte befolgen Sie die Schritte in der angegebenen Reihenfolge.</p></blockquote>

<h2>St&ouml;rungsbeschreibung</h2>
<p><strong>Betroffenes System:</strong> </p>
<p><strong>Symptome:</strong> </p>
<p><strong>Auswirkung:</strong> </p>
<p><strong>Priorit&auml;t:</strong> Hoch / Mittel / Niedrig</p>

<h2>Sofortmassnahmen</h2>
<ol>
<li>Pr&uuml;fen, ob das Problem bekannt ist (Monitoring-Dashboard checken)</li>
<li>Betroffene Benutzer informieren</li>
<li>Eskalation starten, falls n&ouml;tig</li>
</ol>

<h2>Diagnose</h2>
<h3>Pr&uuml;fschritte</h3>
<ol>
<li><strong>Dienststatus pr&uuml;fen:</strong><br><pre><code>systemctl status [service-name]</code></pre></li>
<li><strong>Logs pr&uuml;fen:</strong><br><pre><code>journalctl -u [service-name] --since &quot;1 hour ago&quot;</code></pre></li>
<li><strong>Netzwerk pr&uuml;fen:</strong><br><pre><code>ping [hostname]
telnet [hostname] [port]</code></pre></li>
</ol>

<h2>Behebung</h2>
<h3>Szenario A: Dienst gestoppt</h3>
<ol>
<li>Dienst neu starten</li>
<li>Logs auf Fehler pr&uuml;fen</li>
<li>Funktionstest durchf&uuml;hren</li>
</ol>

<h3>Szenario B: Ressourcen ersch&ouml;pft</h3>
<ol>
<li>Speicher / CPU Auslastung pr&uuml;fen</li>
<li>Tempor&auml;re Dateien bereinigen</li>
<li>Ggf. Ressourcen erweitern</li>
</ol>

<h2>Eskalation</h2>
<table style="width:100%;border-collapse:collapse;">
<thead><tr><th style="border:1px solid #d1d5db;padding:0.75rem;background:#f3f4f6;">Stufe</th><th style="border:1px solid #d1d5db;padding:0.75rem;background:#f3f4f6;">Kontakt</th><th style="border:1px solid #d1d5db;padding:0.75rem;background:#f3f4f6;">Telefon</th><th style="border:1px solid #d1d5db;padding:0.75rem;background:#f3f4f6;">Wann</th></tr></thead>
<tbody>
<tr><td style="border:1px solid #d1d5db;padding:0.75rem;">1</td><td style="border:1px solid #d1d5db;padding:0.75rem;">IT-Team</td><td style="border:1px solid #d1d5db;padding:0.75rem;"></td><td style="border:1px solid #d1d5db;padding:0.75rem;">Sofort</td></tr>
<tr><td style="border:1px solid #d1d5db;padding:0.75rem;">2</td><td style="border:1px solid #d1d5db;padding:0.75rem;">IT-Leitung</td><td style="border:1px solid #d1d5db;padding:0.75rem;"></td><td style="border:1px solid #d1d5db;padding:0.75rem;">Nach 30 Min</td></tr>
<tr><td style="border:1px solid #d1d5db;padding:0.75rem;">3</td><td style="border:1px solid #d1d5db;padding:0.75rem;">Gesch&auml;ftsf&uuml;hrung</td><td style="border:1px solid #d1d5db;padding:0.75rem;"></td><td style="border:1px solid #d1d5db;padding:0.75rem;">Nach 1 Std</td></tr>
</tbody>
</table>

<h2>Nachbereitung</h2>
<ul>
<li>Root Cause Analysis dokumentieren</li>
<li>Pr&auml;ventivmassnahmen festlegen</li>
<li>Runbook aktualisieren</li>
</ul>',
    'Prozesse',
    '🚨',
    5
),
(
    'Meeting-Protokoll',
    'meeting-protokoll',
    'Vorlage fuer Meeting-Protokolle und Besprechungsnotizen',
    '<h2>Meeting-Informationen</h2>
<table style="width:100%;border-collapse:collapse;">
<thead><tr><th style="border:1px solid #d1d5db;padding:0.75rem;background:#f3f4f6;text-align:left;">Feld</th><th style="border:1px solid #d1d5db;padding:0.75rem;background:#f3f4f6;text-align:left;">Wert</th></tr></thead>
<tbody>
<tr><td style="border:1px solid #d1d5db;padding:0.75rem;"><strong>Datum</strong></td><td style="border:1px solid #d1d5db;padding:0.75rem;"></td></tr>
<tr><td style="border:1px solid #d1d5db;padding:0.75rem;"><strong>Uhrzeit</strong></td><td style="border:1px solid #d1d5db;padding:0.75rem;"></td></tr>
<tr><td style="border:1px solid #d1d5db;padding:0.75rem;"><strong>Ort / Link</strong></td><td style="border:1px solid #d1d5db;padding:0.75rem;"></td></tr>
<tr><td style="border:1px solid #d1d5db;padding:0.75rem;"><strong>Teilnehmer</strong></td><td style="border:1px solid #d1d5db;padding:0.75rem;"></td></tr>
<tr><td style="border:1px solid #d1d5db;padding:0.75rem;"><strong>Protokollant</strong></td><td style="border:1px solid #d1d5db;padding:0.75rem;"></td></tr>
</tbody>
</table>

<h2>Agenda</h2>
<ol>
<li>Punkt 1</li>
<li>Punkt 2</li>
<li>Punkt 3</li>
</ol>

<h2>Besprechungspunkte</h2>
<h3>1. Punkt 1</h3>
<p>Zusammenfassung der Diskussion.</p>

<h3>2. Punkt 2</h3>
<p>Zusammenfassung der Diskussion.</p>

<h2>Beschl&uuml;sse &amp; Aufgaben</h2>
<table style="width:100%;border-collapse:collapse;">
<thead><tr><th style="border:1px solid #d1d5db;padding:0.75rem;background:#f3f4f6;">Aufgabe</th><th style="border:1px solid #d1d5db;padding:0.75rem;background:#f3f4f6;">Verantwortlich</th><th style="border:1px solid #d1d5db;padding:0.75rem;background:#f3f4f6;">Frist</th><th style="border:1px solid #d1d5db;padding:0.75rem;background:#f3f4f6;">Status</th></tr></thead>
<tbody>
<tr><td style="border:1px solid #d1d5db;padding:0.75rem;"></td><td style="border:1px solid #d1d5db;padding:0.75rem;"></td><td style="border:1px solid #d1d5db;padding:0.75rem;"></td><td style="border:1px solid #d1d5db;padding:0.75rem;">Offen</td></tr>
</tbody>
</table>

<h2>N&auml;chster Termin</h2>
<p><strong>Datum:</strong> </p>
<p><strong>Themen:</strong> </p>',
    'Allgemein',
    '📝',
    6
),
(
    'Leere Seite',
    'leere-seite',
    'Leere Seite ohne vordefinierten Inhalt',
    '',
    'Allgemein',
    '📄',
    0
)
ON DUPLICATE KEY UPDATE slug = slug;

-- Migration: 020_contract_management.sql
-- Vertragsmanagement-System
-- Vertraege, Dateien fuer Firmen und Vertraege, Kuendigungsfrist-Benachrichtigungen

-- Capability fuer Vertragsmanagement
-- (Kategorie 'contract' im Singular – das historische 'contracts' kollidierte
-- mit spaeteren Statements und der Kategorie-Konvention der uebrigen Module.)
INSERT IGNORE INTO capabilities (name, description, category) VALUES
('view_contracts', 'Vertraege ansehen', 'contract'),
('manage_contracts', 'Vertraege erstellen, bearbeiten und loeschen', 'contract');

-- Admin-Rolle bekommt alle Vertrags-Capabilities
-- (Rollenname korrigiert: die globale Admin-Rolle heisst "System Admin", nicht
-- "Administrator". Die vollstaendige Admin-Vorbelegung erfolgt zusaetzlich in
-- Migration 043_rbac_module_capabilities.sql.)
INSERT IGNORE INTO role_capabilities (role_id, capability_id)
SELECT r.id, c.id FROM roles r, capabilities c
WHERE r.name = 'System Admin' AND c.name IN ('view_contracts', 'manage_contracts');

-- Vertraege
CREATE TABLE IF NOT EXISTS `contracts` (
    `id` BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    `company_id` BIGINT UNSIGNED NOT NULL,
    `contract_number` VARCHAR(100) DEFAULT NULL,
    `title` VARCHAR(255) NOT NULL,
    `description` TEXT DEFAULT NULL,
    `start_date` DATE DEFAULT NULL,
    `end_date` DATE DEFAULT NULL,
    `cancellation_period_months` INT UNSIGNED DEFAULT NULL,
    `cancellation_notice_date` DATE DEFAULT NULL,
    `auto_renew` TINYINT(1) NOT NULL DEFAULT 0,
    `cost_amount` DECIMAL(12,2) DEFAULT NULL,
    `cost_interval` ENUM('monthly', 'yearly') DEFAULT 'monthly',
    `status` ENUM('active', 'expiring', 'expired', 'cancelled') NOT NULL DEFAULT 'active',
    `notes` TEXT DEFAULT NULL,
    `created_by` BIGINT UNSIGNED DEFAULT NULL,
    `created_at` DATETIME DEFAULT CURRENT_TIMESTAMP,
    `updated_at` DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
    INDEX idx_contracts_company (`company_id`),
    INDEX idx_contracts_status (`status`),
    INDEX idx_contracts_end_date (`end_date`),
    INDEX idx_contracts_cancellation (`cancellation_notice_date`),
    FOREIGN KEY (`company_id`) REFERENCES `companies`(`id`) ON DELETE CASCADE,
    FOREIGN KEY (`created_by`) REFERENCES `users`(`id`) ON DELETE SET NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

-- Dateien fuer Firmen
CREATE TABLE IF NOT EXISTS `company_files` (
    `id` BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    `company_id` BIGINT UNSIGNED NOT NULL,
    `original_filename` VARCHAR(255) NOT NULL,
    `stored_filename` VARCHAR(255) NOT NULL,
    `file_size` BIGINT UNSIGNED DEFAULT 0,
    `mime_type` VARCHAR(100) DEFAULT NULL,
    `uploaded_by` BIGINT UNSIGNED DEFAULT NULL,
    `created_at` DATETIME DEFAULT CURRENT_TIMESTAMP,
    INDEX idx_company_files_company (`company_id`),
    FOREIGN KEY (`company_id`) REFERENCES `companies`(`id`) ON DELETE CASCADE,
    FOREIGN KEY (`uploaded_by`) REFERENCES `users`(`id`) ON DELETE SET NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

-- Dateien fuer Vertraege
CREATE TABLE IF NOT EXISTS `contract_files` (
    `id` BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    `contract_id` BIGINT UNSIGNED NOT NULL,
    `original_filename` VARCHAR(255) NOT NULL,
    `stored_filename` VARCHAR(255) NOT NULL,
    `file_size` BIGINT UNSIGNED DEFAULT 0,
    `mime_type` VARCHAR(100) DEFAULT NULL,
    `uploaded_by` BIGINT UNSIGNED DEFAULT NULL,
    `created_at` DATETIME DEFAULT CURRENT_TIMESTAMP,
    INDEX idx_contract_files_contract (`contract_id`),
    FOREIGN KEY (`contract_id`) REFERENCES `contracts`(`id`) ON DELETE CASCADE,
    FOREIGN KEY (`uploaded_by`) REFERENCES `users`(`id`) ON DELETE SET NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

-- Tracking fuer gesendete Kuendigungserinnerungen (verhindert doppeltes Senden)
CREATE TABLE IF NOT EXISTS `contract_notifications_sent` (
    `id` BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    `contract_id` BIGINT UNSIGNED NOT NULL,
    `notification_type` VARCHAR(50) NOT NULL DEFAULT 'cancellation_reminder',
    `sent_at` DATETIME DEFAULT CURRENT_TIMESTAMP,
    UNIQUE KEY uq_contract_notif (`contract_id`, `notification_type`),
    FOREIGN KEY (`contract_id`) REFERENCES `contracts`(`id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

-- Migration: 021_contract_enhancements.sql
-- Vertragsmanagement Erweiterungen
-- Verantwortlicher, Erinnerungsdatum, Benachrichtigungsvorlage

-- Verantwortlicher fuer Vertrag
ALTER TABLE `contracts` ADD COLUMN `responsible_user_id` BIGINT UNSIGNED DEFAULT NULL AFTER `created_by`;
ALTER TABLE `contracts` ADD FOREIGN KEY (`responsible_user_id`) REFERENCES `users`(`id`) ON DELETE SET NULL;
ALTER TABLE `contracts` ADD INDEX idx_contracts_responsible (`responsible_user_id`);

-- Erinnerungsdatum (1 Monat vor Kuendigungsfrist)
ALTER TABLE `contracts` ADD COLUMN `reminder_date` DATE DEFAULT NULL AFTER `cancellation_notice_date`;
ALTER TABLE `contracts` ADD INDEX idx_contracts_reminder (`reminder_date`);

-- Benachrichtigungsvorlage fuer Vertragserinnerung
INSERT IGNORE INTO notification_templates (template_key, title_template, message_template, icon, type, description) VALUES
('contract_expiring', 'Vertrag: Kuendigungsfrist beachten - {{contract_title}}', 'Der Vertrag "{{contract_title}}" ({{company_name}}) muss bis {{cancellation_date}} gekuendigt werden. Vertragsende: {{end_date}}.', '⚠', 'warning', 'Erinnerung wenn die Kuendigungsfrist eines Vertrags naeher rueckt');

-- Migration: 022_cronjob_tracking_and_timezone.sql
-- Cronjob-Tracking und Zeitzone

-- Cronjob-Ausfuehrungen protokollieren
CREATE TABLE IF NOT EXISTS `cronjob_runs` (
    `id` BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    `job_name` VARCHAR(100) NOT NULL,
    `started_at` DATETIME NOT NULL,
    `finished_at` DATETIME DEFAULT NULL,
    `status` ENUM('running', 'success', 'error') NOT NULL DEFAULT 'running',
    `message` TEXT DEFAULT NULL,
    `items_processed` INT UNSIGNED DEFAULT 0,
    INDEX idx_cronjob_name (`job_name`),
    INDEX idx_cronjob_started (`started_at`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

-- Migration: 023_fix_contract_enums_and_capabilities.sql
-- Fix: 'contract' als Kategorie fuer audit_log und capabilities hinzufuegen
-- Die Migration 020 konnte Capabilities nicht einfuegen weil 'contract(s)' nicht im ENUM war
-- Hinweis: Enthaelt alle bekannten Werte (inkl. spaeterer Migrationen),
-- damit ein Rerun-All keine bestehenden Eintraege abschneidet (Data truncated 1265).

-- 1. audit_log.action_category um 'contract' erweitern (und alle spaeten Werte behalten)
ALTER TABLE `audit_log` MODIFY COLUMN `action_category` ENUM(
    'auth',
    'permission',
    'content',
    'file',
    'export',
    'admin',
    'security',
    'contact',
    'order',
    'service',
    'contract',
    'loan',
    'ipam',
    'vault',
    'tickets',
    'ssl',
    'kanban'
) NOT NULL;

-- 2. (entfernt) Historischer ENUM-Umbau von capabilities.category.
--    Die Spalte bleibt VARCHAR(50): Der ENUM-Umbau schlug im strikten SQL-Mode
--    fehl ("Data truncated ... at row 26"), weil bereits geseedete Kategorien
--    nicht in der ENUM-Liste standen, und wird weiter unten ohnehin wieder
--    nach VARCHAR(50) zurueckgebaut.

-- 3. Capabilities einfuegen (jetzt mit korrekter Kategorie 'contract')
INSERT IGNORE INTO capabilities (name, description, category, risk_level) VALUES
('view_contracts', 'Vertraege ansehen', 'contract', 'low'),
('manage_contracts', 'Vertraege erstellen, bearbeiten und loeschen', 'contract', 'medium');

-- 4. Admin-Rolle bekommt alle Vertrags-Capabilities
INSERT IGNORE INTO role_capabilities (role_id, capability_id)
SELECT r.id, c.id FROM roles r, capabilities c
WHERE r.is_admin = TRUE AND c.name IN ('view_contracts', 'manage_contracts');

-- Migration: 024_contract_payment_enhancements.sql
-- Migration 024: Contract Payment Enhancements
-- Adds: quarterly billing interval, first payment date, tax rate

-- Extend cost_interval enum to include quarterly
ALTER TABLE `contracts`
    MODIFY COLUMN `cost_interval` ENUM('monthly', 'quarterly', 'yearly') NOT NULL DEFAULT 'monthly';

-- Add first payment date (for next-payment calculation)
ALTER TABLE `contracts`
    ADD COLUMN `first_payment_date` DATE NULL DEFAULT NULL AFTER `cost_interval`;

-- Add tax rate in percent (e.g. 19.00 for 19% MwSt)
ALTER TABLE `contracts`
    ADD COLUMN `tax_rate` DECIMAL(5,2) NULL DEFAULT NULL AFTER `first_payment_date`;

-- Migration: 025_loan_devices.sql
-- Migration 025: Loan Devices Module
-- Leihgeräte-Verwaltung mit Ausleihe, Rücknahme und Kalender

-- ============================================================
-- 1. loan_devices – die Geräte selbst
-- ============================================================
CREATE TABLE IF NOT EXISTS `loan_devices` (
    `id`            BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    `title`         VARCHAR(255)    NOT NULL,
    `type`          VARCHAR(100)    NOT NULL DEFAULT '',
    `serial_number` VARCHAR(255)    DEFAULT NULL,
    `description`   TEXT            DEFAULT NULL,
    `image_path`    VARCHAR(500)    DEFAULT NULL,
    `notes`         TEXT            DEFAULT NULL,
    `status`        ENUM('available','lent','reserved','maintenance','retired')
                                    NOT NULL DEFAULT 'available',
    `created_by`    BIGINT UNSIGNED DEFAULT NULL,
    `created_at`    DATETIME        DEFAULT CURRENT_TIMESTAMP,
    `updated_at`    DATETIME        DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
    INDEX `idx_loan_devices_status` (`status`),
    INDEX `idx_loan_devices_type`   (`type`),
    CONSTRAINT `fk_loan_devices_created_by`
        FOREIGN KEY (`created_by`) REFERENCES `users`(`id`) ON DELETE SET NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

-- ============================================================
-- 2. loan_device_accessories – Zubehörliste pro Gerät
-- ============================================================
CREATE TABLE IF NOT EXISTS `loan_device_accessories` (
    `id`          BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    `device_id`   BIGINT UNSIGNED NOT NULL,
    `name`        VARCHAR(255)    NOT NULL,
    `description` VARCHAR(500)    DEFAULT NULL,
    `sort_order`  INT UNSIGNED    DEFAULT 0,
    `created_at`  DATETIME        DEFAULT CURRENT_TIMESTAMP,
    INDEX `idx_loan_accessories_device` (`device_id`),
    CONSTRAINT `fk_loan_accessories_device`
        FOREIGN KEY (`device_id`) REFERENCES `loan_devices`(`id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

-- ============================================================
-- 3. loan_lendings – Ausleih-/Reservierungsvorgänge
-- ============================================================
CREATE TABLE IF NOT EXISTS `loan_lendings` (
    `id`              BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    `device_id`       BIGINT UNSIGNED NOT NULL,
    `borrower_name`   VARCHAR(255)    NOT NULL,
    `borrower_email`  VARCHAR(255)    DEFAULT NULL,
    `borrower_phone`  VARCHAR(50)     DEFAULT NULL,
    `purpose`         TEXT            DEFAULT NULL,
    `planned_start`   DATE            NOT NULL,
    `planned_end`     DATE            NOT NULL,
    `actual_start`    DATETIME        DEFAULT NULL,
    `actual_end`      DATETIME        DEFAULT NULL,
    `status`          ENUM('reserved','active','returned','cancelled')
                                      NOT NULL DEFAULT 'reserved',
    `issued_by`       BIGINT UNSIGNED DEFAULT NULL,
    `returned_by`     BIGINT UNSIGNED DEFAULT NULL,
    `notes`           TEXT            DEFAULT NULL,
    `return_notes`    TEXT            DEFAULT NULL,
    `created_by`      BIGINT UNSIGNED DEFAULT NULL,
    `created_at`      DATETIME        DEFAULT CURRENT_TIMESTAMP,
    `updated_at`      DATETIME        DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
    INDEX `idx_loan_lendings_device`  (`device_id`),
    INDEX `idx_loan_lendings_status`  (`status`),
    INDEX `idx_loan_lendings_dates`   (`planned_start`, `planned_end`),
    CONSTRAINT `fk_loan_lendings_device`
        FOREIGN KEY (`device_id`)   REFERENCES `loan_devices`(`id`) ON DELETE CASCADE,
    CONSTRAINT `fk_loan_lendings_issued_by`
        FOREIGN KEY (`issued_by`)   REFERENCES `users`(`id`) ON DELETE SET NULL,
    CONSTRAINT `fk_loan_lendings_returned_by`
        FOREIGN KEY (`returned_by`) REFERENCES `users`(`id`) ON DELETE SET NULL,
    CONSTRAINT `fk_loan_lendings_created_by`
        FOREIGN KEY (`created_by`)  REFERENCES `users`(`id`) ON DELETE SET NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

-- ============================================================
-- 4. loan_lending_accessories – welches Zubehör ging mit raus
-- ============================================================
CREATE TABLE IF NOT EXISTS `loan_lending_accessories` (
    `id`           BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    `lending_id`   BIGINT UNSIGNED NOT NULL,
    `accessory_id` BIGINT UNSIGNED NOT NULL,
    `checked_out`  TINYINT(1)      NOT NULL DEFAULT 0,
    `checked_in`   TINYINT(1)      DEFAULT NULL,
    `created_at`   DATETIME        DEFAULT CURRENT_TIMESTAMP,
    UNIQUE KEY `uk_lending_accessory` (`lending_id`, `accessory_id`),
    CONSTRAINT `fk_lla_lending`
        FOREIGN KEY (`lending_id`)   REFERENCES `loan_lendings`(`id`) ON DELETE CASCADE,
    CONSTRAINT `fk_lla_accessory`
        FOREIGN KEY (`accessory_id`) REFERENCES `loan_device_accessories`(`id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

-- ============================================================
-- 5. Capabilities
-- ============================================================
INSERT IGNORE INTO `capabilities` (`name`, `description`, `category`, `risk_level`) VALUES
('view_loan_devices',   'Leihgeräte und Kalender ansehen',              'loan', 'low'),
('manage_loan_devices', 'Leihgeräte anlegen, bearbeiten und löschen',   'loan', 'medium'),
('issue_loan_devices',  'Leihgeräte ausgeben und zurücknehmen',         'loan', 'medium');

-- Migration: 026_loan_fixes.sql
-- Migration 026: Loan-Modul Fixes
-- Hinweis: Enthaelt alle bekannten Werte (inkl. spaeterer Migrationen),
-- damit ein Rerun-All keine bestehenden Eintraege abschneidet (Data truncated 1265).

-- 1. audit_log.action_category um 'loan' erweitern (und alle spaeten Werte behalten)
ALTER TABLE `audit_log` MODIFY COLUMN `action_category` ENUM(
    'auth',
    'permission',
    'content',
    'file',
    'export',
    'admin',
    'security',
    'contact',
    'order',
    'service',
    'contract',
    'loan',
    'ipam',
    'vault',
    'tickets',
    'ssl',
    'kanban'
) NOT NULL;

-- 2. (entfernt) Historischer ENUM-Umbau von capabilities.category.
--    Die Spalte bleibt VARCHAR(50) – siehe Hinweis beim ersten entfernten
--    ENUM-Umbau weiter oben (Strict-Mode-Fehler "Data truncated ... at row 26").

-- 3. Inventarnummer-Spalte zu loan_devices hinzufuegen
ALTER TABLE `loan_devices`
    ADD COLUMN `inventory_number` VARCHAR(100) DEFAULT NULL
    AFTER `serial_number`;

-- 4. Loan-Capabilities einfuegen (konnten in 025 nicht eingefuegt werden,
--    da capabilities.category 'loan' noch nicht im ENUM war)
INSERT IGNORE INTO `capabilities` (`name`, `description`, `category`, `risk_level`) VALUES
('view_loan_devices',   'Leihgeräte und Kalender ansehen',              'loan', 'low'),
('manage_loan_devices', 'Leihgeräte anlegen, bearbeiten und löschen',   'loan', 'medium'),
('issue_loan_devices',  'Leihgeräte ausgeben und zurücknehmen',         'loan', 'medium');

-- Migration: 027_loan_capabilities.sql
-- Migration 027: Loan-Capabilities sicherstellen
-- Falls Migration 025 die Capabilities nicht einfuegen konnte (ENUM-Fehler)
-- und Migration 026 noch nicht die Capabilities enthielt, werden sie hier
-- sicher eingefuegt. INSERT IGNORE macht den Query idempotent.
INSERT IGNORE INTO `capabilities` (`name`, `description`, `category`, `risk_level`) VALUES
('view_loan_devices',   'Leihgeräte und Kalender ansehen',              'loan', 'low'),
('manage_loan_devices', 'Leihgeräte anlegen, bearbeiten und löschen',   'loan', 'medium'),
('issue_loan_devices',  'Leihgeräte ausgeben und zurücknehmen',         'loan', 'medium');

-- Migration: 028_loan_group_lending.sql
-- Migration 028: Multi-device lending groups
-- Allows multiple devices to be combined in a single lending process

ALTER TABLE loan_lendings
    ADD COLUMN group_id VARCHAR(40) NULL DEFAULT NULL AFTER id;

CREATE INDEX idx_loan_lendings_group ON loan_lendings(group_id);

-- Migration: 029_loan_extensions.sql
-- Migration 029: Loan extension tracking
ALTER TABLE `loan_lendings`
    ADD COLUMN `extended_by`       BIGINT UNSIGNED NULL AFTER `returned_by`,
    ADD COLUMN `extension_reason`  TEXT NULL,
    ADD COLUMN `extension_at`      DATETIME NULL,
    ADD COLUMN `extension_old_end` DATE NULL,
    ADD CONSTRAINT `fk_loan_lendings_extended_by`
        FOREIGN KEY (`extended_by`) REFERENCES `users`(`id`) ON DELETE SET NULL;

-- Migration: 030_ipam.sql
-- Migration 030: IPAM – IP-Adressverwaltung
-- Subnetze, IP-Adressen und flexible Zusatzinfos (z.B. Server-Details)

-- ============================================================
-- 1. Subnetze
-- ============================================================
CREATE TABLE IF NOT EXISTS `ipam_subnets` (
    `id`          BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
    `name`        VARCHAR(100)    NOT NULL,
    `network`     VARCHAR(45)     NOT NULL COMMENT 'z.B. 192.168.1.0 oder 2001:db8::',
    `cidr`        TINYINT UNSIGNED NOT NULL COMMENT 'Präfixlänge, z.B. 24',
    `vlan`        VARCHAR(50)     NULL,
    `location`    VARCHAR(100)    NULL,
    `description` TEXT            NULL,
    `color`       VARCHAR(7)      NOT NULL DEFAULT '#6b7280' COMMENT 'Hex-Farbe für Übersicht',
    `created_by`  BIGINT UNSIGNED NULL,
    `created_at`  DATETIME        NOT NULL DEFAULT CURRENT_TIMESTAMP,
    `updated_at`  DATETIME        NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
    PRIMARY KEY (`id`),
    UNIQUE KEY `uq_subnet_network_cidr` (`network`, `cidr`),
    KEY `idx_subnet_name` (`name`),
    CONSTRAINT `fk_ipam_subnet_user` FOREIGN KEY (`created_by`) REFERENCES `users`(`id`) ON DELETE SET NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

-- ============================================================
-- 2. IP-Adressen
-- ============================================================
CREATE TABLE IF NOT EXISTS `ipam_addresses` (
    `id`               BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
    `subnet_id`        BIGINT UNSIGNED NOT NULL,
    `ip_address`       VARCHAR(45)     NOT NULL,
    `hostname`         VARCHAR(255)    NULL,
    `type`             ENUM('server','workstation','printer','switch','router','ap','camera','phone','other') NOT NULL DEFAULT 'other',
    `status`           ENUM('active','reserved','offline','free') NOT NULL DEFAULT 'active',
    `description`      TEXT            NULL,
    `responsible`      VARCHAR(150)    NULL COMMENT 'Name oder Team',
    `mac_address`      VARCHAR(17)     NULL,
    `created_by`       BIGINT UNSIGNED NULL,
    `updated_by`       BIGINT UNSIGNED NULL,
    `created_at`       DATETIME        NOT NULL DEFAULT CURRENT_TIMESTAMP,
    `updated_at`       DATETIME        NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
    PRIMARY KEY (`id`),
    UNIQUE KEY `uq_ip_per_subnet` (`subnet_id`, `ip_address`),
    KEY `idx_ip_status` (`status`),
    KEY `idx_ip_type` (`type`),
    KEY `idx_ip_hostname` (`hostname`),
    CONSTRAINT `fk_ipam_addr_subnet`  FOREIGN KEY (`subnet_id`)  REFERENCES `ipam_subnets`(`id`) ON DELETE CASCADE,
    CONSTRAINT `fk_ipam_addr_creator` FOREIGN KEY (`created_by`) REFERENCES `users`(`id`) ON DELETE SET NULL,
    CONSTRAINT `fk_ipam_addr_updater` FOREIGN KEY (`updated_by`) REFERENCES `users`(`id`) ON DELETE SET NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

-- ============================================================
-- 3. Flexible Zusatzinfos (Key-Value, z.B. Server-Details)
-- ============================================================
CREATE TABLE IF NOT EXISTS `ipam_address_details` (
    `id`         BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
    `address_id` BIGINT UNSIGNED NOT NULL,
    `detail_key` VARCHAR(100)    NOT NULL COMMENT 'z.B. OS, CPU, RAM, Dienste',
    `detail_val` TEXT            NULL,
    `sort_order` SMALLINT        NOT NULL DEFAULT 0,
    PRIMARY KEY (`id`),
    KEY `idx_detail_address` (`address_id`),
    CONSTRAINT `fk_ipam_detail_addr` FOREIGN KEY (`address_id`) REFERENCES `ipam_addresses`(`id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

-- ============================================================
-- 4. Capabilities
-- ============================================================
INSERT IGNORE INTO `capabilities` (`name`, `description`, `category`, `risk_level`) VALUES
('view_ipam',           'IP-Adressverwaltung ansehen',                  'ipam', 'low'),
('manage_ipam_ips',     'IP-Adressen anlegen und bearbeiten',           'ipam', 'medium'),
('manage_ipam_subnets', 'Subnetze anlegen, bearbeiten und löschen',     'ipam', 'high');

-- Migration: 031_vault.sql
-- ============================================================
-- Migration 031: Passwort-Tresor (Vault)
--
-- SICHERHEITSARCHITEKTUR:
-- Alle Passwörter werden mit AES-256-GCM verschlüsselt.
-- Der Vault-Master-Key (VMK) wird mit PBKDF2-SHA256 (600k Rounds)
-- aus dem Login-Passwort des Nutzers abgeleitet und nur als
-- verschlüsselter Blob in der DB gespeichert.
-- Im Klartext ist der VMK ausschließlich in der PHP-Session.
-- ============================================================

-- Persönliche Vault-Konfiguration pro Nutzer
CREATE TABLE IF NOT EXISTS `vault_users` (
    `id`            BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    `user_id`       BIGINT UNSIGNED NOT NULL,
    `pbkdf2_salt`   BINARY(32) NOT NULL COMMENT 'Salt fuer PBKDF2-Schluesselableitung',
    `vmk_nonce`     BINARY(12) NOT NULL COMMENT 'AES-GCM Nonce fuer VMK-Verschluesselung',
    `vmk_encrypted` BLOB NOT NULL COMMENT 'AES-256-GCM(random_VMK, PBKDF2(login_password, salt))',
    `vmk_tag`       BINARY(16) NOT NULL COMMENT 'AES-GCM Auth-Tag',
    `created_at`    DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
    `updated_at`    DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
    UNIQUE KEY `uq_vault_user` (`user_id`),
    FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

-- Ordner (persönlich: user_id gesetzt; Team: team_id gesetzt)
CREATE TABLE IF NOT EXISTS `vault_folders` (
    `id`         BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    `user_id`    BIGINT UNSIGNED NULL COMMENT 'NULL bei Team-Ordnern',
    `team_id`    BIGINT UNSIGNED NULL COMMENT 'NULL bei persoenlichen Ordnern',
    `parent_id`  BIGINT UNSIGNED NULL COMMENT 'NULL = Wurzelordner',
    `name`       VARCHAR(200) NOT NULL,
    `sort_order` SMALLINT NOT NULL DEFAULT 0,
    `created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
    FOREIGN KEY (`user_id`)   REFERENCES `users`(`id`) ON DELETE CASCADE,
    FOREIGN KEY (`parent_id`) REFERENCES `vault_folders`(`id`) ON DELETE SET NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

-- Passwort-Einträge (alle Felder AES-256-GCM verschlüsselt)
CREATE TABLE IF NOT EXISTS `vault_entries` (
    `id`              BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    `folder_id`       BIGINT UNSIGNED NULL,
    `user_id`         BIGINT UNSIGNED NULL COMMENT 'NULL bei Team-Eintraegen',
    `team_id`         BIGINT UNSIGNED NULL COMMENT 'NULL bei persoenlichen Eintraegen',

    -- Titel
    `title_enc`       BLOB NOT NULL,
    `title_nonce`     BINARY(12) NOT NULL,
    `title_tag`       BINARY(16) NOT NULL,

    -- Benutzername
    `username_enc`    BLOB,
    `username_nonce`  BINARY(12),
    `username_tag`    BINARY(16),

    -- Passwort
    `password_enc`    BLOB NOT NULL,
    `password_nonce`  BINARY(12) NOT NULL,
    `password_tag`    BINARY(16) NOT NULL,

    -- URL
    `url_enc`         BLOB,
    `url_nonce`       BINARY(12),
    `url_tag`         BINARY(16),

    -- Notizen
    `notes_enc`       MEDIUMBLOB,
    `notes_nonce`     BINARY(12),
    `notes_tag`       BINARY(16),

    -- TOTP-Secret (Base32-kodiert, verschlüsselt)
    `totp_enc`        BLOB,
    `totp_nonce`      BINARY(12),
    `totp_tag`        BINARY(16),

    -- Favicon: nur Domain gespeichert (kein Secret)
    `favicon_domain`  VARCHAR(253) NULL,

    `created_by`      BIGINT UNSIGNED NULL,
    `updated_by`      BIGINT UNSIGNED NULL,
    `created_at`      DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
    `updated_at`      DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,

    FOREIGN KEY (`folder_id`)  REFERENCES `vault_folders`(`id`) ON DELETE SET NULL,
    FOREIGN KEY (`user_id`)    REFERENCES `users`(`id`) ON DELETE CASCADE,
    FOREIGN KEY (`created_by`) REFERENCES `users`(`id`) ON DELETE SET NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

-- Team-Tresore
CREATE TABLE IF NOT EXISTS `vault_teams` (
    `id`               BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    `name`             VARCHAR(200) NOT NULL,
    `description`      TEXT,

    -- TVK verschlüsselt mit PBKDF2(APP_KEY, admin_salt) → Admin-Kopie
    -- Ermöglicht Admin-Operationen (Mitglieder hinzufügen) ohne Mitglied zu sein
    `admin_salt`       BINARY(32) NOT NULL,
    `admin_key_enc`    BLOB NOT NULL COMMENT 'AES-256-GCM(TVK, PBKDF2(APP_KEY, admin_salt))',
    `admin_key_nonce`  BINARY(12) NOT NULL,
    `admin_key_tag`    BINARY(16) NOT NULL,

    `created_by`       BIGINT UNSIGNED NULL,
    `created_at`       DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
    FOREIGN KEY (`created_by`) REFERENCES `users`(`id`) ON DELETE SET NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

-- Team-Mitglieder: je eine Kopie des TVK, verschlüsselt mit dem VMK des Mitglieds
CREATE TABLE IF NOT EXISTS `vault_team_members` (
    `id`         BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    `team_id`    BIGINT UNSIGNED NOT NULL,
    `user_id`    BIGINT UNSIGNED NOT NULL,
    `role`       ENUM('admin','member') NOT NULL DEFAULT 'member',
    `tvk_enc`    BLOB NULL COMMENT 'AES-256-GCM(TVK, user_VMK) – NULL = Provisioning ausstehend',
    `tvk_nonce`  BINARY(12) NULL,
    `tvk_tag`    BINARY(16) NULL,
    `joined_at`  DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
    UNIQUE KEY `uq_team_member` (`team_id`, `user_id`),
    FOREIGN KEY (`team_id`) REFERENCES `vault_teams`(`id`) ON DELETE CASCADE,
    FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

-- Capabilities für Vault
INSERT IGNORE INTO `capabilities` (`name`, `description`, `category`, `risk_level`) VALUES
('view_vault',          'Persönlichen Passwort-Tresor nutzen',             'vault', 'low'),
('manage_vault_teams',  'Team-Tresore erstellen und eigene verwalten',     'vault', 'medium'),
('admin_vault_teams',   'Alle Team-Tresore im Admin-Bereich verwalten',    'vault', 'high');

-- Migration: 032_audit_ipam_vault_category.sql
-- Migration 032: Audit-Log action_category um 'ipam' und 'vault' erweitern
-- Hinweis: Enthaelt alle bekannten Werte (inkl. spaeterer Migrationen),
-- damit ein Rerun-All keine bestehenden Eintraege abschneidet (Data truncated 1265).
ALTER TABLE `audit_log` MODIFY COLUMN `action_category` ENUM(
    'auth',
    'permission',
    'content',
    'file',
    'export',
    'admin',
    'security',
    'contact',
    'order',
    'service',
    'contract',
    'loan',
    'ipam',
    'vault',
    'tickets',
    'ssl',
    'kanban'
) NOT NULL;

-- Migration: 033_ipam_dhcp_range.sql
-- Migration 033: IPAM – DHCP-Bereich pro Subnetz

ALTER TABLE `ipam_subnets`
    ADD COLUMN `dhcp_range_start` VARCHAR(45) NULL COMMENT 'Start der DHCP-Vergabe, z.B. 192.168.1.100' AFTER `description`,
    ADD COLUMN `dhcp_range_end`   VARCHAR(45) NULL COMMENT 'Ende der DHCP-Vergabe, z.B. 192.168.1.200' AFTER `dhcp_range_start`;

-- Migration: 034_tickets.sql
-- ============================================================
-- 034 Ticket-System
-- ============================================================

-- Kategorien (mit Unterkategorien)
CREATE TABLE IF NOT EXISTS `ticket_categories` (
    `id`         INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    `parent_id`  INT UNSIGNED NULL,
    `name`       VARCHAR(255) NOT NULL,
    `sort_order` INT          NOT NULL DEFAULT 0,
    `created_at` DATETIME     NOT NULL DEFAULT CURRENT_TIMESTAMP,
    FOREIGN KEY (`parent_id`) REFERENCES `ticket_categories`(`id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

-- Standorte (mit Unterstandorten)
CREATE TABLE IF NOT EXISTS `ticket_locations` (
    `id`         INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    `parent_id`  INT UNSIGNED NULL,
    `name`       VARCHAR(255) NOT NULL,
    `sort_order` INT          NOT NULL DEFAULT 0,
    `created_at` DATETIME     NOT NULL DEFAULT CURRENT_TIMESTAMP,
    FOREIGN KEY (`parent_id`) REFERENCES `ticket_locations`(`id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

-- Haupt-Ticket-Tabelle
-- Hinweis: assignee_id referenziert users.id (BIGINT UNSIGNED) → muss ebenfalls BIGINT UNSIGNED sein
CREATE TABLE IF NOT EXISTS `tickets` (
    `id`              INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    `subject`         VARCHAR(500) NOT NULL,
    `description`     TEXT,
    `from_email`      VARCHAR(255) NOT NULL,
    `from_name`       VARCHAR(255) NOT NULL DEFAULT '',
    `status`          ENUM('new','open','waiting','solved','closed') NOT NULL DEFAULT 'new',
    `assignee_id`     BIGINT UNSIGNED NULL,
    `category_id`     INT UNSIGNED NULL,
    `location_id`     INT UNSIGNED NULL,
    `mail_message_id` VARCHAR(500) NULL,
    `solved_at`       DATETIME NULL,
    `closed_at`       DATETIME NULL,
    `created_at`      DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
    `updated_at`      DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
    FOREIGN KEY (`assignee_id`) REFERENCES `users`(`id`) ON DELETE SET NULL,
    FOREIGN KEY (`category_id`) REFERENCES `ticket_categories`(`id`) ON DELETE SET NULL,
    FOREIGN KEY (`location_id`) REFERENCES `ticket_locations`(`id`) ON DELETE SET NULL,
    INDEX (`status`),
    INDEX (`assignee_id`),
    INDEX (`created_at`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

-- Ticket-Antworten
-- Hinweis: author_id referenziert users.id (BIGINT UNSIGNED) → muss ebenfalls BIGINT UNSIGNED sein
CREATE TABLE IF NOT EXISTS `ticket_replies` (
    `id`          INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    `ticket_id`   INT UNSIGNED NOT NULL,
    `author_type` ENUM('staff','customer') NOT NULL,
    `author_id`   BIGINT UNSIGNED NULL,
    `author_name` VARCHAR(255) NULL,
    `body`        TEXT NOT NULL,
    `created_at`  DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
    FOREIGN KEY (`ticket_id`) REFERENCES `tickets`(`id`) ON DELETE CASCADE,
    FOREIGN KEY (`author_id`) REFERENCES `users`(`id`) ON DELETE SET NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

-- E-Mail-Vorlagen
CREATE TABLE IF NOT EXISTS `ticket_email_templates` (
    `id`         INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    `event_type` VARCHAR(50)  NOT NULL UNIQUE,
    `subject`    VARCHAR(500) NOT NULL,
    `body`       TEXT         NOT NULL,
    `updated_at` DATETIME     NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

-- Standard-E-Mail-Vorlagen
INSERT IGNORE INTO `ticket_email_templates` (`event_type`, `subject`, `body`) VALUES
('created',
 '[Ticket #{{ticket_id}}] Ihre Anfrage wurde erhalten',
 '<p>Sehr geehrte(r) {{from_name}},</p><p>vielen Dank für Ihre Anfrage. Wir haben Ihr Ticket erhalten und werden uns so schnell wie möglich darum kümmern.</p><p><strong>Ticket-Nummer:</strong> #{{ticket_id}}<br><strong>Betreff:</strong> {{ticket_subject}}</p><p>Sie erhalten weitere Benachrichtigungen, sobald sich der Status ändert.</p><p>Mit freundlichen Grüßen<br>{{app_name}}</p>'),

('assigned',
 '[Ticket #{{ticket_id}}] Ihre Anfrage wurde zugewiesen',
 '<p>Sehr geehrte(r) {{from_name}},</p><p>Ihr Ticket wurde einem Mitarbeiter zugewiesen.</p><p><strong>Ticket-Nummer:</strong> #{{ticket_id}}<br><strong>Betreff:</strong> {{ticket_subject}}<br><strong>Zuständig:</strong> {{assignee_name}}</p><p>Mit freundlichen Grüßen<br>{{app_name}}</p>'),

('reply',
 '[Ticket #{{ticket_id}}] Neue Antwort zu Ihrer Anfrage',
 '<p>Sehr geehrte(r) {{from_name}},</p><p>es gibt eine neue Antwort zu Ihrem Ticket:</p><p><strong>Ticket-Nummer:</strong> #{{ticket_id}}<br><strong>Betreff:</strong> {{ticket_subject}}</p><hr style="margin:1rem 0;border:none;border-top:1px solid #e5e7eb;"><div style="background:#f9fafb;padding:1rem;border-radius:0.5rem;">{{reply_text}}</div><hr style="margin:1rem 0;border:none;border-top:1px solid #e5e7eb;"><p>Sie können auf diese E-Mail antworten, um dem Ticket eine Nachricht hinzuzufügen.</p><p>Mit freundlichen Grüßen<br>{{app_name}}</p>'),

('solved',
 '[Ticket #{{ticket_id}}] Ihre Anfrage wurde als gelöst markiert',
 '<p>Sehr geehrte(r) {{from_name}},</p><p>Ihr Ticket wurde als <strong>gelöst</strong> markiert.</p><p><strong>Ticket-Nummer:</strong> #{{ticket_id}}<br><strong>Betreff:</strong> {{ticket_subject}}</p><p>Falls Ihr Anliegen noch nicht vollständig gelöst ist, antworten Sie bitte <strong>innerhalb von 3 Tagen</strong> auf diese E-Mail. Andernfalls wird das Ticket automatisch geschlossen.</p><p>Mit freundlichen Grüßen<br>{{app_name}}</p>'),

('closed',
 '[Ticket #{{ticket_id}}] Ihre Anfrage wurde geschlossen',
 '<p>Sehr geehrte(r) {{from_name}},</p><p>Ihr Ticket wurde geschlossen.</p><p><strong>Ticket-Nummer:</strong> #{{ticket_id}}<br><strong>Betreff:</strong> {{ticket_subject}}</p><p>Für weitere Fragen schicken Sie uns bitte eine neue E-Mail.</p><p>Mit freundlichen Grüßen<br>{{app_name}}</p>');

-- Capabilities
INSERT IGNORE INTO `capabilities` (`name`, `description`, `category`) VALUES
('view_tickets',   'Tickets anzeigen und lesen',                               'tickets'),
('manage_tickets', 'Tickets zuweisen, beantworten und Status ändern',          'tickets'),
('admin_tickets',  'Ticket-Einstellungen, Kategorien und Standorte verwalten', 'tickets');

-- Migration: 035_ssl_checker.sql
-- ============================================================
-- 035 SSL-Zertifikat-Checker
-- ============================================================

CREATE TABLE IF NOT EXISTS `ssl_entries` (
    `id`                INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    `label`             VARCHAR(255)    NOT NULL DEFAULT '',
    `host`              VARCHAR(255)    NOT NULL,
    `port`              SMALLINT UNSIGNED NOT NULL DEFAULT 443,
    -- Letzte gecheckte Zertifikatsdaten
    `last_checked_at`   DATETIME        NULL,
    `check_error`       TEXT            NULL,
    -- Zertifikats-Metadaten
    `cert_cn`           VARCHAR(500)    NULL COMMENT 'Common Name',
    `cert_issuer_cn`    VARCHAR(500)    NULL,
    `cert_issuer_org`   VARCHAR(500)    NULL,
    `cert_org`          VARCHAR(500)    NULL COMMENT 'Subject Organization',
    `cert_valid_from`   DATETIME        NULL,
    `cert_valid_to`     DATETIME        NULL,
    `cert_days_left`    INT             NULL,
    `cert_serial`       VARCHAR(255)    NULL,
    `cert_sig_algo`     VARCHAR(100)    NULL,
    `cert_key_type`     VARCHAR(50)     NULL COMMENT 'RSA / EC / DSA',
    `cert_key_bits`     SMALLINT        NULL,
    `cert_sans`         TEXT            NULL COMMENT 'Subject Alternative Names, kommagetrennt',
    `cert_ocsp_url`     VARCHAR(1000)   NULL,
    `cert_crl_url`      VARCHAR(1000)   NULL,
    `cert_is_wildcard`  TINYINT(1)      NOT NULL DEFAULT 0,
    `cert_chain_depth`  TINYINT         NULL COMMENT 'Anzahl Zertifikate in der Chain',
    -- Metadata
    `created_by`        BIGINT UNSIGNED NULL,
    `created_at`        DATETIME        NOT NULL DEFAULT CURRENT_TIMESTAMP,
    `updated_at`        DATETIME        NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
    FOREIGN KEY (`created_by`) REFERENCES `users`(`id`) ON DELETE SET NULL,
    INDEX (`cert_valid_to`),
    INDEX (`cert_days_left`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

-- Capabilities
INSERT IGNORE INTO `capabilities` (`name`, `description`, `category`) VALUES
('view_ssl',   'SSL-Zertifikate anzeigen',             'ssl'),
('manage_ssl', 'SSL-Einträge anlegen und löschen',     'ssl');

-- Migration: 036_kanban.sql
-- ============================================================
-- 036 Kanban-Board
-- ============================================================

CREATE TABLE IF NOT EXISTS `kanban_categories` (
    `id`         INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    `name`       VARCHAR(255) NOT NULL,
    `color`      VARCHAR(7)   NOT NULL DEFAULT '#6366f1',
    `sort_order` INT          NOT NULL DEFAULT 0,
    `created_at` DATETIME     NOT NULL DEFAULT CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

CREATE TABLE IF NOT EXISTS `kanban_tasks` (
    `id`            INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    `title`         VARCHAR(500)    NOT NULL,
    `description`   TEXT            NULL,
    `month`         CHAR(7)         NOT NULL COMMENT 'YYYY-MM',
    `category_id`   INT UNSIGNED    NULL,
    `priority`      ENUM('low','normal','high','urgent') NOT NULL DEFAULT 'normal',
    `due_date`      DATE            NULL,
    `sort_order`    INT             NOT NULL DEFAULT 0,
    `created_by`    BIGINT UNSIGNED NULL,
    `created_at`    DATETIME        NOT NULL DEFAULT CURRENT_TIMESTAMP,
    `updated_at`    DATETIME        NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
    FOREIGN KEY (`category_id`) REFERENCES `kanban_categories`(`id`) ON DELETE SET NULL,
    FOREIGN KEY (`created_by`)  REFERENCES `users`(`id`) ON DELETE SET NULL,
    INDEX (`month`),
    INDEX (`category_id`),
    INDEX (`priority`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

-- Mehrere Benutzer pro Aufgabe (n:m)
CREATE TABLE IF NOT EXISTS `kanban_task_users` (
    `task_id` INT UNSIGNED    NOT NULL,
    `user_id` BIGINT UNSIGNED NOT NULL,
    PRIMARY KEY (`task_id`, `user_id`),
    FOREIGN KEY (`task_id`) REFERENCES `kanban_tasks`(`id`) ON DELETE CASCADE,
    FOREIGN KEY (`user_id`) REFERENCES `users`(`id`)        ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

-- Capabilities
INSERT IGNORE INTO `capabilities` (`name`, `description`, `category`) VALUES
('view_kanban',   'Kanban-Board anzeigen',              'kanban'),
('manage_kanban', 'Aufgaben und Kategorien verwalten',  'kanban');

-- Migration: 037_vault_extension_tokens.sql
-- ============================================================
-- 037 Vault – Browser-Extension API-Tokens
--
-- Der Token ist ein zufälliger 32-Byte-Wert (Base64URL).
-- Der VMK (Vault Master Key) wird mit einem aus dem Token
-- abgeleiteten Schlüssel (HKDF-SHA-256) erneut verschlüsselt
-- und gespeichert. Dadurch kann die Extension ohne aktive
-- Browser-Session auf den Vault zugreifen.
-- ============================================================

CREATE TABLE IF NOT EXISTS `vault_extension_tokens` (
    `id`            BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    `user_id`       BIGINT UNSIGNED NOT NULL,
    `label`         VARCHAR(200)    NOT NULL DEFAULT 'Chrome Extension',
    `token_hash`    CHAR(64)        NOT NULL COMMENT 'SHA-256(token) hex',
    -- VMK erneut verschlüsselt mit HKDF(token, "vault-ext-v1")
    `vmk_enc`       BLOB            NOT NULL,
    `vmk_nonce`     BINARY(12)      NOT NULL,
    `vmk_tag`       BINARY(16)      NOT NULL,
    `last_used_at`  DATETIME        NULL,
    `created_at`    DATETIME        NOT NULL DEFAULT CURRENT_TIMESTAMP,
    UNIQUE KEY `uq_token_hash` (`token_hash`),
    FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

-- Migration: 038_audit_tickets_ssl_kanban_category.sql
-- Migration 038: Audit-Log action_category um 'tickets', 'ssl', 'kanban' erweitern
ALTER TABLE `audit_log` MODIFY COLUMN `action_category` ENUM(
    'auth',
    'permission',
    'content',
    'file',
    'export',
    'admin',
    'security',
    'contact',
    'order',
    'service',
    'contract',
    'loan',
    'ipam',
    'vault',
    'tickets',
    'ssl',
    'kanban'
) NOT NULL;

-- Migration: 039_email_templates_redesign.sql
-- Migration 039: Moderne E-Mail-Vorlagen mit Logo-Support und neuem Design

-- Hilfsmakro: gemeinsames Layout als Basis-HTML (wird per PHP zusammengebaut)
-- Die Vorlagen nutzen {{app_logo_url}} und {{ticket_url}} als neue Platzhalter.

-- ============================================================
-- VORLAGE 1: Ticket erstellt
-- ============================================================
-- Hinweis: INSERT IGNORE stellt sicher, dass bestehende angepasste Vorlagen
-- NICHT überschrieben werden. Zum Zurücksetzen: Admin → Ticket-E-Mails → "Standard wiederherstellen".
INSERT IGNORE INTO `ticket_email_templates` (`event_type`, `subject`, `body`, `updated_at`)
VALUES ('created',
'Ihr Ticket #{{ticket_id}} wurde erfolgreich erstellt',
'<!DOCTYPE html>
<html lang="de">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Ticket erstellt</title>
</head>
<body style="margin:0;padding:0;background:#f3f4f6;font-family:Arial,Helvetica,sans-serif;">
  <table width="100%" cellpadding="0" cellspacing="0" style="background:#f3f4f6;padding:32px 16px;">
    <tr><td align="center">
      <table width="600" cellpadding="0" cellspacing="0" style="max-width:600px;width:100%;background:#ffffff;border-radius:12px;overflow:hidden;box-shadow:0 2px 12px rgba(0,0,0,0.08);">

        <!-- Header -->
        <tr>
          <td style="background:linear-gradient(135deg,#1e40af 0%,#2563eb 100%);padding:28px 36px;">
            <table width="100%" cellpadding="0" cellspacing="0">
              <tr>
                <td>
                  {{#if app_logo_url}}
                  <img src="{{app_logo_url}}" alt="{{app_name}}" style="height:36px;width:auto;display:block;filter:brightness(10);" onerror="this.style.display=''none''">
                  {{/if}}
                  <div style="color:#ffffff;font-size:22px;font-weight:700;margin-top:8px;">{{app_name}}</div>
                </td>
                <td align="right" style="vertical-align:middle;">
                  <div style="background:rgba(255,255,255,0.2);border-radius:8px;padding:8px 16px;display:inline-block;">
                    <div style="color:rgba(255,255,255,0.8);font-size:11px;font-weight:600;text-transform:uppercase;letter-spacing:0.5px;">Ticket</div>
                    <div style="color:#ffffff;font-size:20px;font-weight:700;">#{{ticket_id}}</div>
                  </div>
                </td>
              </tr>
            </table>
          </td>
        </tr>

        <!-- Status Banner -->
        <tr>
          <td style="background:#eff6ff;border-bottom:2px solid #bfdbfe;padding:14px 36px;">
            <table cellpadding="0" cellspacing="0">
              <tr>
                <td style="width:32px;height:32px;background:#2563eb;border-radius:50%;text-align:center;vertical-align:middle;">
                  <span style="color:#fff;font-size:16px;line-height:32px;">✓</span>
                </td>
                <td style="padding-left:12px;color:#1e40af;font-size:14px;font-weight:600;">
                  Ihr Ticket wurde erfolgreich eingereicht
                </td>
              </tr>
            </table>
          </td>
        </tr>

        <!-- Content -->
        <tr>
          <td style="padding:32px 36px;">
            <p style="margin:0 0 16px;color:#374151;font-size:16px;">Guten Tag, <strong>{{from_name}}</strong>,</p>
            <p style="margin:0 0 24px;color:#6b7280;font-size:14px;line-height:1.7;">
              wir haben Ihre Anfrage erhalten und bearbeiten sie schnellstmöglich. Sie erhalten eine weitere Benachrichtigung, sobald sich der Status Ihres Tickets ändert.
            </p>

            <!-- Ticket Info Box -->
            <table width="100%" cellpadding="0" cellspacing="0" style="background:#f9fafb;border:1px solid #e5e7eb;border-radius:10px;overflow:hidden;margin-bottom:28px;">
              <tr>
                <td style="background:#f3f4f6;padding:12px 20px;border-bottom:1px solid #e5e7eb;">
                  <span style="font-size:12px;font-weight:700;color:#6b7280;text-transform:uppercase;letter-spacing:0.5px;">Ihre Anfrage</span>
                </td>
              </tr>
              <tr>
                <td style="padding:20px;">
                  <table width="100%" cellpadding="0" cellspacing="0">
                    <tr>
                      <td style="padding:6px 0;color:#9ca3af;font-size:13px;width:120px;">Ticket-Nr.</td>
                      <td style="padding:6px 0;color:#111827;font-size:13px;font-weight:600;">#{{ticket_id}}</td>
                    </tr>
                    <tr>
                      <td style="padding:6px 0;color:#9ca3af;font-size:13px;">Betreff</td>
                      <td style="padding:6px 0;color:#111827;font-size:13px;">{{ticket_subject}}</td>
                    </tr>
                    <tr>
                      <td style="padding:6px 0;color:#9ca3af;font-size:13px;">Status</td>
                      <td style="padding:6px 0;">
                        <span style="background:#fef3c7;color:#92400e;font-size:12px;font-weight:600;padding:2px 10px;border-radius:20px;">Neu</span>
                      </td>
                    </tr>
                  </table>
                </td>
              </tr>
            </table>

            <p style="margin:0 0 8px;color:#6b7280;font-size:13px;">
              Sie können auf diese E-Mail antworten, um Ihrem Ticket weitere Informationen hinzuzufügen.
            </p>
          </td>
        </tr>

        <!-- Footer -->
        <tr>
          <td style="background:#f9fafb;border-top:1px solid #e5e7eb;padding:20px 36px;text-align:center;">
            <p style="margin:0 0 4px;color:#9ca3af;font-size:12px;">Diese E-Mail wurde automatisch vom Support-System gesendet.</p>
            <p style="margin:0;color:#9ca3af;font-size:12px;">© {{app_name}}</p>
          </td>
        </tr>

      </table>
    </td></tr>
  </table>
</body>
</html>',
NOW());

-- ============================================================
-- VORLAGE 2: Ticket zugewiesen
-- ============================================================
INSERT IGNORE INTO `ticket_email_templates` (`event_type`, `subject`, `body`, `updated_at`)
VALUES ('assigned',
'Ticket #{{ticket_id}} wurde Ihnen zugewiesen',
'<!DOCTYPE html>
<html lang="de">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Ticket zugewiesen</title>
</head>
<body style="margin:0;padding:0;background:#f3f4f6;font-family:Arial,Helvetica,sans-serif;">
  <table width="100%" cellpadding="0" cellspacing="0" style="background:#f3f4f6;padding:32px 16px;">
    <tr><td align="center">
      <table width="600" cellpadding="0" cellspacing="0" style="max-width:600px;width:100%;background:#ffffff;border-radius:12px;overflow:hidden;box-shadow:0 2px 12px rgba(0,0,0,0.08);">

        <tr>
          <td style="background:linear-gradient(135deg,#1e40af 0%,#2563eb 100%);padding:28px 36px;">
            <table width="100%" cellpadding="0" cellspacing="0">
              <tr>
                <td>
                  <div style="color:#ffffff;font-size:22px;font-weight:700;">{{app_name}}</div>
                </td>
                <td align="right">
                  <div style="background:rgba(255,255,255,0.2);border-radius:8px;padding:8px 16px;display:inline-block;">
                    <div style="color:rgba(255,255,255,0.8);font-size:11px;font-weight:600;text-transform:uppercase;">Ticket</div>
                    <div style="color:#ffffff;font-size:20px;font-weight:700;">#{{ticket_id}}</div>
                  </div>
                </td>
              </tr>
            </table>
          </td>
        </tr>

        <tr>
          <td style="background:#f0fdf4;border-bottom:2px solid #bbf7d0;padding:14px 36px;">
            <table cellpadding="0" cellspacing="0">
              <tr>
                <td style="width:32px;height:32px;background:#16a34a;border-radius:50%;text-align:center;vertical-align:middle;">
                  <span style="color:#fff;font-size:18px;line-height:32px;">👤</span>
                </td>
                <td style="padding-left:12px;color:#15803d;font-size:14px;font-weight:600;">
                  Ihr Ticket wurde einem Mitarbeiter zugewiesen
                </td>
              </tr>
            </table>
          </td>
        </tr>

        <tr>
          <td style="padding:32px 36px;">
            <p style="margin:0 0 16px;color:#374151;font-size:16px;">Guten Tag, <strong>{{from_name}}</strong>,</p>
            <p style="margin:0 0 24px;color:#6b7280;font-size:14px;line-height:1.7;">
              Ihr Ticket wurde einem Mitarbeiter zur Bearbeitung zugewiesen. Wir werden uns so schnell wie möglich bei Ihnen melden.
            </p>

            <table width="100%" cellpadding="0" cellspacing="0" style="background:#f9fafb;border:1px solid #e5e7eb;border-radius:10px;overflow:hidden;margin-bottom:28px;">
              <tr>
                <td style="background:#f3f4f6;padding:12px 20px;border-bottom:1px solid #e5e7eb;">
                  <span style="font-size:12px;font-weight:700;color:#6b7280;text-transform:uppercase;letter-spacing:0.5px;">Ticket-Details</span>
                </td>
              </tr>
              <tr>
                <td style="padding:20px;">
                  <table width="100%" cellpadding="0" cellspacing="0">
                    <tr>
                      <td style="padding:6px 0;color:#9ca3af;font-size:13px;width:140px;">Ticket-Nr.</td>
                      <td style="padding:6px 0;color:#111827;font-size:13px;font-weight:600;">#{{ticket_id}}</td>
                    </tr>
                    <tr>
                      <td style="padding:6px 0;color:#9ca3af;font-size:13px;">Betreff</td>
                      <td style="padding:6px 0;color:#111827;font-size:13px;">{{ticket_subject}}</td>
                    </tr>
                    <tr>
                      <td style="padding:6px 0;color:#9ca3af;font-size:13px;">Bearbeiter</td>
                      <td style="padding:6px 0;color:#111827;font-size:13px;font-weight:600;">{{assignee_name}}</td>
                    </tr>
                    <tr>
                      <td style="padding:6px 0;color:#9ca3af;font-size:13px;">Status</td>
                      <td style="padding:6px 0;">
                        <span style="background:#dbeafe;color:#1e40af;font-size:12px;font-weight:600;padding:2px 10px;border-radius:20px;">In Bearbeitung</span>
                      </td>
                    </tr>
                  </table>
                </td>
              </tr>
            </table>

            <p style="margin:0;color:#6b7280;font-size:13px;">
              Sie können auf diese E-Mail antworten, um dem Ticket weitere Informationen hinzuzufügen.
            </p>
          </td>
        </tr>

        <tr>
          <td style="background:#f9fafb;border-top:1px solid #e5e7eb;padding:20px 36px;text-align:center;">
            <p style="margin:0 0 4px;color:#9ca3af;font-size:12px;">Diese E-Mail wurde automatisch vom Support-System gesendet.</p>
            <p style="margin:0;color:#9ca3af;font-size:12px;">© {{app_name}}</p>
          </td>
        </tr>

      </table>
    </td></tr>
  </table>
</body>
</html>',
NOW());

-- ============================================================
-- VORLAGE 3: Neue Antwort
-- ============================================================
INSERT IGNORE INTO `ticket_email_templates` (`event_type`, `subject`, `body`, `updated_at`)
VALUES ('reply',
'Neue Antwort zu Ticket #{{ticket_id}}: {{ticket_subject}}',
'<!DOCTYPE html>
<html lang="de">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Neue Antwort</title>
</head>
<body style="margin:0;padding:0;background:#f3f4f6;font-family:Arial,Helvetica,sans-serif;">
  <table width="100%" cellpadding="0" cellspacing="0" style="background:#f3f4f6;padding:32px 16px;">
    <tr><td align="center">
      <table width="600" cellpadding="0" cellspacing="0" style="max-width:600px;width:100%;background:#ffffff;border-radius:12px;overflow:hidden;box-shadow:0 2px 12px rgba(0,0,0,0.08);">

        <tr>
          <td style="background:linear-gradient(135deg,#1e40af 0%,#2563eb 100%);padding:28px 36px;">
            <table width="100%" cellpadding="0" cellspacing="0">
              <tr>
                <td>
                  <div style="color:#ffffff;font-size:22px;font-weight:700;">{{app_name}}</div>
                </td>
                <td align="right">
                  <div style="background:rgba(255,255,255,0.2);border-radius:8px;padding:8px 16px;display:inline-block;">
                    <div style="color:rgba(255,255,255,0.8);font-size:11px;font-weight:600;text-transform:uppercase;">Ticket</div>
                    <div style="color:#ffffff;font-size:20px;font-weight:700;">#{{ticket_id}}</div>
                  </div>
                </td>
              </tr>
            </table>
          </td>
        </tr>

        <tr>
          <td style="background:#eff6ff;border-bottom:2px solid #bfdbfe;padding:14px 36px;">
            <table cellpadding="0" cellspacing="0">
              <tr>
                <td style="width:32px;height:32px;background:#2563eb;border-radius:50%;text-align:center;vertical-align:middle;">
                  <span style="color:#fff;font-size:14px;line-height:32px;">✉</span>
                </td>
                <td style="padding-left:12px;color:#1e40af;font-size:14px;font-weight:600;">
                  Neue Antwort von {{assignee_name}}
                </td>
              </tr>
            </table>
          </td>
        </tr>

        <tr>
          <td style="padding:32px 36px;">
            <p style="margin:0 0 16px;color:#374151;font-size:16px;">Guten Tag, <strong>{{from_name}}</strong>,</p>
            <p style="margin:0 0 20px;color:#6b7280;font-size:14px;line-height:1.7;">
              es gibt eine neue Antwort zu Ihrem Ticket <strong>„{{ticket_subject}}"</strong>:
            </p>

            <!-- Reply Content -->
            <table width="100%" cellpadding="0" cellspacing="0" style="margin-bottom:28px;">
              <tr>
                <td style="background:#f9fafb;border-left:4px solid #2563eb;border-radius:0 8px 8px 0;padding:20px 24px;">
                  <div style="font-size:12px;font-weight:700;color:#6b7280;text-transform:uppercase;letter-spacing:0.5px;margin-bottom:12px;">
                    Antwort von {{assignee_name}}
                  </div>
                  <div style="color:#374151;font-size:14px;line-height:1.8;">
                    {{reply_text}}
                  </div>
                </td>
              </tr>
            </table>

            <table width="100%" cellpadding="0" cellspacing="0" style="background:#f9fafb;border:1px solid #e5e7eb;border-radius:10px;overflow:hidden;margin-bottom:28px;">
              <tr>
                <td style="padding:16px 20px;">
                  <table width="100%" cellpadding="0" cellspacing="0">
                    <tr>
                      <td style="color:#9ca3af;font-size:13px;width:120px;padding:4px 0;">Ticket-Nr.</td>
                      <td style="color:#111827;font-size:13px;font-weight:600;padding:4px 0;">#{{ticket_id}}</td>
                    </tr>
                    <tr>
                      <td style="color:#9ca3af;font-size:13px;padding:4px 0;">Betreff</td>
                      <td style="color:#111827;font-size:13px;padding:4px 0;">{{ticket_subject}}</td>
                    </tr>
                  </table>
                </td>
              </tr>
            </table>

            <p style="margin:0;color:#6b7280;font-size:13px;">
              Sie können auf diese E-Mail antworten, um dem Ticket weitere Informationen hinzuzufügen.
            </p>
          </td>
        </tr>

        <tr>
          <td style="background:#f9fafb;border-top:1px solid #e5e7eb;padding:20px 36px;text-align:center;">
            <p style="margin:0 0 4px;color:#9ca3af;font-size:12px;">Diese E-Mail wurde automatisch vom Support-System gesendet.</p>
            <p style="margin:0;color:#9ca3af;font-size:12px;">© {{app_name}}</p>
          </td>
        </tr>

      </table>
    </td></tr>
  </table>
</body>
</html>',
NOW());

-- ============================================================
-- VORLAGE 4: Ticket gelöst
-- ============================================================
INSERT IGNORE INTO `ticket_email_templates` (`event_type`, `subject`, `body`, `updated_at`)
VALUES ('solved',
'Ticket #{{ticket_id}} wurde als gelöst markiert',
'<!DOCTYPE html>
<html lang="de">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Ticket gelöst</title>
</head>
<body style="margin:0;padding:0;background:#f3f4f6;font-family:Arial,Helvetica,sans-serif;">
  <table width="100%" cellpadding="0" cellspacing="0" style="background:#f3f4f6;padding:32px 16px;">
    <tr><td align="center">
      <table width="600" cellpadding="0" cellspacing="0" style="max-width:600px;width:100%;background:#ffffff;border-radius:12px;overflow:hidden;box-shadow:0 2px 12px rgba(0,0,0,0.08);">

        <tr>
          <td style="background:linear-gradient(135deg,#065f46 0%,#059669 100%);padding:28px 36px;">
            <table width="100%" cellpadding="0" cellspacing="0">
              <tr>
                <td>
                  <div style="color:#ffffff;font-size:22px;font-weight:700;">{{app_name}}</div>
                </td>
                <td align="right">
                  <div style="background:rgba(255,255,255,0.2);border-radius:8px;padding:8px 16px;display:inline-block;">
                    <div style="color:rgba(255,255,255,0.8);font-size:11px;font-weight:600;text-transform:uppercase;">Ticket</div>
                    <div style="color:#ffffff;font-size:20px;font-weight:700;">#{{ticket_id}}</div>
                  </div>
                </td>
              </tr>
            </table>
          </td>
        </tr>

        <tr>
          <td style="background:#f0fdf4;border-bottom:2px solid #bbf7d0;padding:14px 36px;">
            <table cellpadding="0" cellspacing="0">
              <tr>
                <td style="width:32px;height:32px;background:#16a34a;border-radius:50%;text-align:center;vertical-align:middle;">
                  <span style="color:#fff;font-size:18px;line-height:32px;">✓</span>
                </td>
                <td style="padding-left:12px;color:#15803d;font-size:14px;font-weight:600;">
                  Ihr Ticket wurde als gelöst markiert
                </td>
              </tr>
            </table>
          </td>
        </tr>

        <tr>
          <td style="padding:32px 36px;">
            <p style="margin:0 0 16px;color:#374151;font-size:16px;">Guten Tag, <strong>{{from_name}}</strong>,</p>
            <p style="margin:0 0 24px;color:#6b7280;font-size:14px;line-height:1.7;">
              Ihr Ticket <strong>„{{ticket_subject}}"</strong> wurde von unserem Support-Team als gelöst markiert. Wir hoffen, dass Ihr Anliegen zur Ihrer Zufriedenheit bearbeitet wurde.
            </p>
            <p style="margin:0 0 24px;color:#6b7280;font-size:14px;line-height:1.7;">
              Sollte das Problem weiterhin bestehen, antworten Sie einfach auf diese E-Mail — wir öffnen das Ticket dann wieder für Sie.
            </p>

            <table width="100%" cellpadding="0" cellspacing="0" style="background:#f9fafb;border:1px solid #e5e7eb;border-radius:10px;overflow:hidden;margin-bottom:28px;">
              <tr>
                <td style="padding:16px 20px;">
                  <table width="100%" cellpadding="0" cellspacing="0">
                    <tr>
                      <td style="color:#9ca3af;font-size:13px;width:120px;padding:4px 0;">Ticket-Nr.</td>
                      <td style="color:#111827;font-size:13px;font-weight:600;padding:4px 0;">#{{ticket_id}}</td>
                    </tr>
                    <tr>
                      <td style="color:#9ca3af;font-size:13px;padding:4px 0;">Betreff</td>
                      <td style="color:#111827;font-size:13px;padding:4px 0;">{{ticket_subject}}</td>
                    </tr>
                    <tr>
                      <td style="color:#9ca3af;font-size:13px;padding:4px 0;">Status</td>
                      <td style="padding:4px 0;">
                        <span style="background:#d1fae5;color:#065f46;font-size:12px;font-weight:600;padding:2px 10px;border-radius:20px;">Gelöst</span>
                      </td>
                    </tr>
                  </table>
                </td>
              </tr>
            </table>
          </td>
        </tr>

        <tr>
          <td style="background:#f9fafb;border-top:1px solid #e5e7eb;padding:20px 36px;text-align:center;">
            <p style="margin:0 0 4px;color:#9ca3af;font-size:12px;">Diese E-Mail wurde automatisch vom Support-System gesendet.</p>
            <p style="margin:0;color:#9ca3af;font-size:12px;">© {{app_name}}</p>
          </td>
        </tr>

      </table>
    </td></tr>
  </table>
</body>
</html>',
NOW());

-- ============================================================
-- VORLAGE 5: Ticket geschlossen
-- ============================================================
INSERT IGNORE INTO `ticket_email_templates` (`event_type`, `subject`, `body`, `updated_at`)
VALUES ('closed',
'Ticket #{{ticket_id}} wurde geschlossen',
'<!DOCTYPE html>
<html lang="de">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Ticket geschlossen</title>
</head>
<body style="margin:0;padding:0;background:#f3f4f6;font-family:Arial,Helvetica,sans-serif;">
  <table width="100%" cellpadding="0" cellspacing="0" style="background:#f3f4f6;padding:32px 16px;">
    <tr><td align="center">
      <table width="600" cellpadding="0" cellspacing="0" style="max-width:600px;width:100%;background:#ffffff;border-radius:12px;overflow:hidden;box-shadow:0 2px 12px rgba(0,0,0,0.08);">

        <tr>
          <td style="background:linear-gradient(135deg,#374151 0%,#4b5563 100%);padding:28px 36px;">
            <table width="100%" cellpadding="0" cellspacing="0">
              <tr>
                <td>
                  <div style="color:#ffffff;font-size:22px;font-weight:700;">{{app_name}}</div>
                </td>
                <td align="right">
                  <div style="background:rgba(255,255,255,0.2);border-radius:8px;padding:8px 16px;display:inline-block;">
                    <div style="color:rgba(255,255,255,0.8);font-size:11px;font-weight:600;text-transform:uppercase;">Ticket</div>
                    <div style="color:#ffffff;font-size:20px;font-weight:700;">#{{ticket_id}}</div>
                  </div>
                </td>
              </tr>
            </table>
          </td>
        </tr>

        <tr>
          <td style="background:#f9fafb;border-bottom:2px solid #e5e7eb;padding:14px 36px;">
            <table cellpadding="0" cellspacing="0">
              <tr>
                <td style="width:32px;height:32px;background:#6b7280;border-radius:50%;text-align:center;vertical-align:middle;">
                  <span style="color:#fff;font-size:14px;line-height:32px;">✕</span>
                </td>
                <td style="padding-left:12px;color:#374151;font-size:14px;font-weight:600;">
                  Ihr Ticket wurde geschlossen
                </td>
              </tr>
            </table>
          </td>
        </tr>

        <tr>
          <td style="padding:32px 36px;">
            <p style="margin:0 0 16px;color:#374151;font-size:16px;">Guten Tag, <strong>{{from_name}}</strong>,</p>
            <p style="margin:0 0 24px;color:#6b7280;font-size:14px;line-height:1.7;">
              Ihr Ticket <strong>„{{ticket_subject}}"</strong> wurde geschlossen. Vielen Dank für Ihre Geduld.
            </p>
            <p style="margin:0 0 24px;color:#6b7280;font-size:14px;line-height:1.7;">
              Falls Sie weitere Unterstützung benötigen, erstellen Sie gerne ein neues Ticket oder kontaktieren Sie uns direkt.
            </p>

            <table width="100%" cellpadding="0" cellspacing="0" style="background:#f9fafb;border:1px solid #e5e7eb;border-radius:10px;overflow:hidden;margin-bottom:28px;">
              <tr>
                <td style="padding:16px 20px;">
                  <table width="100%" cellpadding="0" cellspacing="0">
                    <tr>
                      <td style="color:#9ca3af;font-size:13px;width:120px;padding:4px 0;">Ticket-Nr.</td>
                      <td style="color:#111827;font-size:13px;font-weight:600;padding:4px 0;">#{{ticket_id}}</td>
                    </tr>
                    <tr>
                      <td style="color:#9ca3af;font-size:13px;padding:4px 0;">Betreff</td>
                      <td style="color:#111827;font-size:13px;padding:4px 0;">{{ticket_subject}}</td>
                    </tr>
                    <tr>
                      <td style="color:#9ca3af;font-size:13px;padding:4px 0;">Status</td>
                      <td style="padding:4px 0;">
                        <span style="background:#f3f4f6;color:#374151;font-size:12px;font-weight:600;padding:2px 10px;border-radius:20px;">Geschlossen</span>
                      </td>
                    </tr>
                  </table>
                </td>
              </tr>
            </table>
          </td>
        </tr>

        <tr>
          <td style="background:#f9fafb;border-top:1px solid #e5e7eb;padding:20px 36px;text-align:center;">
            <p style="margin:0 0 4px;color:#9ca3af;font-size:12px;">Diese E-Mail wurde automatisch vom Support-System gesendet.</p>
            <p style="margin:0;color:#9ca3af;font-size:12px;">© {{app_name}}</p>
          </td>
        </tr>

      </table>
    </td></tr>
  </table>
</body>
</html>',
NOW());

-- Migration: 040_kanban_done.sql
-- ============================================================
-- 040 Kanban: done-Flag für Aufgaben
-- ============================================================

ALTER TABLE `kanban_tasks`
    ADD COLUMN `done` TINYINT(1) NOT NULL DEFAULT 0 AFTER `sort_order`;

ALTER TABLE `kanban_tasks`
    ADD INDEX `idx_kanban_tasks_done` (`done`);

-- Migration: 041_fix_capability_categories.sql
-- Migration 041: Fix capability categories for modules added via migrations
-- These capabilities were inserted with the correct category but the DB may differ
-- if seeded from older versions or if INSERT IGNORE masked an update.

UPDATE `capabilities` SET `category` = 'tickets' WHERE `name` IN ('view_tickets', 'manage_tickets', 'admin_tickets')   AND (`category` IS NULL OR `category` = '');
UPDATE `capabilities` SET `category` = 'ssl'     WHERE `name` IN ('view_ssl', 'manage_ssl')                            AND (`category` IS NULL OR `category` = '');
UPDATE `capabilities` SET `category` = 'kanban'  WHERE `name` IN ('view_kanban', 'manage_kanban')                      AND (`category` IS NULL OR `category` = '');
UPDATE `capabilities` SET `category` = 'vault'   WHERE `name` IN ('view_vault', 'manage_vault_teams', 'admin_vault_teams') AND (`category` IS NULL OR `category` = '');
UPDATE `capabilities` SET `category` = 'ipam'    WHERE `name` IN ('view_ipam', 'manage_ipam_ips', 'manage_ipam_subnets')  AND (`category` IS NULL OR `category` = '');
UPDATE `capabilities` SET `category` = 'loan'    WHERE `name` IN ('view_loan_devices', 'manage_loan_devices', 'issue_loan_devices') AND (`category` IS NULL OR `category` = '');
UPDATE `capabilities` SET `category` = 'contract' WHERE `name` IN ('view_contracts', 'manage_contracts')               AND (`category` IS NULL OR `category` = '');

-- Migration: 041_personal_todos.sql
-- ============================================================
-- 041: Persönliche To-Do-Liste
-- ============================================================

CREATE TABLE IF NOT EXISTS `personal_todos` (
    `id`         INT UNSIGNED   NOT NULL AUTO_INCREMENT,
    `user_id`    BIGINT UNSIGNED NOT NULL,
    `title`      VARCHAR(500)   NOT NULL,
    `done`       TINYINT(1)     NOT NULL DEFAULT 0,
    `sort_order` INT            NOT NULL DEFAULT 0,
    `done_at`    DATETIME       NULL,
    `created_at` DATETIME       NOT NULL DEFAULT CURRENT_TIMESTAMP,
    PRIMARY KEY (`id`),
    KEY `idx_todos_user` (`user_id`),
    CONSTRAINT `fk_todos_user`
        FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

-- Migration: 042_audit_category_varchar.sql
-- ============================================================
-- Migration 042: action_category und capabilities.category von ENUM auf VARCHAR
--
-- Warum: ENUM-Spalten muessen bei jedem neuen Wert in ALLEN vorherigen
-- Migrationen nachgepflegt werden, damit ein Rerun-All keine bestehenden
-- Eintraege abschneidet (SQLSTATE 01000 / 1265 Data truncated).
-- Als VARCHAR reicht es, den neuen Wert einfach im Code zu verwenden –
-- keine Schema-Aenderung mehr noetig.
--
-- MySQL konvertiert ENUM → VARCHAR verlustfrei (alle Werte bleiben erhalten).
-- ============================================================

-- audit_log.action_category: ENUM → VARCHAR(50)
ALTER TABLE `audit_log`
    MODIFY COLUMN `action_category` VARCHAR(50) NOT NULL DEFAULT '';

-- capabilities.category: ENUM → VARCHAR(50)
-- (betroffen: Migrationen 004, 005, 006, 011, 023, 026)
ALTER TABLE `capabilities`
    MODIFY COLUMN `category` VARCHAR(50) NOT NULL DEFAULT '';

-- Migration: 043_assets.sql
-- ============================================================
-- 043 Inventar / Asset-Management
-- ============================================================

-- Kategorien (Client, Server, Netzwerk, ...)
CREATE TABLE IF NOT EXISTS `asset_categories` (
    `id`                    INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    `name`                  VARCHAR(255) NOT NULL,
    `icon`                  VARCHAR(50)  NOT NULL DEFAULT '🖥️',
    `color`                 VARCHAR(7)   NOT NULL DEFAULT '#6366f1',
    `number_format`         VARCHAR(255) NOT NULL DEFAULT 'INV-%Jahr%-%fortlaufendenummer%',
    `counter_reset_yearly`  TINYINT(1)   NOT NULL DEFAULT 0,
    `sort_order`            INT          NOT NULL DEFAULT 0,
    `created_at`            DATETIME     NOT NULL DEFAULT CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

-- Hersteller (Dell, HP, Cisco, ...)
CREATE TABLE IF NOT EXISTS `asset_manufacturers` (
    `id`         INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    `name`       VARCHAR(255) NOT NULL,
    `website`    VARCHAR(500) NULL,
    `notes`      TEXT         NULL,
    `created_at` DATETIME     NOT NULL DEFAULT CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

-- Typen / Modelle (Latitude 3530, ProLiant DL360, ...)
CREATE TABLE IF NOT EXISTS `asset_types` (
    `id`              INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    `name`            VARCHAR(255) NOT NULL,
    `manufacturer_id` INT UNSIGNED NOT NULL,
    `category_id`     INT UNSIGNED NULL COMMENT 'Optionale Kategorie-Einschraenkung',
    `description`     TEXT         NULL,
    `specs`           TEXT         NULL COMMENT 'Technische Daten (frei)',
    `created_at`      DATETIME     NOT NULL DEFAULT CURRENT_TIMESTAMP,
    FOREIGN KEY (`manufacturer_id`) REFERENCES `asset_manufacturers`(`id`) ON DELETE CASCADE,
    FOREIGN KEY (`category_id`)     REFERENCES `asset_categories`(`id`) ON DELETE SET NULL,
    INDEX (`manufacturer_id`),
    INDEX (`category_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

-- Bilder pro Typ
CREATE TABLE IF NOT EXISTS `asset_type_images` (
    `id`         INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    `type_id`    INT UNSIGNED NOT NULL,
    `filename`   VARCHAR(500) NOT NULL,
    `sort_order` INT          NOT NULL DEFAULT 0,
    FOREIGN KEY (`type_id`) REFERENCES `asset_types`(`id`) ON DELETE CASCADE,
    INDEX (`type_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

-- Sequenz-Zaehler pro Kategorie (und optional Jahr)
CREATE TABLE IF NOT EXISTS `asset_number_sequences` (
    `id`          INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    `category_id` INT UNSIGNED NOT NULL,
    `year`        SMALLINT UNSIGNED NOT NULL DEFAULT 0 COMMENT '0 = kein Jahres-Reset',
    `counter`     INT UNSIGNED NOT NULL DEFAULT 0,
    UNIQUE KEY `uq_category_year` (`category_id`, `year`),
    FOREIGN KEY (`category_id`) REFERENCES `asset_categories`(`id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

-- Assets (Geraete)
CREATE TABLE IF NOT EXISTS `assets` (
    `id`               INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    `inventory_number` VARCHAR(100) NOT NULL UNIQUE,
    `serial_number`    VARCHAR(255) NULL,
    `category_id`      INT UNSIGNED NOT NULL,
    `manufacturer_id`  INT UNSIGNED NULL,
    `type_id`          INT UNSIGNED NULL,
    `location_id`      INT UNSIGNED NULL COMMENT 'FK zu ticket_locations',
    `assigned_user_id` BIGINT UNSIGNED NULL,
    `status`           ENUM('active','storage','decommissioned') NOT NULL DEFAULT 'active',
    `purchase_date`    DATE         NULL,
    `warranty_until`   DATE         NULL,
    `notes`            TEXT         NULL,
    `created_by`       BIGINT UNSIGNED NULL,
    `created_at`       DATETIME     NOT NULL DEFAULT CURRENT_TIMESTAMP,
    `updated_at`       DATETIME     NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
    FOREIGN KEY (`category_id`)      REFERENCES `asset_categories`(`id`)    ON DELETE RESTRICT,
    FOREIGN KEY (`manufacturer_id`)  REFERENCES `asset_manufacturers`(`id`) ON DELETE SET NULL,
    FOREIGN KEY (`type_id`)          REFERENCES `asset_types`(`id`)         ON DELETE SET NULL,
    FOREIGN KEY (`assigned_user_id`) REFERENCES `users`(`id`)               ON DELETE SET NULL,
    FOREIGN KEY (`created_by`)       REFERENCES `users`(`id`)               ON DELETE SET NULL,
    INDEX (`category_id`),
    INDEX (`manufacturer_id`),
    INDEX (`type_id`),
    INDEX (`status`),
    INDEX (`assigned_user_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

-- Capabilities
INSERT IGNORE INTO `capabilities` (`name`, `description`, `category`)
VALUES
    ('view_assets',   'Inventar einsehen',              'assets'),
    ('manage_assets', 'Inventar verwalten (CRUD)',       'assets'),
    ('admin_assets',  'Inventar-Kategorien & Typen verwalten', 'assets');

-- Standard-Kategorien
INSERT IGNORE INTO `asset_categories` (`name`, `icon`, `color`, `number_format`, `counter_reset_yearly`, `sort_order`)
VALUES
    ('Client',    '💻', '#2563eb', 'CLI-%Jahr%-%fortlaufendenummer%', 1, 1),
    ('Server',    '🖥️', '#7c3aed', 'SRV-%Jahr%-%fortlaufendenummer%', 1, 2),
    ('Netzwerk',  '🌐', '#0891b2', 'NET-%Jahr%-%fortlaufendenummer%', 1, 3),
    ('Drucker',   '🖨️', '#65a30d', 'DRU-%Jahr%-%fortlaufendenummer%', 1, 4),
    ('Smartphone','📱', '#dc2626', 'MOB-%Jahr%-%fortlaufendenummer%', 1, 5),
    ('Sonstiges', '📦', '#9ca3af', 'SON-%Jahr%-%fortlaufendenummer%', 0, 99);

-- Migration: 044_assets_assigned_to.sql
-- Add free-text assigned_to field for asset assignment (replaces FK-based assigned_user_id in forms)
ALTER TABLE `assets` ADD COLUMN `assigned_to` VARCHAR(255) NULL DEFAULT NULL AFTER `assigned_user_id`;

-- Migration: 045_asset_categories_unique_name.sql
-- ============================================================
-- 045 Asset-Kategorien: UNIQUE KEY auf name + Duplikate bereinigen
-- ============================================================

-- Duplikate entfernen: nur den Eintrag mit der kleinsten ID behalten
DELETE ac1 FROM `asset_categories` ac1
INNER JOIN `asset_categories` ac2
    ON ac1.name = ac2.name AND ac1.id > ac2.id;

-- UNIQUE KEY hinzufügen (damit INSERT IGNORE bei Re-Run greift)
ALTER TABLE `asset_categories` ADD UNIQUE KEY `uq_asset_categories_name` (`name`);

-- Migration: 046_todo_notes.sql
-- ============================================================
-- 046: Notiz-Feld für persönliche To-Dos
-- ============================================================

ALTER TABLE `personal_todos`
    ADD COLUMN `notes` TEXT NULL DEFAULT NULL AFTER `title`;

-- Migration: 047_ssl_reminder.sql
-- ============================================================
-- 047: SSL-Zertifikat-Ablauferinnerung
-- ============================================================

-- Tracking-Feld: speichert cert_valid_to-Wert zum Zeitpunkt der Benachrichtigung,
-- um Duplikate zu verhindern und bei Zertifikatserneuerung erneut zu benachrichtigen
ALTER TABLE `ssl_entries`
    ADD COLUMN `ssl_reminder_sent_at` DATE NULL DEFAULT NULL AFTER `cert_days_left`;

-- In-App Benachrichtigungs-Vorlage
INSERT INTO notification_templates (template_key, title_template, message_template, icon, type, description)
VALUES (
    'ssl_expiry',
    'SSL-Zertifikat läuft ab: {{host}}',
    'Das SSL-Zertifikat für {{host}} läuft in {{days_left}} Tagen ab ({{expiry_date}}).',
    '🔒',
    'warning',
    'Benachrichtigung 5 Tage vor Ablauf eines SSL-Zertifikats'
)
ON DUPLICATE KEY UPDATE template_key = template_key;

-- E-Mail-Vorlage (konfigurierbar unter Einstellungen → E-Mail-Vorlagen)
INSERT INTO email_templates (template_key, subject, body_html, description)
VALUES (
    'ssl_expiry',
    '[{{app_name}}] SSL-Zertifikat läuft ab: {{host}}',
    '<p>Hallo {{user_name}},</p>
<p>das SSL-Zertifikat für <strong>{{host}}</strong> läuft in <strong>{{days_left}} Tagen</strong> ab.</p>
<table style="border-collapse:collapse;width:100%;max-width:520px;margin:1rem 0;">
  <tr><td style="padding:8px 12px;border-bottom:1px solid #e5e7eb;font-weight:600;background:#f9fafb;width:40%;">Host</td><td style="padding:8px 12px;border-bottom:1px solid #e5e7eb;">{{host}}</td></tr>
  <tr><td style="padding:8px 12px;border-bottom:1px solid #e5e7eb;font-weight:600;background:#f9fafb;">Label</td><td style="padding:8px 12px;border-bottom:1px solid #e5e7eb;">{{label}}</td></tr>
  <tr><td style="padding:8px 12px;border-bottom:1px solid #e5e7eb;font-weight:600;background:#f9fafb;">Ablaufdatum</td><td style="padding:8px 12px;border-bottom:1px solid #e5e7eb;color:#dc2626;font-weight:600;">{{expiry_date}} (in {{days_left}} Tagen)</td></tr>
  <tr><td style="padding:8px 12px;border-bottom:1px solid #e5e7eb;font-weight:600;background:#f9fafb;">Aussteller</td><td style="padding:8px 12px;border-bottom:1px solid #e5e7eb;">{{issuer}}</td></tr>
</table>
<p style="margin-top:1.5rem;">
  <a href="{{ssl_url}}" style="background:#2563eb;color:#fff;padding:10px 20px;text-decoration:none;border-radius:6px;display:inline-block;">SSL-Checker öffnen</a>
</p>
<p>Freundliche Grüße<br>{{app_name}}</p>',
    'Wird 5 Tage vor Ablauf eines SSL-Zertifikats gesendet. Konfigurierbare Vorlage.'
)
ON DUPLICATE KEY UPDATE template_key = template_key;

-- Migration: 048_location_prefix.sql
-- Migration 048: Standort-Präfix für Inventarnummern
-- Fügt ein optionales Präfix-Feld zu Hauptstandorten hinzu,
-- das als %standortpräfix% in Inventarnummer-Formaten verwendet werden kann.

ALTER TABLE `ticket_locations`
    ADD COLUMN `prefix` VARCHAR(10) NULL DEFAULT NULL
    COMMENT 'Kurz-Präfix für Inventarnummern (nur Hauptstandorte empfohlen)';

-- Migration: 049_directory_persons.sql
-- Migration 049: Verzeichnis-Personen (Entra / LDAP / Manuell)
-- Interne Personenliste für Asset-Zuweisung und Ticket-Einsender.
-- Personen sind KEINE Tool-Benutzer (kein Login), sondern reine Datensätze.

CREATE TABLE IF NOT EXISTS `directory_persons` (
    `id`             INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    `source`         ENUM('entra','ldap','manual') NOT NULL DEFAULT 'manual'
                     COMMENT 'Quelle: entra=Azure AD, ldap=Active Directory, manual=manuell',
    `external_id`    VARCHAR(500)  NULL COMMENT 'Azure objectId oder LDAP DN',
    `display_name`   VARCHAR(255)  NOT NULL,
    `first_name`     VARCHAR(100)  NULL,
    `last_name`      VARCHAR(100)  NULL,
    `email`          VARCHAR(255)  NULL,
    `department`     VARCHAR(255)  NULL,
    `job_title`      VARCHAR(255)  NULL,
    `phone`          VARCHAR(100)  NULL,
    `is_active`      TINYINT(1)   NOT NULL DEFAULT 1,
    `last_synced_at` DATETIME     NULL,
    `created_at`     DATETIME     NOT NULL DEFAULT CURRENT_TIMESTAMP,
    `updated_at`     DATETIME     NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
    UNIQUE KEY `uq_person_external` (`source`, `external_id`(191)),
    INDEX `idx_person_email`  (`email`),
    INDEX `idx_person_name`   (`display_name`(64)),
    INDEX `idx_person_active` (`is_active`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

-- Migration: 050_asset_person_id.sql
-- Migration 050: Verzeichnis-Person-Verknüpfung für Asset-Zuweisung
-- Ergänzt die bestehende assigned_to (Freitext) um eine FK auf directory_persons.

ALTER TABLE `assets`
    ADD COLUMN `assigned_person_id` INT UNSIGNED NULL DEFAULT NULL
        COMMENT 'FK zu directory_persons (optional, ergänzt assigned_to)'
        AFTER `assigned_to`;

ALTER TABLE `assets`
    ADD CONSTRAINT `fk_assets_directory_person`
    FOREIGN KEY (`assigned_person_id`) REFERENCES `directory_persons`(`id`)
    ON DELETE SET NULL;

-- Migration: 051_ticket_requester_person_id.sql
-- Migration 051: Verzeichnis-Person-Verknüpfung für Ticket-Einsender
-- Ergänzt from_name/from_email um eine FK auf directory_persons.
-- Bei bekannter E-Mail (IMAP-Eingang oder manuelle Auswahl) wird die Person automatisch verknüpft.

ALTER TABLE `tickets`
    ADD COLUMN `requester_person_id` INT UNSIGNED NULL DEFAULT NULL
        COMMENT 'FK zu directory_persons (auto-match über E-Mail oder manuell)'
        AFTER `from_name`;

ALTER TABLE `tickets`
    ADD CONSTRAINT `fk_tickets_requester_person`
    FOREIGN KEY (`requester_person_id`) REFERENCES `directory_persons`(`id`)
    ON DELETE SET NULL;

-- Migration: 052_lending_borrower_person_id.sql
-- Migration 052: Ausleiher-Person-Verknüpfung für loan_lendings
-- Ergänzt borrower_name/borrower_email um eine FK auf directory_persons.
-- Ermöglicht die Verknüpfung des Ausleihers mit einem Eintrag aus dem Personenverzeichnis.

ALTER TABLE `loan_lendings`
    ADD COLUMN `borrower_person_id` INT UNSIGNED NULL DEFAULT NULL
        COMMENT 'FK zu directory_persons (manuell oder Auswahl aus Verzeichnis)'
        AFTER `borrower_name`;

ALTER TABLE `loan_lendings`
    ADD CONSTRAINT `fk_loan_lendings_borrower_person`
    FOREIGN KEY (`borrower_person_id`) REFERENCES `directory_persons`(`id`)
    ON DELETE SET NULL;

-- Migration: 053_loan_overdue_notification.sql
-- Überfälligkeits-Tracking für Leihvorgänge
ALTER TABLE loan_lendings
    ADD COLUMN overdue_notified_at DATETIME DEFAULT NULL;

-- E-Mail-Vorlage für Überfälligkeitserinnerung (Leihgeräte)
INSERT INTO email_templates (template_key, subject, body_html, description)
VALUES (
    'loan_overdue',
    'Erinnerung: Leihgerät «{{device_title}}» ist überfällig',
    '<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
</head>
<body style="margin:0;padding:0;background:#f3f4f6;font-family:-apple-system,BlinkMacSystemFont,''Segoe UI'',Roboto,''Helvetica Neue'',Arial,sans-serif;">
<table width="100%" cellpadding="0" cellspacing="0" style="background:#f3f4f6;padding:32px 16px;">
  <tr><td align="center">
    <table width="600" cellpadding="0" cellspacing="0" style="max-width:600px;width:100%;background:#fff;border-radius:12px;overflow:hidden;box-shadow:0 2px 8px rgba(0,0,0,0.08);">

      <!-- Header -->
      <tr>
        <td style="background:linear-gradient(135deg,#78350f 0%,#d97706 60%,#fbbf24 100%);padding:32px 40px;text-align:center;">
          <div style="font-size:28px;font-weight:900;color:#fff;letter-spacing:-0.5px;">{{app_name}}</div>
          <div style="font-size:13px;color:rgba(255,255,255,0.75);margin-top:4px;">IT-Dokumentation</div>
        </td>
      </tr>

      <!-- Warnung-Banner -->
      <tr>
        <td style="background:#fef2f2;border-left:4px solid #dc2626;padding:16px 40px;">
          <p style="margin:0;font-size:15px;font-weight:700;color:#991b1b;">⚠️ Leihgerät überfällig</p>
          <p style="margin:4px 0 0;font-size:13px;color:#b91c1c;">Das geplante Rückgabedatum ist bereits überschritten.</p>
        </td>
      </tr>

      <!-- Body -->
      <tr>
        <td style="padding:32px 40px;">
          <p style="margin:0 0 16px;font-size:16px;color:#1f2937;">Hallo <strong>{{borrower_name}}</strong>,</p>
          <p style="margin:0 0 20px;font-size:15px;color:#374151;line-height:1.7;">
            wir möchten Sie freundlich daran erinnern, dass das folgende Leihgerät bereits seit dem
            <strong>{{planned_end}}</strong> zur Rückgabe fällig war:
          </p>

          <!-- Gerät-Box -->
          <table width="100%" cellpadding="0" cellspacing="0" style="background:#f9fafb;border:1px solid #e5e7eb;border-radius:8px;margin-bottom:24px;">
            <tr>
              <td style="padding:20px 24px;">
                <div style="font-size:18px;font-weight:700;color:#111827;margin-bottom:8px;">💻 {{device_title}}</div>
                <table cellpadding="0" cellspacing="0">
                  <tr>
                    <td style="font-size:13px;color:#9ca3af;padding-right:8px;">Typ:</td>
                    <td style="font-size:13px;color:#374151;">{{device_type}}</td>
                  </tr>
                  <tr>
                    <td style="font-size:13px;color:#9ca3af;padding-right:8px;">Ausgeliehen seit:</td>
                    <td style="font-size:13px;color:#374151;">{{planned_start}}</td>
                  </tr>
                  <tr>
                    <td style="font-size:13px;color:#9ca3af;padding-right:8px;">Geplante Rückgabe:</td>
                    <td style="font-size:13px;color:#dc2626;font-weight:700;">{{planned_end}}</td>
                  </tr>
                  {{#serial}}<tr>
                    <td style="font-size:13px;color:#9ca3af;padding-right:8px;">Seriennummer:</td>
                    <td style="font-size:13px;color:#374151;font-family:monospace;">{{serial_number}}</td>
                  </tr>{{/serial}}
                </table>
              </td>
            </tr>
          </table>

          <p style="margin:0 0 20px;font-size:15px;color:#374151;line-height:1.7;">
            Bitte geben Sie das Gerät so bald wie möglich zurück.<br>
            Falls Sie das Gerät länger benötigen, melden Sie sich bitte beim Ausgeber, damit der Leihvorgang verlängert werden kann.
          </p>

          <!-- CTA Button -->
          <table cellpadding="0" cellspacing="0" style="margin-bottom:24px;">
            <tr>
              <td style="background:#d97706;border-radius:8px;padding:12px 28px;">
                <a href="{{lending_url}}" style="color:#fff;text-decoration:none;font-size:15px;font-weight:700;">Leihvorgang ansehen →</a>
              </td>
            </tr>
          </table>

          <p style="margin:0;font-size:14px;color:#6b7280;line-height:1.7;">
            Mit freundlichen Grüßen<br>
            <strong>{{app_name}}</strong>
          </p>
        </td>
      </tr>

      <!-- Footer -->
      <tr>
        <td style="background:#f9fafb;border-top:1px solid #e5e7eb;padding:20px 40px;text-align:center;">
          <p style="margin:0;font-size:12px;color:#9ca3af;">
            Diese E-Mail wurde automatisch von {{app_name}} generiert.<br>
            <a href="{{base_url}}/loans" style="color:#d97706;text-decoration:none;">Zur Leihgeräte-Übersicht</a>
          </p>
        </td>
      </tr>

    </table>
  </td></tr>
</table>
</body>
</html>',
    'Wird automatisch gesendet wenn ein Leihgerät überfällig ist (geplante Rückgabe überschritten). Empfänger: Ausleiher (CC: Ausgeber).'
)
ON DUPLICATE KEY UPDATE template_key = template_key;

-- Migration: 054_todo_deadline.sql
-- ============================================================
-- 054: Deadline-Unterstützung für persönliche To-Dos
-- ============================================================

ALTER TABLE `personal_todos`
    ADD COLUMN `due_date`        DATE     NULL DEFAULT NULL AFTER `notes`,
    ADD COLUMN `due_notified_at` DATETIME NULL DEFAULT NULL AFTER `due_date`;

-- E-Mail-Vorlage für To-Do-Fälligkeitserinnerung
INSERT INTO email_templates (template_key, subject, body_html, description)
VALUES (
    'todo_due_reminder',
    'Erinnerung: To-Do «{{todo_title}}» ist morgen fällig',
    '<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
</head>
<body style="margin:0;padding:0;background:#f3f4f6;font-family:-apple-system,BlinkMacSystemFont,''Segoe UI'',Roboto,''Helvetica Neue'',Arial,sans-serif;">
<table width="100%" cellpadding="0" cellspacing="0" style="background:#f3f4f6;padding:32px 16px;">
  <tr><td align="center">
    <table width="600" cellpadding="0" cellspacing="0" style="max-width:600px;width:100%;background:#fff;border-radius:12px;overflow:hidden;box-shadow:0 2px 8px rgba(0,0,0,0.08);">

      <!-- Header -->
      <tr>
        <td style="background:linear-gradient(135deg,#4338ca 0%,#6366f1 60%,#818cf8 100%);padding:32px 40px;text-align:center;">
          <div style="font-size:28px;font-weight:900;color:#fff;letter-spacing:-0.5px;">{{app_name}}</div>
          <div style="font-size:13px;color:rgba(255,255,255,0.75);margin-top:4px;">IT-Dokumentation</div>
        </td>
      </tr>

      <!-- Hinweis-Banner -->
      <tr>
        <td style="background:#eff6ff;border-left:4px solid #3b82f6;padding:16px 40px;">
          <p style="margin:0;font-size:15px;font-weight:700;color:#1e40af;">📋 To-Do fällig morgen</p>
          <p style="margin:4px 0 0;font-size:13px;color:#1d4ed8;">Das Fälligkeitsdatum Ihres To-Dos rückt näher.</p>
        </td>
      </tr>

      <!-- Body -->
      <tr>
        <td style="padding:32px 40px;">
          <p style="margin:0 0 16px;font-size:16px;color:#1f2937;">Hallo <strong>{{user_name}}</strong>,</p>
          <p style="margin:0 0 20px;font-size:15px;color:#374151;line-height:1.7;">
            wir möchten Sie daran erinnern, dass folgendes To-Do morgen fällig ist:
          </p>

          <!-- To-Do-Box -->
          <table width="100%" cellpadding="0" cellspacing="0" style="background:#f9fafb;border:1px solid #e5e7eb;border-radius:8px;margin-bottom:24px;">
            <tr>
              <td style="padding:20px 24px;">
                <div style="font-size:18px;font-weight:700;color:#111827;margin-bottom:12px;">✅ {{todo_title}}</div>
                <table cellpadding="0" cellspacing="0">
                  <tr>
                    <td style="font-size:13px;color:#9ca3af;padding-right:8px;">Fällig am:</td>
                    <td style="font-size:13px;color:#4f46e5;font-weight:700;">{{due_date}}</td>
                  </tr>
                  {{#notes}}<tr>
                    <td style="font-size:13px;color:#9ca3af;padding-right:8px;padding-top:6px;vertical-align:top;">Notiz:</td>
                    <td style="font-size:13px;color:#374151;padding-top:6px;">{{notes}}</td>
                  </tr>{{/notes}}
                </table>
              </td>
            </tr>
          </table>

          <!-- CTA Button -->
          <table cellpadding="0" cellspacing="0" style="margin-bottom:24px;">
            <tr>
              <td style="background:#4f46e5;border-radius:8px;padding:12px 28px;">
                <a href="{{todos_url}}" style="color:#fff;text-decoration:none;font-size:15px;font-weight:700;">Zur To-Do-Liste →</a>
              </td>
            </tr>
          </table>

          <p style="margin:0;font-size:14px;color:#6b7280;line-height:1.7;">
            Mit freundlichen Grüßen<br>
            <strong>{{app_name}}</strong>
          </p>
        </td>
      </tr>

      <!-- Footer -->
      <tr>
        <td style="background:#f9fafb;border-top:1px solid #e5e7eb;padding:20px 40px;text-align:center;">
          <p style="margin:0;font-size:12px;color:#9ca3af;">
            Diese E-Mail wurde automatisch von {{app_name}} generiert.<br>
            <a href="{{todos_url}}" style="color:#6366f1;text-decoration:none;">Zur To-Do-Liste</a>
          </p>
        </td>
      </tr>

    </table>
  </td></tr>
</table>
</body>
</html>',
    'Wird einen Tag vor Fälligkeit eines persönlichen To-Dos gesendet. Empfänger: der Ersteller des To-Dos.'
)
ON DUPLICATE KEY UPDATE template_key = template_key;

-- Migration: 055_monitoring_events.sql
CREATE TABLE IF NOT EXISTS `monitoring_events` (
    `id`               INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    `source`           VARCHAR(50)   NOT NULL DEFAULT 'zabbix',
    `external_id`      VARCHAR(255)  NULL,
    `host`             VARCHAR(255)  NOT NULL DEFAULT '',
    `hostname`         VARCHAR(500)  NOT NULL DEFAULT '',
    `trigger_name`     VARCHAR(500)  NOT NULL DEFAULT '',
    `severity`         ENUM('not_classified','information','warning','average','high','disaster') NOT NULL DEFAULT 'warning',
    `status`           ENUM('problem','resolved') NOT NULL DEFAULT 'problem',
    `message`          TEXT          NULL,
    `trigger_url`      VARCHAR(1000) NULL,
    `raw_payload`      LONGTEXT      NULL,
    `created_at`       DATETIME      NOT NULL DEFAULT CURRENT_TIMESTAMP,
    `resolved_at`      DATETIME      NULL,
    `acknowledged_at`  DATETIME      NULL,
    `acknowledged_by`  INT UNSIGNED  NULL,
    INDEX `idx_status`   (`status`),
    INDEX `idx_severity` (`severity`),
    INDEX `idx_created`  (`created_at`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

CREATE TABLE IF NOT EXISTS `monitoring_settings` (
    `id`            INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    `setting_key`   VARCHAR(100) NOT NULL UNIQUE,
    `setting_value` TEXT         NULL,
    `updated_at`    DATETIME     NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

INSERT IGNORE INTO `monitoring_settings` (`setting_key`, `setting_value`)
VALUES ('webhook_token', NULL);

INSERT IGNORE INTO `capabilities` (`name`, `description`, `category`) VALUES
('view_monitoring',   'Monitoring-Ereignisse anzeigen',              'monitoring'),
('manage_monitoring', 'Monitoring-Ereignisse verwalten und löschen', 'monitoring');

-- Migration: 056_monitoring_zabbix_api.sql
INSERT IGNORE INTO `monitoring_settings` (`setting_key`, `setting_value`) VALUES
('zabbix_api_url',   NULL),
('zabbix_api_token', NULL),
('zabbix_api_user',  NULL),
('zabbix_api_pass',  NULL),
('zabbix_last_sync', NULL),
('zabbix_sync_mode', 'webhook');

-- Migration: 057_stock.sql
-- ============================================================
-- 057: Lager / Gerätebestand
-- ============================================================

-- Kategorien (Monitor, Laptop, ThinClient, Maus, ...)
CREATE TABLE IF NOT EXISTS `stock_categories` (
    `id`         INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    `name`       VARCHAR(100) NOT NULL,
    `icon`       VARCHAR(10)  NOT NULL DEFAULT '📦',
    `sort_order` INT          NOT NULL DEFAULT 0,
    `created_at` DATETIME     NOT NULL DEFAULT CURRENT_TIMESTAMP,
    UNIQUE KEY `uq_name` (`name`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

-- Artikel im Lager
CREATE TABLE IF NOT EXISTS `stock_items` (
    `id`            INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    `category_id`   INT UNSIGNED NOT NULL,
    `name`          VARCHAR(255) NOT NULL,
    `description`   TEXT         NULL,
    `model`         VARCHAR(255) NULL COMMENT 'Modellbezeichnung / Artikelnummer',
    `quantity`      INT          NOT NULL DEFAULT 0,
    `min_quantity`  INT          NOT NULL DEFAULT 0 COMMENT 'Mindestbestand für Warnung',
    `location`      VARCHAR(255) NULL COMMENT 'Lagerörtlichkeit (Regal, Raum, ...)',
    `is_active`     TINYINT(1)   NOT NULL DEFAULT 1,
    `created_at`    DATETIME     NOT NULL DEFAULT CURRENT_TIMESTAMP,
    `updated_at`    DATETIME     NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
    FOREIGN KEY (`category_id`) REFERENCES `stock_categories`(`id`) ON DELETE RESTRICT,
    INDEX (`category_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

-- Buchungsbewegungen (Ein- / Ausbuchungen)
CREATE TABLE IF NOT EXISTS `stock_movements` (
    `id`         INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    `item_id`    INT UNSIGNED NOT NULL,
    `type`       ENUM('in','out') NOT NULL,
    `quantity`   INT          NOT NULL,
    `reason`     VARCHAR(500) NULL COMMENT 'Begründung / Empfänger',
    `user_id`    BIGINT UNSIGNED NULL,
    `created_at` DATETIME     NOT NULL DEFAULT CURRENT_TIMESTAMP,
    FOREIGN KEY (`item_id`)  REFERENCES `stock_items`(`id`) ON DELETE CASCADE,
    FOREIGN KEY (`user_id`)  REFERENCES `users`(`id`) ON DELETE SET NULL,
    INDEX (`item_id`),
    INDEX (`created_at`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

-- Standard-Kategorien
INSERT IGNORE INTO `stock_categories` (`name`, `icon`, `sort_order`) VALUES
    ('Laptop',      '💻', 10),
    ('Monitor',     '🖥️', 20),
    ('ThinClient',  '🖳', 30),
    ('Maus',        '🖱️', 40),
    ('Tastatur',    '⌨️', 50),
    ('Headset',     '🎧', 60),
    ('Netzteil',    '🔌', 70),
    ('Sonstiges',   '📦', 99);

-- Capabilities
INSERT IGNORE INTO `capabilities` (`name`, `description`, `category`) VALUES
    ('view_stock',   'Lagerbestand einsehen',           'stock'),
    ('manage_stock', 'Lagerbestand verwalten (Ein-/Ausbuchen, Artikel anlegen)', 'stock');

-- Migration: 058_ticket_delete_capability.sql
-- Migration: Add delete_closed_tickets capability
-- Allows users with this right to delete closed tickets from the frontend

INSERT IGNORE INTO `capabilities` (`name`, `description`, `category`) VALUES
('delete_closed_tickets', 'Geschlossene Tickets löschen', 'tickets');

-- Migration: 059_ticket_internal_notes.sql
ALTER TABLE `ticket_replies`
    ADD COLUMN `is_internal` TINYINT(1) NOT NULL DEFAULT 0 AFTER `body`;

-- Migration: 060_dell_warranty_cache.sql
-- ============================================================
-- 060 Dell Warranty Cache
-- ============================================================
CREATE TABLE IF NOT EXISTS `dell_warranty_cache` (
    `service_tag`   VARCHAR(50)  NOT NULL PRIMARY KEY,
    `raw_json`      MEDIUMTEXT   NOT NULL,
    `fetched_at`    DATETIME     NOT NULL DEFAULT CURRENT_TIMESTAMP,
    `error`         VARCHAR(500) NULL COMMENT 'Fehlermeldung falls Abruf fehlschlug'
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

-- Migration: 061_hp_warranty_cache.sql
-- ============================================================
-- 061 HP Warranty Cache
-- ============================================================
CREATE TABLE IF NOT EXISTS `hp_warranty_cache` (
    `serial_number` VARCHAR(100) NOT NULL PRIMARY KEY,
    `raw_json`      MEDIUMTEXT   NOT NULL,
    `fetched_at`    DATETIME     NOT NULL DEFAULT CURRENT_TIMESTAMP,
    `error`         VARCHAR(500) NULL COMMENT 'Fehlermeldung falls Abruf fehlschlug'
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

-- Migration: 062_loan_signatures.sql
-- Migration 062: Unterschriften-Felder für Leihvorgänge
-- Ermöglicht das Einholen von Unterschriften via Tablet-Seite

ALTER TABLE loan_lendings
    ADD COLUMN signature_token          VARCHAR(64)  NULL DEFAULT NULL AFTER return_notes,
    ADD COLUMN signature_requested_at   DATETIME     NULL DEFAULT NULL AFTER signature_token,
    ADD COLUMN signature_data           LONGTEXT     NULL DEFAULT NULL AFTER signature_requested_at,
    ADD COLUMN signature_obtained_at    DATETIME     NULL DEFAULT NULL AFTER signature_data,
    ADD INDEX idx_loan_signature_token (signature_token);

-- Migration: 063_manufacturer_support_url.sql
-- Migration 063: Support-URL bei Herstellern
ALTER TABLE `asset_manufacturers`
    ADD COLUMN `support_url` VARCHAR(500) NULL AFTER `website`;

-- Migration: 064_unifi.sql
-- ============================================================
-- Migration: UniFi Controller Integration
-- Version: 1.0.64
-- ============================================================

SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;

-- Gecachte Gerätedaten aus UniFi (Switches, APs, Gateways)
CREATE TABLE IF NOT EXISTS `unifi_devices` (
    `id`          INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    `site_id`     VARCHAR(100)  NOT NULL                COMMENT 'UniFi Site-ID (z.B. default)',
    `site_name`   VARCHAR(255)  NOT NULL DEFAULT ''     COMMENT 'Anzeigename der Site',
    `device_id`   VARCHAR(255)  NOT NULL                COMMENT 'Interner UniFi _id',
    `mac`         VARCHAR(17)   NOT NULL                COMMENT 'MAC-Adresse des Geräts',
    `ip`          VARCHAR(45)   NULL                    COMMENT 'IP-Adresse',
    `name`        VARCHAR(255)  NULL                    COMMENT 'Gerätename (alias oder hostname)',
    `model`       VARCHAR(100)  NULL                    COMMENT 'Modellbezeichnung',
    `type`        VARCHAR(10)   NOT NULL DEFAULT 'other' COMMENT 'uap|usw|ugw|udm|uxg|ubb|other',
    `state`       TINYINT UNSIGNED NOT NULL DEFAULT 0   COMMENT '0=offline,1=online,2=pending,4=upgrading',
    `uptime`      INT UNSIGNED  NULL                    COMMENT 'Uptime in Sekunden',
    `firmware`    VARCHAR(50)   NULL                    COMMENT 'Firmware-Version',
    `last_seen`   DATETIME      NULL,
    `fetched_at`  DATETIME      NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
    UNIQUE KEY `uq_site_device`   (site_id, device_id),
    KEY `idx_unifi_site`  (site_id),
    KEY `idx_unifi_state` (state)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
COMMENT='Gecachte UniFi Gerätedaten pro Site';

-- Gecachte Netzwerk-/VLAN-Daten
CREATE TABLE IF NOT EXISTS `unifi_networks` (
    `id`          INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    `site_id`     VARCHAR(100)  NOT NULL,
    `site_name`   VARCHAR(255)  NOT NULL DEFAULT '',
    `network_id`  VARCHAR(255)  NOT NULL                COMMENT 'UniFi _id des Netzwerks',
    `name`        VARCHAR(255)  NOT NULL                COMMENT 'Netzwerkname',
    `vlan_id`     SMALLINT UNSIGNED NULL                COMMENT 'VLAN-Tag (1-4094)',
    `subnet`      VARCHAR(50)   NULL                    COMMENT 'Subnetz in CIDR-Notation',
    `purpose`     VARCHAR(50)   NULL                    COMMENT 'corporate|guest|wan|...',
    `is_guest`    TINYINT(1)    NOT NULL DEFAULT 0      COMMENT '1 wenn Gast-WLAN Voucher-Auth',
    `fetched_at`  DATETIME      NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
    UNIQUE KEY `uq_site_network` (site_id, network_id),
    KEY `idx_unifi_net_site` (site_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
COMMENT='Gecachte UniFi Netzwerk- und VLAN-Daten';

-- Capabilities (category ist seit Migration 042 VARCHAR(50))
INSERT IGNORE INTO `capabilities` (`name`, `description`, `category`, `risk_level`) VALUES
    ('view_unifi',            'UniFi Gerätestatus anzeigen',          'unifi', 'low'),
    ('manage_unifi',          'UniFi IPAM- und VLAN-Sync ausführen',  'unifi', 'medium'),
    ('manage_unifi_vouchers', 'UniFi Gast-Voucher erstellen',         'unifi', 'medium');

SET FOREIGN_KEY_CHECKS = 1;

-- Migration: 065_system_settings.sql
-- Migration 065: system_settings Tabelle
-- Allgemeine Key-Value-Tabelle für systemweite Einstellungen.
-- Notschalter für lokalen Login:
--   INSERT INTO system_settings (`key`, `value`) VALUES ('local_login_force_enabled', '1')
--     ON DUPLICATE KEY UPDATE `value` = '1';
--   Zum Deaktivieren des Notschalters:
--   UPDATE system_settings SET `value` = '0' WHERE `key` = 'local_login_force_enabled';

CREATE TABLE IF NOT EXISTS `system_settings` (
    `key`        VARCHAR(100)  NOT NULL,
    `value`      TEXT          NOT NULL,
    `updated_at` TIMESTAMP     NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
    PRIMARY KEY (`key`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

-- Migration: 066_page_locks.sql
-- Migration 066: Sperren für gleichzeitiges Bearbeiten von Seiten
-- Wenn ein Benutzer eine Seite öffnet, wird ein Lock gesetzt (2 Min TTL).
-- Heartbeat erneuert den Lock alle 60s. Bei Speichern/Verlassen wird er gelöscht.

CREATE TABLE IF NOT EXISTS `page_locks` (
    `page_id`     BIGINT UNSIGNED NOT NULL,
    `user_id`     BIGINT UNSIGNED NOT NULL,
    `user_name`   VARCHAR(255)    NOT NULL DEFAULT '',
    `locked_at`   TIMESTAMP       NOT NULL DEFAULT CURRENT_TIMESTAMP,
    `expires_at`  TIMESTAMP       NOT NULL,
    PRIMARY KEY (`page_id`),
    CONSTRAINT `fk_page_locks_page` FOREIGN KEY (`page_id`) REFERENCES `pages`(`id`) ON DELETE CASCADE,
    CONSTRAINT `fk_page_locks_user` FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

-- Migration: 067_ticket_last_reply_placeholder.sql
-- Migration 067: Add {{last_reply_text}} placeholder block to solved/closed email templates
-- Adds an optional "last reply" section so customers see the last staff reply when their
-- ticket is marked as solved or closed.

UPDATE `ticket_email_templates`
SET `body` = REPLACE(
    `body`,
    '{{ticket_subject}}</p>',
    '{{ticket_subject}}</p>{{#if last_reply_text}}<p style="margin:1rem 0 0.25rem;"><strong>Letzte Antwort unseres Teams:</strong></p><blockquote style="margin:0.5rem 0 1rem;padding:0.75rem 1rem;background:#f9fafb;border-left:4px solid #e5e7eb;color:#374151;border-radius:0 0.375rem 0.375rem 0;">{{last_reply_text}}</blockquote>{{/if}}'
)
WHERE `event_type` IN ('solved', 'closed')
  AND `body` LIKE '%{{ticket_subject}}</p>%';

-- Migration: 068_unifi_vouchers_cache.sql
-- ============================================================
-- Migration 068: UniFi Voucher Cache
-- Cached voucher data per site so the vouchers page is fast.
-- ============================================================

CREATE TABLE IF NOT EXISTS `unifi_vouchers` (
    `id`          INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    `site_id`     VARCHAR(100)  NOT NULL                 COMMENT 'UniFi Site-ID (z.B. default)',
    `site_name`   VARCHAR(255)  NOT NULL DEFAULT ''      COMMENT 'Anzeigename der Site',
    `voucher_id`  VARCHAR(255)  NOT NULL                 COMMENT 'UniFi interner _id',
    `code`        VARCHAR(50)   NOT NULL,
    `quota`       SMALLINT UNSIGNED NOT NULL DEFAULT 1   COMMENT 'Max. Nutzungen (0=unbegrenzt)',
    `duration`    INT UNSIGNED  NOT NULL DEFAULT 1440    COMMENT 'Gültigkeitsdauer in Minuten',
    `used`        SMALLINT UNSIGNED NOT NULL DEFAULT 0,
    `note`        VARCHAR(255)  NULL,
    `expires_at`  DATETIME      NULL,
    `created_at`  DATETIME      NULL,
    `fetched_at`  DATETIME      NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
    UNIQUE KEY `uq_site_voucher`     (site_id, voucher_id),
    KEY `idx_unifi_voucher_site`     (site_id),
    KEY `idx_unifi_voucher_expires`  (expires_at)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
COMMENT='Gecachte UniFi Voucher-Daten pro Site';

-- Migration: 069_unifi_sites.sql
-- Migration 069: UniFi Site-Metadaten Cache
-- Speichert je Site ob ein Voucher-WLAN (Guest Portal mit Voucher-Auth) vorhanden ist.
-- Wird beim Gerätedaten-Refresh befüllt.

CREATE TABLE IF NOT EXISTS `unifi_sites` (
    `site_id`          VARCHAR(100) NOT NULL,
    `site_name`        VARCHAR(255) NOT NULL DEFAULT '',
    `has_voucher_wlan` TINYINT(1)   NOT NULL DEFAULT 0  COMMENT '1 wenn min. 1 Gast-WLAN mit Voucher-Auth vorhanden',
    `fetched_at`       DATETIME     NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
    PRIMARY KEY (`site_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
COMMENT='Gecachte UniFi Site-Metadaten (inkl. Voucher-Fähigkeit)';

-- Migration: 070_ticket_subject_brackets.sql
-- Migration 070: Ticket-E-Mail-Subjects auf [Ticket #ID]-Format umstellen
-- Damit erkennt extractTicketIdFromSubject Antworten korrekt als zugehörig.

UPDATE `ticket_email_templates` SET
    `subject` = '[Ticket #{{ticket_id}}] Ihre Anfrage wurde erfolgreich erstellt'
WHERE `event_type` = 'created'
  AND `subject` NOT LIKE '\[Ticket #%';

UPDATE `ticket_email_templates` SET
    `subject` = '[Ticket #{{ticket_id}}] Neue Antwort: {{ticket_subject}}'
WHERE `event_type` = 'reply'
  AND `subject` NOT LIKE '\[Ticket #%';

UPDATE `ticket_email_templates` SET
    `subject` = '[Ticket #{{ticket_id}}] Ihre Anfrage wurde als gelöst markiert'
WHERE `event_type` = 'solved'
  AND `subject` NOT LIKE '\[Ticket #%';

UPDATE `ticket_email_templates` SET
    `subject` = '[Ticket #{{ticket_id}}] Ihre Anfrage wurde geschlossen'
WHERE `event_type` = 'closed'
  AND `subject` NOT LIKE '\[Ticket #%';

UPDATE `ticket_email_templates` SET
    `subject` = '[Ticket #{{ticket_id}}] Ihre Anfrage wurde zugewiesen'
WHERE `event_type` = 'assigned'
  AND `subject` NOT LIKE '\[Ticket #%';

-- Migration: 071_backup_veeam.sql
-- ============================================================
-- Migration: Veeam Backup Integration
-- Version: 1.0.71
-- ============================================================

SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;

-- Einstellungen (Key-Value wie monitoring_settings)
CREATE TABLE IF NOT EXISTS `backup_settings` (
    `id`            INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    `setting_key`   VARCHAR(100) NOT NULL,
    `setting_value` TEXT NULL,
    `updated_at`    DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
    UNIQUE KEY `uq_setting_key` (`setting_key`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
COMMENT='Veeam Backup Konfiguration';

-- Gecachte Job-Daten
CREATE TABLE IF NOT EXISTS `backup_jobs_cache` (
    `id`            VARCHAR(255) NOT NULL,
    `name`          VARCHAR(255) NOT NULL,
    `type`          VARCHAR(100) NULL,
    `last_status`   VARCHAR(20)  NOT NULL DEFAULT 'none' COMMENT 'success|warning|failed|running|none',
    `last_run`      DATETIME NULL,
    `next_run`      DATETIME NULL,
    `duration_sec`  INT UNSIGNED NULL,
    `fetched_at`    DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
    PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
COMMENT='Gecachte Veeam Backup-Job-Daten';

-- Gecachte Repository-Daten
CREATE TABLE IF NOT EXISTS `backup_repositories_cache` (
    `id`            VARCHAR(255) NOT NULL,
    `name`          VARCHAR(255) NOT NULL,
    `type`          VARCHAR(100) NULL,
    `path`          VARCHAR(500) NULL,
    `capacity_gb`   DECIMAL(14,2) NULL COMMENT 'Gesamtkapazität in GB',
    `free_space_gb` DECIMAL(14,2) NULL COMMENT 'Freier Speicher in GB',
    `fetched_at`    DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
    PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
COMMENT='Gecachte Veeam Repository-Speicherdaten';

-- Capabilities (category ist seit Migration 042 VARCHAR(50))
INSERT IGNORE INTO `capabilities` (`name`, `description`, `category`) VALUES
    ('view_backup',   'Backup-Übersicht anzeigen',       'backup'),
    ('manage_backup', 'Backup-Einstellungen verwalten',  'backup');

SET FOREIGN_KEY_CHECKS = 1;

-- Migration: 072_backup_jobs_extra_fields.sql
-- ============================================================
-- Migration: Veeam Backup Jobs – erweiterte Felder
-- Version: 1.0.72
-- ============================================================

SET NAMES utf8mb4;

ALTER TABLE `backup_jobs_cache`
    ADD COLUMN `schedule_info`  VARCHAR(100) NULL          COMMENT 'Lesbare Zeitplan-Beschreibung (Täglich, Mo–Fr …)' AFTER `next_run`,
    ADD COLUMN `is_disabled`    TINYINT(1)   NOT NULL DEFAULT 0 AFTER `schedule_info`,
    ADD COLUMN `repository_id`  VARCHAR(255) NULL          COMMENT 'Veeam Repository-ID (aus job.storage.backupRepositoryId)' AFTER `is_disabled`;

-- Migration: 072_hpe_warranty_cache.sql
-- ============================================================
-- 072 HPE Warranty Cache
-- ============================================================
CREATE TABLE IF NOT EXISTS `hpe_warranty_cache` (
    `serial_number` VARCHAR(100) NOT NULL PRIMARY KEY,
    `raw_json`      MEDIUMTEXT   NOT NULL,
    `fetched_at`    DATETIME     NOT NULL DEFAULT CURRENT_TIMESTAMP,
    `error`         VARCHAR(500) NULL COMMENT 'Fehlermeldung falls Abruf fehlschlug'
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

-- Migration: 073_manufacturer_warranty_provider.sql
-- ============================================================
-- 073 Hersteller: warranty_provider-Feld
-- Verknüpft einen Hersteller explizit mit einem Garantie-Dienst
-- statt auf String-Matching im Namen zu angewiesen zu sein.
-- Mögliche Werte: NULL | 'hp' | 'hpe' | 'dell'
-- ============================================================

ALTER TABLE `asset_manufacturers`
    ADD COLUMN `warranty_provider` ENUM('hp','hpe','dell') NULL DEFAULT NULL
        COMMENT 'Garantie-API-Anbieter (hp = HP Inc., hpe = HPE, dell = Dell)'
    AFTER `support_url`;

-- Zwei fest definierte Hersteller eintragen (nur falls noch nicht vorhanden – kein Duplikat)
INSERT INTO `asset_manufacturers` (`name`, `website`, `support_url`, `warranty_provider`, `notes`)
SELECT 'HP Inc.', 'https://www.hp.com', 'https://support.hp.com', 'hp',
       'HP Inc. – PCs, Notebooks, Drucker, Monitore (früher: Hewlett-Packard PSG)'
WHERE NOT EXISTS (SELECT 1 FROM `asset_manufacturers` WHERE `name` = 'HP Inc.' LIMIT 1);

INSERT INTO `asset_manufacturers` (`name`, `website`, `support_url`, `warranty_provider`, `notes`)
SELECT 'HPE', 'https://www.hpe.com', 'https://support.hpe.com', 'hpe',
       'Hewlett Packard Enterprise – Server, Storage, Networking (ProLiant, Aruba, Nimble)'
WHERE NOT EXISTS (SELECT 1 FROM `asset_manufacturers` WHERE `name` = 'HPE' LIMIT 1);

-- Migration: 074_file_explorer.sql
-- File Explorer: Folders
CREATE TABLE IF NOT EXISTS file_explorer_folders (
    id          BIGINT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
    parent_id   BIGINT UNSIGNED NULL,
    name        VARCHAR(255) NOT NULL,
    path        TEXT NOT NULL COMMENT 'Materialized path like /1/5/12/',
    created_by  BIGINT UNSIGNED NOT NULL,
    created_at  DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
    updated_at  DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
    FOREIGN KEY (parent_id) REFERENCES file_explorer_folders(id) ON DELETE CASCADE,
    FOREIGN KEY (created_by) REFERENCES users(id) ON DELETE CASCADE
);

-- File Explorer: Files
CREATE TABLE IF NOT EXISTS file_explorer_files (
    id            BIGINT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
    folder_id     BIGINT UNSIGNED NULL COMMENT 'NULL = root folder',
    original_name VARCHAR(255) NOT NULL,
    stored_name   VARCHAR(255) NOT NULL COMMENT 'UUID-based filename on disk',
    mime_type     VARCHAR(150) DEFAULT NULL,
    file_size     BIGINT NOT NULL DEFAULT 0,
    uploaded_by   BIGINT UNSIGNED NOT NULL,
    created_at    DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
    FOREIGN KEY (folder_id) REFERENCES file_explorer_folders(id) ON DELETE CASCADE,
    FOREIGN KEY (uploaded_by) REFERENCES users(id) ON DELETE CASCADE
);

-- Capabilities
INSERT IGNORE INTO capabilities (name, description, category, risk_level) VALUES
    ('view_files',          'Datei-Explorer ansehen',       'files', 'low'),
    ('upload_files',        'Dateien hochladen',            'files', 'medium'),
    ('delete_files',        'Dateien löschen',              'files', 'medium'),
    ('manage_file_folders', 'Ordner erstellen und löschen', 'files', 'medium');

-- Migration: 075_user_totp.sql
-- Migration 075: MFA/TOTP für lokale Benutzerkonten

ALTER TABLE users
    ADD COLUMN totp_secret  VARCHAR(64)  NULL DEFAULT NULL AFTER password_hash,
    ADD COLUMN totp_enabled TINYINT(1)   NOT NULL DEFAULT 0 AFTER totp_secret;

-- Migration: 076_maintenance_calendar.sql
-- Migration 076: Wartungskalender – manuelle Ereignisse

CREATE TABLE IF NOT EXISTS `maintenance_events` (
    `id`          BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
    `title`       VARCHAR(255)    NOT NULL,
    `description` TEXT            NULL,
    `event_date`  DATE            NOT NULL,
    `end_date`    DATE            NULL COMMENT 'optional: mehrtägige Ereignisse',
    `type`        ENUM('maintenance','review','renewal','other') NOT NULL DEFAULT 'maintenance',
    `source`      ENUM('manual','ssl','asset','contract')         NOT NULL DEFAULT 'manual',
    `ref_id`      BIGINT UNSIGNED NULL COMMENT 'ID des verknüpften Objekts (Asset, Vertrag, SSL)',
    `created_by`  BIGINT UNSIGNED NULL,
    `created_at`  DATETIME        NOT NULL DEFAULT CURRENT_TIMESTAMP,
    `updated_at`  DATETIME        NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
    PRIMARY KEY (`id`),
    INDEX `idx_maint_date` (`event_date`),
    CONSTRAINT `fk_maint_user` FOREIGN KEY (`created_by`) REFERENCES `users` (`id`) ON DELETE SET NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

-- Migration: 077_unifi_uplink.sql
-- Migration 077: Echte Uplink-Topologiedaten für UniFi-Geräte

ALTER TABLE `unifi_devices`
    ADD COLUMN `uplink_mac`  VARCHAR(17)  NULL DEFAULT NULL
        COMMENT 'MAC des übergeordneten Geräts (aus uplink.uplink_mac)' AFTER `last_seen`,
    ADD COLUMN `uplink_type` VARCHAR(10)  NULL DEFAULT NULL
        COMMENT 'wire | wireless' AFTER `uplink_mac`,
    ADD COLUMN `uplink_port` SMALLINT UNSIGNED NULL DEFAULT NULL
        COMMENT 'Port-Nr am übergeordneten Gerät' AFTER `uplink_type`;

-- Migration: 078_network_diagrams.sql
-- Migration 078: Netzwerkpläne (interaktiver Diagramm-Editor)

CREATE TABLE IF NOT EXISTS `network_diagrams` (
    `id`          INT UNSIGNED NOT NULL AUTO_INCREMENT,
    `name`        VARCHAR(255) NOT NULL,
    `description` TEXT         NULL DEFAULT NULL,
    `data`        LONGTEXT     NULL DEFAULT NULL
                  COMMENT 'JSON: {nodes:[{id,type,label,x,y,description}], edges:[{id,from,to,label,style}]}',
    `created_by`  INT UNSIGNED NULL DEFAULT NULL,
    `updated_by`  INT UNSIGNED NULL DEFAULT NULL,
    `created_at`  TIMESTAMP    NOT NULL DEFAULT CURRENT_TIMESTAMP,
    `updated_at`  TIMESTAMP    NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
    PRIMARY KEY (`id`),
    KEY `idx_netdiagram_created_by` (`created_by`),
    KEY `idx_netdiagram_updated_at` (`updated_at`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
  COMMENT='Interaktive Netzwerkpläne';

-- Capabilities
INSERT IGNORE INTO `capabilities` (`name`, `description`, `category`, `risk_level`) VALUES
    ('view_netdiagram',   'Netzwerkpläne ansehen',            'page',  'low'),
    ('edit_netdiagram',   'Netzwerkpläne erstellen und bearbeiten', 'page', 'medium');

-- Migration: 079_webauthn_credentials.sql
-- WebAuthn / FIDO2 Sicherheitsschlüssel (YubiKey etc.)
CREATE TABLE IF NOT EXISTS `webauthn_credentials` (
    `id`             INT UNSIGNED  NOT NULL AUTO_INCREMENT,
    `user_id`        INT UNSIGNED  NOT NULL,
    `credential_id`  VARCHAR(700)  NOT NULL COMMENT 'Base64url-kodierte Credential-ID',
    `public_key`     TEXT          NOT NULL COMMENT 'PEM Public Key',
    `sign_count`     INT UNSIGNED  NOT NULL DEFAULT 0,
    `device_name`    VARCHAR(255)  NOT NULL DEFAULT 'Sicherheitsschlüssel',
    `created_at`     TIMESTAMP     NOT NULL DEFAULT CURRENT_TIMESTAMP,
    `last_used_at`   TIMESTAMP     NULL     DEFAULT NULL,
    PRIMARY KEY (`id`),
    UNIQUE KEY `uk_webauthn_cred_id` (`credential_id`(191)),
    INDEX `idx_webauthn_user_id` (`user_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
  COMMENT='FIDO2/WebAuthn Sicherheitsschlüssel der Benutzer';

-- Migration: 080_netdiagram_device_types.sql
-- Verwaltbare Gerätetypen für Netzwerkpläne
CREATE TABLE IF NOT EXISTS `netdiagram_device_types` (
    `id`           INT UNSIGNED  NOT NULL AUTO_INCREMENT,
    `type_key`     VARCHAR(64)   NOT NULL COMMENT 'Eindeutiger Schlüssel, z.B. switch, router',
    `label`        VARCHAR(100)  NOT NULL COMMENT 'Anzeigename',
    `icon_type`    ENUM('emoji','image') NOT NULL DEFAULT 'emoji',
    `icon_value`   VARCHAR(512)  NOT NULL DEFAULT '◆' COMMENT 'Unicode-Char oder Pfad zum hochgeladenen Bild',
    `bg_color`     CHAR(7)       NOT NULL DEFAULT '#f1f5f9',
    `stroke_color` CHAR(7)       NOT NULL DEFAULT '#475569',
    `sort_order`   INT           NOT NULL DEFAULT 0,
    `is_active`    TINYINT(1)    NOT NULL DEFAULT 1,
    `created_at`   TIMESTAMP     NOT NULL DEFAULT CURRENT_TIMESTAMP,
    PRIMARY KEY (`id`),
    UNIQUE KEY `uk_nd_device_type_key` (`type_key`),
    INDEX `idx_nd_device_sort` (`sort_order`, `is_active`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
  COMMENT='Gerätetypen für den Netzwerkplan-Editor';

-- Bestehende 8 Standard-Typen (Keys müssen mit gespeicherten Diagrammen übereinstimmen)
INSERT IGNORE INTO `netdiagram_device_types`
    (`type_key`, `label`, `icon_type`, `icon_value`, `bg_color`, `stroke_color`, `sort_order`)
VALUES
    ('switch',   'Switch',    'emoji', '⇅',  '#dbeafe', '#3b82f6', 10),
    ('router',   'Router',    'emoji', '↻',  '#ede9fe', '#8b5cf6', 20),
    ('firewall', 'Firewall',  'emoji', '🛡', '#fee2e2', '#ef4444', 30),
    ('ap',       'WLAN-AP',   'emoji', '📡', '#d1fae5', '#10b981', 40),
    ('server',   'Server',    'emoji', '🖥', '#fef3c7', '#f59e0b', 50),
    ('pc',       'PC',        'emoji', '💻', '#f3f4f6', '#6b7280', 60),
    ('cloud',    'Cloud',     'emoji', '☁',  '#cffafe', '#06b6d4', 70),
    ('generic',  'Gerät',     'emoji', '◆',  '#f1f5f9', '#475569', 80);

-- Migration: 081_ticket_multi_assignees.sql
-- Migration 081: Mehrere Bearbeiter pro Ticket (Multi-Assignee)
--
-- Erstellt die Junction-Tabelle ticket_assignees und migriert vorhandene
-- Einzelzuweisungen (tickets.assignee_id) dorthin.
-- Das Feld tickets.assignee_id bleibt als "primärer Bearbeiter" erhalten,
-- damit bestehende Listen-Ansichten und E-Mail-Benachrichtigungen unverändert
-- weiterarbeiten.

CREATE TABLE IF NOT EXISTS `ticket_assignees` (
    `ticket_id`   INT UNSIGNED   NOT NULL,
    `user_id`     BIGINT UNSIGNED NOT NULL,
    `assigned_at` DATETIME       NOT NULL DEFAULT CURRENT_TIMESTAMP,
    `assigned_by` BIGINT UNSIGNED NULL,
    PRIMARY KEY (`ticket_id`, `user_id`),
    FOREIGN KEY (`ticket_id`) REFERENCES `tickets`(`id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

-- Bestehende Einzelzuweisungen migrieren
INSERT IGNORE INTO `ticket_assignees` (`ticket_id`, `user_id`, `assigned_at`)
SELECT `id`, `assignee_id`, COALESCE(`updated_at`, `created_at`)
FROM   `tickets`
WHERE  `assignee_id` IS NOT NULL;

-- Migration: 082_ticket_watchers.sql
-- Migration 082: Ticket-Beobachter (Watchers)
--
-- Beobachter erhalten Antworten von Mitarbeitern per E-Mail,
-- aber KEINE Systembenachrichtigungen (erstellt, zugewiesen, gelöst, geschlossen).
-- Sie werden typischerweise aus dem CC-Feld eingehender E-Mails befüllt,
-- können aber auch manuell eingetragen werden.

CREATE TABLE IF NOT EXISTS `ticket_watchers` (
    `id`        INT UNSIGNED  AUTO_INCREMENT PRIMARY KEY,
    `ticket_id` INT UNSIGNED  NOT NULL,
    `email`     VARCHAR(255)  NOT NULL,
    `name`      VARCHAR(255)  NULL DEFAULT NULL,
    `added_at`  DATETIME      NOT NULL DEFAULT CURRENT_TIMESTAMP,
    UNIQUE KEY `uq_ticket_watcher` (`ticket_id`, `email`),
    FOREIGN KEY (`ticket_id`) REFERENCES `tickets`(`id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

-- Migration: 083_ticket_assets.sql
-- Migration 083: Ticket ↔ Asset Verknüpfung
--
-- Ermöglicht die Zuordnung von Assets zu Tickets und umgekehrt,
-- sofern beide Module (tickets + assets) aktiviert sind.
-- n:m-Beziehung: ein Ticket kann mehrere Assets haben,
-- ein Asset kann in mehreren Tickets auftauchen.

CREATE TABLE IF NOT EXISTS `ticket_assets` (
    `ticket_id` INT UNSIGNED    NOT NULL,
    `asset_id`  INT UNSIGNED    NOT NULL,
    `linked_at` DATETIME        NOT NULL DEFAULT CURRENT_TIMESTAMP,
    `linked_by` BIGINT UNSIGNED NULL,
    PRIMARY KEY (`ticket_id`, `asset_id`),
    FOREIGN KEY (`ticket_id`) REFERENCES `tickets`(`id`)  ON DELETE CASCADE,
    FOREIGN KEY (`asset_id`)  REFERENCES `assets`(`id`)   ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

-- Migration: 084_asset_manufacturers_unique_name.sql
-- ============================================================
-- Migration: Hersteller – Duplikat-Bereinigung & UNIQUE-Constraint
-- Version: 1.0.84
-- ============================================================
-- Verhindert, dass bei jedem Migrations-Durchlauf neue HP/HPE-Einträge
-- angelegt werden (INSERT IGNORE greift nur bei UNIQUE-Verletzungen).
-- ============================================================

SET NAMES utf8mb4;

-- 1. Duplikate bereinigen: Bei mehrfach vorhandenen Namen den Eintrag mit
--    warranty_provider behalten, alle anderen löschen.
DELETE am1 FROM `asset_manufacturers` am1
INNER JOIN `asset_manufacturers` am2
    ON am1.name = am2.name
    AND am1.id > am2.id
    AND am2.warranty_provider IS NOT NULL;

-- Weitere Duplikate bereinigen (wenn keiner warranty_provider hat, den neueren löschen)
DELETE am1 FROM `asset_manufacturers` am1
INNER JOIN `asset_manufacturers` am2
    ON am1.name = am2.name
    AND am1.id > am2.id;

-- 2. UNIQUE-Index auf name setzen (verhindert zukünftige Duplikate)
ALTER TABLE `asset_manufacturers`
    ADD UNIQUE KEY `uq_manufacturer_name` (`name`);

-- 3. HP Inc. und HPE sicher einfügen / aktualisieren (idempotent)
INSERT INTO `asset_manufacturers` (`name`, `website`, `support_url`, `warranty_provider`, `notes`)
VALUES
    ('HP Inc.',
     'https://www.hp.com',
     'https://support.hp.com',
     'hp',
     'HP Inc. – PCs, Notebooks, Drucker, Monitore (früher: Hewlett-Packard PSG)'),
    ('HPE',
     'https://www.hpe.com',
     'https://support.hpe.com',
     'hpe',
     'Hewlett Packard Enterprise – Server, Storage, Networking (ProLiant, Aruba, Nimble)')
ON DUPLICATE KEY UPDATE
    website          = VALUES(website),
    support_url      = VALUES(support_url),
    warranty_provider= VALUES(warranty_provider),
    notes            = VALUES(notes);

-- Migration: 085_asset_is_loan_device.sql
-- ============================================================
-- Migration: Assets – Leihgerät-Kennzeichnung
-- Version: 1.0.85
-- ============================================================
-- Erlaubt es, ein Asset im Inventar als Leihgerät zu markieren.
-- Leihgeräte stehen dann direkt im Loans-Modul zum Verleihen
-- zur Verfügung (wird als loan_device angelegt/synchronisiert).
-- ============================================================

SET NAMES utf8mb4;

ALTER TABLE `assets`
    ADD COLUMN `is_loan_device` TINYINT(1) NOT NULL DEFAULT 0
        COMMENT 'Wenn 1: Gerät steht im Leihgeräte-Modul zur Verfügung'
    AFTER `status`;

-- Migration: 086_loan_devices_asset_link.sql
-- ============================================================
-- Migration: Leihgeräte – Verknüpfung mit Asset-Inventar
-- Version: 1.0.86
-- ============================================================
-- Verbindet loan_devices mit dem Asset-Inventar.
-- Wenn ein Asset als Leihgerät markiert wird, wird automatisch
-- ein loan_device-Eintrag erstellt und dieser Spalte verknüpft.
-- ============================================================

SET NAMES utf8mb4;

ALTER TABLE `loan_devices`
    ADD COLUMN `asset_id` INT UNSIGNED NULL DEFAULT NULL
        COMMENT 'Verknüpftes Asset aus dem Inventar (optional)'
    AFTER `id`,
    ADD UNIQUE KEY `uq_loan_device_asset` (`asset_id`),
    ADD CONSTRAINT `fk_loan_device_asset`
        FOREIGN KEY (`asset_id`) REFERENCES `assets`(`id`) ON DELETE SET NULL;

-- Migration: 087_contract_categories.sql
-- Migration 087: Vertragskategorien
-- Fügt eine frei definierbare Kategorienverwaltung für Verträge hinzu.

CREATE TABLE IF NOT EXISTS `contract_categories` (
    `id`          INT UNSIGNED    NOT NULL AUTO_INCREMENT,
    `name`        VARCHAR(100)    NOT NULL,
    `color`       VARCHAR(7)      NOT NULL DEFAULT '#6b7280',
    `description` VARCHAR(255)    NULL DEFAULT NULL,
    `sort_order`  SMALLINT        NOT NULL DEFAULT 0,
    `created_at`  DATETIME        NOT NULL DEFAULT CURRENT_TIMESTAMP,
    PRIMARY KEY (`id`),
    UNIQUE KEY `uq_category_name` (`name`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

ALTER TABLE `contracts`
    ADD COLUMN `category_id` INT UNSIGNED NULL DEFAULT NULL AFTER `company_id`,
    ADD CONSTRAINT `fk_contract_category`
        FOREIGN KEY (`category_id`) REFERENCES `contract_categories`(`id`)
        ON DELETE SET NULL,
    ADD INDEX `idx_contracts_category` (`category_id`);

-- Migration: 088_asset_custom_fields.sql
-- Migration 088: Asset Custom Fields + Contract/Page ENUM-Erweiterungen
-- Fügt benutzerdefinierte Felder für Inventar-Kategorien und -Typen hinzu.
-- Erweitert außerdem cost_interval um 'one_time' und ansprechpartner_type um 'user'.

-- 1. Contracts: cost_interval ENUM erweitern
ALTER TABLE `contracts`
    MODIFY COLUMN `cost_interval` ENUM('monthly','quarterly','yearly','one_time') NOT NULL DEFAULT 'monthly';

-- 2. Pages: ansprechpartner_type ENUM erweitern
ALTER TABLE `pages`
    MODIFY COLUMN `ansprechpartner_type` ENUM('contact','company','user') NULL DEFAULT NULL;

-- 3. Benutzerdefinierte Felder pro Kategorie oder Typ
CREATE TABLE IF NOT EXISTS `asset_custom_fields` (
    `id`               INT UNSIGNED    NOT NULL AUTO_INCREMENT,
    `entity_type`      ENUM('category','type') NOT NULL,
    `entity_id`        INT UNSIGNED    NOT NULL,
    `field_name`       VARCHAR(100)    NOT NULL,
    `field_label`      VARCHAR(255)    NOT NULL,
    `field_type`       ENUM('text','dropdown') NOT NULL DEFAULT 'text',
    `dropdown_options` TEXT            NULL DEFAULT NULL COMMENT 'JSON-Array mit Optionen',
    `sort_order`       SMALLINT        NOT NULL DEFAULT 0,
    `is_required`      TINYINT(1)      NOT NULL DEFAULT 0,
    `created_at`       DATETIME        NOT NULL DEFAULT CURRENT_TIMESTAMP,
    PRIMARY KEY (`id`),
    INDEX `idx_acf_entity` (`entity_type`, `entity_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

-- 4. Werte der benutzerdefinierten Felder pro Asset
CREATE TABLE IF NOT EXISTS `asset_custom_field_values` (
    `id`              INT UNSIGNED    NOT NULL AUTO_INCREMENT,
    `asset_id`        INT UNSIGNED    NOT NULL,
    `custom_field_id` INT UNSIGNED    NOT NULL,
    `field_value`     TEXT            NULL DEFAULT NULL,
    `updated_at`      DATETIME        NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
    PRIMARY KEY (`id`),
    UNIQUE KEY `uq_asset_field` (`asset_id`, `custom_field_id`),
    CONSTRAINT `fk_acfv_asset`  FOREIGN KEY (`asset_id`)        REFERENCES `assets`(`id`)              ON DELETE CASCADE,
    CONSTRAINT `fk_acfv_field`  FOREIGN KEY (`custom_field_id`) REFERENCES `asset_custom_fields`(`id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

-- Migration: 089_asset_custom_field_default_value.sql
-- Migration 089: Standard-Wert für benutzerdefinierte Felder
-- Ermöglicht das Hinterlegen eines Standard-Werts pro Custom Field,
-- der beim Anlegen eines neuen Assets automatisch vorausgefüllt wird.

ALTER TABLE `asset_custom_fields`
    ADD COLUMN `default_value` TEXT NULL DEFAULT NULL AFTER `dropdown_options`;

-- Migration: 090_directory_person_image_and_ap_type.sql
-- Migration 090: Profilbild für Verzeichnis-Personen + 'directory' als Ansprechpartner-Typ

-- 1. Profilbild-Spalte für Verzeichnis-Personen
ALTER TABLE `directory_persons`
    ADD COLUMN `image_path` VARCHAR(500) NULL DEFAULT NULL
    COMMENT 'Relativer Pfad zum Profilbild, z.B. /uploads/directory-persons/1.jpg'
    AFTER `phone`;

-- 2. 'directory' als neuen Ansprechpartner-Typ auf Seiten erlauben
ALTER TABLE `pages`
    MODIFY COLUMN `ansprechpartner_type`
        ENUM('contact','company','user','directory') NULL DEFAULT NULL;

-- Migration: 091_user_directory_person_link.sql
-- Verknüpfung: Benutzer ↔ Verzeichnis-Person
-- Ermöglicht automatischen Foto-Import und "Person zum Benutzer machen"

ALTER TABLE `users`
    ADD COLUMN `directory_person_id` INT UNSIGNED NULL DEFAULT NULL
        COMMENT 'FK → directory_persons.id – verknüpfte Verzeichnis-Person'
        AFTER `is_admin`,
    ADD CONSTRAINT `fk_users_directory_person`
        FOREIGN KEY (`directory_person_id`) REFERENCES `directory_persons` (`id`)
        ON DELETE SET NULL ON UPDATE CASCADE;

CREATE INDEX `idx_users_directory_person` ON `users` (`directory_person_id`);

-- Migration: 092_page_autosave_draft.sql
-- Autosave-Draft für Seiten
-- Speichert den Zwischen-Entwurf ohne Version oder Benachrichtigung zu erzeugen.
-- draft_content_html: wird beim Autosave befüllt, beim manuellen Speichern geleert
-- draft_saved_at:     Zeitstempel des letzten Autosaves

ALTER TABLE `pages`
    ADD COLUMN `draft_content_html` LONGTEXT NULL DEFAULT NULL
        COMMENT 'Autosave-Entwurf, erzeugt keine Version'
        AFTER `current_version_id`,
    ADD COLUMN `draft_saved_at` DATETIME NULL DEFAULT NULL
        COMMENT 'Zeitpunkt des letzten Autosaves'
        AFTER `draft_content_html`;

-- Migration: 093_asset_handovers.sql
-- Migration 093: Asset-Übergaben (Ausgabe + Rücknahme) mit Unterschrift
-- Ermöglicht die dokumentierte Ausgabe und Rücknahme von Assets mit Unterschrift und PDF-Bestätigung.

-- Tabelle: Übergabe-Datensätze
CREATE TABLE IF NOT EXISTS `asset_handovers` (
    `id`                      INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    `asset_id`                INT UNSIGNED NOT NULL,
    `type`                    ENUM('handout', 'return') NOT NULL
                              COMMENT 'handout=Ausgabe, return=Rücknahme',
    `person_name`             VARCHAR(255) NOT NULL,
    `person_email`            VARCHAR(255) NULL,
    `person_id`               INT UNSIGNED NULL COMMENT 'Verknüpfung zu directory_persons',

    -- Unterschrift (nur bei Ausgabe)
    `signature_token`         VARCHAR(64)  NULL COMMENT '64-Hex-Token für Tablet-Unterschrift',
    `signature_requested_at`  DATETIME     NULL,
    `signature_data`          LONGTEXT     NULL COMMENT 'Base64-PNG der Unterschrift',
    `signature_obtained_at`   DATETIME     NULL,

    -- Generiertes PDF (Dateipfad relativ zu storage/)
    `pdf_path`                VARCHAR(500) NULL,

    -- Notizen (vor allem für Rücknahme)
    `notes`                   TEXT NULL,

    `created_by`              INT UNSIGNED NULL,
    `created_at`              DATETIME     NOT NULL DEFAULT CURRENT_TIMESTAMP,

    INDEX `idx_ah_asset`  (`asset_id`),
    INDEX `idx_ah_token`  (`signature_token`),
    INDEX `idx_ah_type`   (`asset_id`, `type`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

-- Tabelle: WYSIWYG-editierbare PDF-Templates
CREATE TABLE IF NOT EXISTS `asset_pdf_templates` (
    `id`           INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    `template_key` VARCHAR(50)  NOT NULL UNIQUE
                   COMMENT 'handout oder return',
    `name`         VARCHAR(255) NOT NULL,
    `body_html`    LONGTEXT     NOT NULL COMMENT 'HTML-Body des PDFs mit {{Platzhaltern}}',
    `updated_by`   INT UNSIGNED NULL,
    `updated_at`   DATETIME     NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

-- Standard-PDF-Templates
INSERT INTO `asset_pdf_templates` (`template_key`, `name`, `body_html`) VALUES
('handout',
 'Gerät-Ausgabe Bestätigung',
 '<h2 style="text-align:center;color:#1e3a5f;border-bottom:2px solid #1e3a5f;padding-bottom:8px;">Bestätigung der Geräteübergabe</h2>
<p style="margin:16px 0;">Hiermit bestätige ich, <strong>{{person_name}}</strong>, den Erhalt des folgenden Geräts:</p>
<table style="width:100%;border-collapse:collapse;margin:16px 0;">
  <tr><th style="text-align:left;padding:6px 10px;background:#f3f4f6;border:1px solid #e5e7eb;width:35%;">Inventarnummer</th><td style="padding:6px 10px;border:1px solid #e5e7eb;">{{asset_inventory_number}}</td></tr>
  <tr><th style="text-align:left;padding:6px 10px;background:#f3f4f6;border:1px solid #e5e7eb;">Seriennummer</th><td style="padding:6px 10px;border:1px solid #e5e7eb;">{{asset_serial_number}}</td></tr>
  <tr><th style="text-align:left;padding:6px 10px;background:#f3f4f6;border:1px solid #e5e7eb;">Hersteller / Modell</th><td style="padding:6px 10px;border:1px solid #e5e7eb;">{{asset_manufacturer}} {{asset_model}}</td></tr>
  <tr><th style="text-align:left;padding:6px 10px;background:#f3f4f6;border:1px solid #e5e7eb;">Kategorie</th><td style="padding:6px 10px;border:1px solid #e5e7eb;">{{asset_category}}</td></tr>
  <tr><th style="text-align:left;padding:6px 10px;background:#f3f4f6;border:1px solid #e5e7eb;">Ausgabedatum</th><td style="padding:6px 10px;border:1px solid #e5e7eb;">{{handout_date}}</td></tr>
</table>
<p style="margin:16px 0;">Mit meiner Unterschrift bestätige ich die vollständige und unbeschädigte Übergabe des oben genannten Geräts. Ich bin für die sichere Aufbewahrung und ordnungsgemäße Verwendung verantwortlich.</p>
<div style="margin-top:32px;border-top:1px solid #e5e7eb;padding-top:16px;">
  <p style="font-weight:bold;margin-bottom:8px;">Unterschrift des Empfängers:</p>
  {{signature_image}}
  <p style="margin-top:8px;font-size:10pt;color:#6b7280;">{{person_name}} – {{handout_date}}</p>
</div>'
),
('return',
 'Gerät-Rücknahme Bestätigung',
 '<h2 style="text-align:center;color:#1e3a5f;border-bottom:2px solid #1e3a5f;padding-bottom:8px;">Bestätigung der Geräterücknahme</h2>
<p style="margin:16px 0;">Hiermit bestätigen wir die Rückgabe des folgenden Geräts durch <strong>{{person_name}}</strong>:</p>
<table style="width:100%;border-collapse:collapse;margin:16px 0;">
  <tr><th style="text-align:left;padding:6px 10px;background:#f3f4f6;border:1px solid #e5e7eb;width:35%;">Inventarnummer</th><td style="padding:6px 10px;border:1px solid #e5e7eb;">{{asset_inventory_number}}</td></tr>
  <tr><th style="text-align:left;padding:6px 10px;background:#f3f4f6;border:1px solid #e5e7eb;">Seriennummer</th><td style="padding:6px 10px;border:1px solid #e5e7eb;">{{asset_serial_number}}</td></tr>
  <tr><th style="text-align:left;padding:6px 10px;background:#f3f4f6;border:1px solid #e5e7eb;">Hersteller / Modell</th><td style="padding:6px 10px;border:1px solid #e5e7eb;">{{asset_manufacturer}} {{asset_model}}</td></tr>
  <tr><th style="text-align:left;padding:6px 10px;background:#f3f4f6;border:1px solid #e5e7eb;">Rückgabedatum</th><td style="padding:6px 10px;border:1px solid #e5e7eb;">{{return_date}}</td></tr>
</table>
{{notes_section}}
<p style="margin:16px 0;font-size:10pt;color:#374151;">Das Gerät wurde in einwandfreiem Zustand zurückgenommen und wieder eingelagert.</p>'
);

-- E-Mail-Vorlagen für Asset-Übergaben (erscheinen in /admin/notifications)
INSERT INTO `notification_templates`
    (`template_key`, `title_template`, `message_template`, `type`, `description`, `is_enabled`)
VALUES
(
    'asset_handout_confirmed',
    'Bestätigung: Gerät {{asset_inventory_number}} erhalten',
    'Hallo {{person_name}},\n\nhiermit bestätigen wir die Ausgabe des folgenden Geräts an Sie:\n\nInventarnummer: {{asset_inventory_number}}\nSeriennummer: {{asset_serial_number}}\nHersteller / Modell: {{asset_manufacturer}} {{asset_model}}\nAusgabedatum: {{handout_date}}\n\nIm Anhang finden Sie die Übergabebestätigung mit Ihrer Unterschrift als PDF.\n\nBei Fragen wenden Sie sich bitte an Ihre IT-Abteilung.',
    'system',
    'Wird nach der Unterschrift bei der Geräteausgabe an die zugewiesene Person gesendet.',
    1
),
(
    'asset_return_confirmed',
    'Bestätigung: Gerät {{asset_inventory_number}} zurückgenommen',
    'Hallo {{person_name}},\n\nhiermit bestätigen wir die Rückgabe des folgenden Geräts:\n\nInventarnummer: {{asset_inventory_number}}\nSeriennummer: {{asset_serial_number}}\nHersteller / Modell: {{asset_manufacturer}} {{asset_model}}\nRückgabedatum: {{return_date}}\n\nIm Anhang finden Sie die Rücknahmebestätigung als PDF.\n\nBei Fragen wenden Sie sich bitte an Ihre IT-Abteilung.',
    'system',
    'Wird bei der Geräterücknahme an die bisherige Nutzerin / den bisherigen Nutzer gesendet.',
    1
)
ON DUPLICATE KEY UPDATE `description` = VALUES(`description`);

-- Migration: 094_asset_email_templates.sql
-- Migration 094: Dedizierte E-Mail-Vorlagen für Asset-Übergaben
-- Separate Tabelle asset_email_templates (statt notification_templates, da externe Empfänger).
-- Außerdem Übergabe-Bedingungen für die Unterschriften-Seite.

-- Tabelle: E-Mail-Vorlagen für Ausgabe + Rücknahme
CREATE TABLE IF NOT EXISTS `asset_email_templates` (
    `id`               INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    `template_key`     VARCHAR(50)  NOT NULL UNIQUE
                       COMMENT 'asset_handout_email oder asset_return_email',
    `name`             VARCHAR(255) NOT NULL,
    `subject_template` VARCHAR(500) NOT NULL,
    `body_html`        LONGTEXT     NOT NULL COMMENT 'HTML-Body mit {{Platzhaltern}}',
    `is_enabled`       TINYINT(1)   NOT NULL DEFAULT 1,
    `updated_by`       INT UNSIGNED NULL,
    `updated_at`       DATETIME     NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

-- Standard E-Mail-Vorlagen
INSERT INTO `asset_email_templates` (`template_key`, `name`, `subject_template`, `body_html`, `is_enabled`) VALUES
(
    'asset_handout_email',
    'Ausgabe-Bestätigung (E-Mail an Mitarbeiter)',
    'Bestätigung: Gerät {{asset_inventory_number}} erhalten',
    '<p>Hallo {{person_name}},</p>
<p>hiermit bestätigen wir die Ausgabe des folgenden Geräts an Sie:</p>
<table style="border-collapse:collapse;width:100%;margin:12px 0;">
  <tr><th style="text-align:left;padding:5px 10px;background:#f3f4f6;border:1px solid #e5e7eb;width:40%;">Inventarnummer</th><td style="padding:5px 10px;border:1px solid #e5e7eb;">{{asset_inventory_number}}</td></tr>
  <tr><th style="text-align:left;padding:5px 10px;background:#f3f4f6;border:1px solid #e5e7eb;">Seriennummer</th><td style="padding:5px 10px;border:1px solid #e5e7eb;">{{asset_serial_number}}</td></tr>
  <tr><th style="text-align:left;padding:5px 10px;background:#f3f4f6;border:1px solid #e5e7eb;">Hersteller / Modell</th><td style="padding:5px 10px;border:1px solid #e5e7eb;">{{asset_manufacturer}} {{asset_model}}</td></tr>
  <tr><th style="text-align:left;padding:5px 10px;background:#f3f4f6;border:1px solid #e5e7eb;">Ausgabedatum</th><td style="padding:5px 10px;border:1px solid #e5e7eb;">{{handout_date}}</td></tr>
</table>
<p>Im Anhang finden Sie die Übergabebestätigung mit Ihrer Unterschrift als PDF.</p>
<p>Bei Fragen wenden Sie sich bitte an Ihre IT-Abteilung.</p>',
    1
),
(
    'asset_return_email',
    'Rücknahme-Bestätigung (E-Mail an Mitarbeiter)',
    'Bestätigung: Gerät {{asset_inventory_number}} zurückgenommen',
    '<p>Hallo {{person_name}},</p>
<p>hiermit bestätigen wir die Rückgabe des folgenden Geräts:</p>
<table style="border-collapse:collapse;width:100%;margin:12px 0;">
  <tr><th style="text-align:left;padding:5px 10px;background:#f3f4f6;border:1px solid #e5e7eb;width:40%;">Inventarnummer</th><td style="padding:5px 10px;border:1px solid #e5e7eb;">{{asset_inventory_number}}</td></tr>
  <tr><th style="text-align:left;padding:5px 10px;background:#f3f4f6;border:1px solid #e5e7eb;">Seriennummer</th><td style="padding:5px 10px;border:1px solid #e5e7eb;">{{asset_serial_number}}</td></tr>
  <tr><th style="text-align:left;padding:5px 10px;background:#f3f4f6;border:1px solid #e5e7eb;">Hersteller / Modell</th><td style="padding:5px 10px;border:1px solid #e5e7eb;">{{asset_manufacturer}} {{asset_model}}</td></tr>
  <tr><th style="text-align:left;padding:5px 10px;background:#f3f4f6;border:1px solid #e5e7eb;">Rückgabedatum</th><td style="padding:5px 10px;border:1px solid #e5e7eb;">{{return_date}}</td></tr>
</table>
<p>Im Anhang finden Sie die Rücknahmebestätigung als PDF.</p>
<p>Bei Fragen wenden Sie sich bitte an Ihre IT-Abteilung.</p>',
    1
)
ON DUPLICATE KEY UPDATE `name` = VALUES(`name`);

-- Übergabe-Bedingungen (angezeigt auf der Unterschriften-Seite)
INSERT INTO `asset_pdf_templates` (`template_key`, `name`, `body_html`) VALUES
(
    'handout_terms',
    'Übergabe-Bedingungen (Unterschriften-Seite)',
    '<h4>Datenschutzerklärung &amp; Nutzungsbedingungen</h4>
<p>Der Arbeitgeber behält sich die Durchsicht und Kontrolle der Einzelverbindungsnachweise vor.</p>
<p>Die Geräte sind Eigentum des Unternehmens. Verlust oder Beschädigung der Geräte oder des Zubehörs ist umgehend der IT-Abteilung zu melden. Bei einem Ausscheiden aus dem Unternehmen sind die Geräte sowie Zubehör wieder abzugeben.</p>
<p>Mobilfunkverträge: Es fallen keine Kosten für Telefonate, SMS oder Internetnutzung innerhalb Deutschlands an. Die Nutzung im Ausland muss dem Arbeitgeber im Voraus mitgeteilt werden. Die Inanspruchnahme von Sonderrufnummern, Servicerufnummern sowie Abo-Diensten ist nicht gestattet.</p>'
)
ON DUPLICATE KEY UPDATE `name` = VALUES(`name`);

-- Veraltete Einträge aus notification_templates entfernen (wurden durch asset_email_templates ersetzt)
DELETE FROM `notification_templates`
WHERE `template_key` IN ('asset_handout_confirmed', 'asset_return_confirmed');

-- Migration: 095_netdiagram_drawio.sql
-- Migration 095: draw.io Integration
-- Erweitert die Tabelle um draw.io XML-Speicherung.

-- draw.io XML-Spalte + Format-Marker hinzufügen
ALTER TABLE network_diagrams
  ADD COLUMN `drawio_xml` MEDIUMTEXT NULL COMMENT 'draw.io Diagramm-XML' AFTER `data`,
  ADD COLUMN `diagram_format` ENUM('drawio') NOT NULL DEFAULT 'drawio' COMMENT 'Diagrammformat' AFTER `drawio_xml`;

-- Migration: 096_netdiagram_preview.sql
-- Migration 096: SVG Vorschau für Netzwerkpläne
-- Fügt eine Spalte für die automatisch generierte SVG-Vorschau hinzu.

ALTER TABLE network_diagrams
  ADD COLUMN `preview_svg` MEDIUMTEXT NULL COMMENT 'Automatisch generierte SVG-Vorschau' AFTER `drawio_xml`;
-- ============================================================
-- Migration 002: VM- und Host-Verwaltung
--
-- Tabellen:
--   vm_clusters  – Cluster-Gruppen für Hosts
--   vm_hosts     – Physische Server / Hypervisoren
--   vm_vms       – Virtuelle Maschinen
--
-- IPAM-Integration:
--   Beim Anlegen von VMs und Hosts wird die IP automatisch in
--   ipam_addresses eingetragen, wenn ein passendes Subnetz gefunden wird.
-- ============================================================

-- Cluster (Gruppen von Hosts)
CREATE TABLE IF NOT EXISTS `vm_clusters` (
    `id`          INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    `name`        VARCHAR(255)    NOT NULL,
    `description` TEXT            NULL,
    `created_by`  INT UNSIGNED    NULL,
    `created_at`  TIMESTAMP       NOT NULL DEFAULT CURRENT_TIMESTAMP,
    `updated_at`  TIMESTAMP       NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
    KEY `idx_vmcluster_name` (`name`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

-- Physische Hosts / Hypervisoren
CREATE TABLE IF NOT EXISTS `vm_hosts` (
    `id`               INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    `name`             VARCHAR(255)    NOT NULL,
    `ip_address`       VARCHAR(45)     NULL,
    `ipam_address_id`  INT UNSIGNED    NULL COMMENT 'Referenz auf ipam_addresses.id (optional)',
    `hypervisor`       ENUM('vmware_esxi','proxmox','hyper_v','kvm','xen','other') NOT NULL DEFAULT 'proxmox',
    `cluster_id`       INT UNSIGNED    NULL,
    `cpu_cores`        INT UNSIGNED    NULL,
    `ram_gb`           DECIMAL(10,2)   NULL,
    `storage_gb`       DECIMAL(12,2)   NULL,
    `location`         VARCHAR(255)    NULL,
    `description`      TEXT            NULL,
    `status`           ENUM('online','offline','maintenance') NOT NULL DEFAULT 'online',
    `created_by`       INT UNSIGNED    NULL,
    `created_at`       TIMESTAMP       NOT NULL DEFAULT CURRENT_TIMESTAMP,
    `updated_at`       TIMESTAMP       NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
    KEY `idx_vmhost_cluster` (`cluster_id`),
    KEY `idx_vmhost_name`    (`name`),
    CONSTRAINT `fk_vmhost_cluster` FOREIGN KEY (`cluster_id`) REFERENCES `vm_clusters`(`id`) ON DELETE SET NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

-- Virtuelle Maschinen
CREATE TABLE IF NOT EXISTS `vm_vms` (
    `id`               INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    `name`             VARCHAR(255)    NOT NULL,
    `ip_address`       VARCHAR(45)     NULL,
    `ipam_address_id`  INT UNSIGNED    NULL COMMENT 'Referenz auf ipam_addresses.id (optional)',
    `host_id`          INT UNSIGNED    NULL,
    `cpu_cores`        INT UNSIGNED    NULL,
    `ram_gb`           DECIMAL(10,2)   NULL,
    `storage_gb`       DECIMAL(12,2)   NULL,
    `purpose`          TEXT            NULL COMMENT 'Aufgabe / Verwendungszweck der VM',
    `os`               VARCHAR(255)    NULL,
    `status`           ENUM('running','stopped','paused','error') NOT NULL DEFAULT 'running',
    `notes`            TEXT            NULL,
    `created_by`       INT UNSIGNED    NULL,
    `created_at`       TIMESTAMP       NOT NULL DEFAULT CURRENT_TIMESTAMP,
    `updated_at`       TIMESTAMP       NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
    KEY `idx_vmvm_host`   (`host_id`),
    KEY `idx_vmvm_name`   (`name`),
    KEY `idx_vmvm_status` (`status`),
    CONSTRAINT `fk_vmvm_host` FOREIGN KEY (`host_id`) REFERENCES `vm_hosts`(`id`) ON DELETE SET NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

-- Capabilities VM-Modul (granular)
INSERT IGNORE INTO `capabilities` (`name`, `description`, `category`, `risk_level`) VALUES
('view_vms',           'VMs, Hosts und Cluster ansehen (read-only)',                        'vms', 'low'),
('manage_vms',         'VMs anlegen, bearbeiten und löschen',                              'vms', 'medium'),
('manage_vm_hosts',    'Hosts anlegen, bearbeiten und löschen',                            'vms', 'medium'),
('manage_vm_clusters', 'Cluster anlegen, bearbeiten und löschen',                          'vms', 'low'),
('sync_vms',           'Hypervisor-Synchronisierung auslösen und Verbindung testen',       'vms', 'medium'),
('admin_vms',          'Hypervisor-API-Zugangsdaten konfigurieren (verschlüsselt, Admin)', 'vms', 'high');

-- Admins erhalten alle VM-Capabilities automatisch
INSERT IGNORE INTO `role_capabilities` (`role_id`, `capability_id`)
    SELECT r.id, c.id
    FROM `roles` r, `capabilities` c
    WHERE r.is_admin = TRUE
      AND c.name IN ('view_vms', 'manage_vms', 'manage_vm_hosts', 'manage_vm_clusters', 'sync_vms', 'admin_vms');
-- ============================================================
-- Migration 003: Hypervisor-Sync-Spalten
--
-- Erweitert vm_hosts um Verbindungsdaten für die automatische
-- Synchronisierung mit Proxmox VE, VMware vSphere und Hyper-V.
-- Erweitert vm_vms um die externe VM-ID des Hypervisors.
-- ============================================================
-- ============================================================
-- Migration 003: Hypervisor-Sync-Spalten
--
-- Erweitert vm_hosts um Verbindungsdaten für die automatische
-- Synchronisierung mit Proxmox VE, VMware vSphere und Hyper-V.
-- Erweitert vm_vms um die externe VM-ID des Hypervisors.
-- ============================================================

ALTER TABLE `vm_hosts`
    ADD COLUMN `hypervisor_url`          VARCHAR(512)  NULL COMMENT 'API-URL, z.B. https://proxmox.local:8006' AFTER `description`,
    ADD COLUMN `hypervisor_user`         VARCHAR(255)  NULL COMMENT 'Benutzername / API-Token-ID'               AFTER `hypervisor_url`,
    ADD COLUMN `hypervisor_password_enc` TEXT          NULL COMMENT 'Verschlüsseltes Passwort (CredentialCrypto)' AFTER `hypervisor_user`,
    ADD COLUMN `hypervisor_token`        TEXT          NULL COMMENT 'API-Token (z.B. Proxmox PVEAPIToken)'       AFTER `hypervisor_password_enc`,
    ADD COLUMN `hypervisor_verify_ssl`   TINYINT(1)   NOT NULL DEFAULT 0 COMMENT 'SSL-Zertifikat prüfen'        AFTER `hypervisor_token`,
    ADD COLUMN `hypervisor_node_id`      VARCHAR(255)  NULL COMMENT 'Proxmox-Knoten / VMware-Datacenter'        AFTER `hypervisor_verify_ssl`,
    ADD COLUMN `last_sync_at`            TIMESTAMP     NULL                                                      AFTER `hypervisor_node_id`,
    ADD COLUMN `sync_status`             ENUM('never','ok','error') NOT NULL DEFAULT 'never'                    AFTER `last_sync_at`,
    ADD COLUMN `sync_error`              TEXT          NULL COMMENT 'Letzte Fehlermeldung beim Sync'             AFTER `sync_status`;

ALTER TABLE `vm_vms`
    ADD COLUMN `hypervisor_vm_id` VARCHAR(255) NULL COMMENT 'VM-ID im Hypervisor (VMID, MoRef, usw.)' AFTER `notes`,
    ADD COLUMN `last_sync_at`     TIMESTAMP    NULL                                                    AFTER `hypervisor_vm_id`;


-- ============================================================
-- Migrationen 005–031: In initiales Schema integriert
--
-- Alle Statements sind idempotent:
--   CREATE TABLE IF NOT EXISTS  → sicher bei Neuinstallation
--   INSERT IGNORE               → sicher bei Neuinstallation
--   ALTER TABLE ADD COLUMN      → Runner ignoriert "already exists"
--   ALTER TABLE MODIFY COLUMN   → läuft problemlos mehrfach
--   ALTER TABLE ADD CONSTRAINT/INDEX → Runner ignoriert "Duplicate"
-- ============================================================

-- ============================================================
-- Migration 005: Änderungsprotokoll (Config Log)
-- ============================================================
SET NAMES utf8mb4;

CREATE TABLE IF NOT EXISTS `config_log_entries` (
    `id`               INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    `title`            VARCHAR(500)  NOT NULL,
    `type`             ENUM('update','config_change','installation','planned_maintenance') NOT NULL DEFAULT 'config_change',
    `description`      TEXT          NULL,
    `affected_system`  VARCHAR(255)  NULL,
    `asset_id`         INT UNSIGNED  NULL,
    `location_id`      INT UNSIGNED  NULL,
    `ticket_ref`       VARCHAR(100)  NULL,
    `ticket_id`        INT UNSIGNED  NULL,
    `planned_date`     DATE          NULL,
    `changed_at`       DATETIME      NULL,
    `status`           ENUM('planned','done','failed') NOT NULL DEFAULT 'planned',
    `is_recurring`     TINYINT(1) UNSIGNED NOT NULL DEFAULT 0,
    `recurrence_interval` ENUM('weekly','monthly','quarterly','yearly') NULL,
    `notes`            TEXT          NULL,
    `created_by`       BIGINT UNSIGNED NOT NULL,
    `created_at`       DATETIME      NOT NULL DEFAULT CURRENT_TIMESTAMP,
    `updated_at`       DATETIME      NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
    FOREIGN KEY (`asset_id`)    REFERENCES `assets`(`id`)           ON DELETE SET NULL,
    FOREIGN KEY (`location_id`) REFERENCES `ticket_locations`(`id`) ON DELETE SET NULL,
    FOREIGN KEY (`created_by`)  REFERENCES `users`(`id`),
    INDEX (`status`),
    INDEX (`planned_date`),
    INDEX (`type`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

INSERT IGNORE INTO `capabilities` (`name`, `description`, `category`, `risk_level`) VALUES
    ('view_config_log',   'Änderungsprotokoll einsehen',                        'configlog', 'low'),
    ('manage_config_log', 'Änderungsprotokoll-Einträge anlegen und bearbeiten', 'configlog', 'medium');

INSERT IGNORE INTO `role_capabilities` (`role_id`, `capability_id`)
    SELECT r.id, c.id FROM `roles` r, `capabilities` c
    WHERE r.is_admin = TRUE AND c.name IN ('view_config_log', 'manage_config_log')
      AND NOT EXISTS (SELECT 1 FROM `role_capabilities` rc WHERE rc.role_id = r.id AND rc.capability_id = c.id);

-- ============================================================
-- Migration 006: Änderungsprotokoll – Erweiterungen
-- ============================================================

ALTER TABLE `config_log_entries`
    ADD COLUMN `is_recurring` TINYINT(1) UNSIGNED NOT NULL DEFAULT 0 AFTER `status`;

ALTER TABLE `config_log_entries`
    ADD COLUMN `recurrence_interval` ENUM('weekly','monthly','quarterly','yearly') NULL AFTER `is_recurring`;

CREATE TABLE IF NOT EXISTS `config_log_vms` (
    `config_log_id` INT UNSIGNED NOT NULL,
    `vm_id`         INT UNSIGNED NOT NULL,
    `linked_at`     DATETIME     NOT NULL DEFAULT CURRENT_TIMESTAMP,
    PRIMARY KEY (`config_log_id`, `vm_id`),
    INDEX `idx_clv_entry` (`config_log_id`),
    INDEX `idx_clv_vm`    (`vm_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

-- ============================================================
-- Migration 007: Lizenzmanagement
-- ============================================================

CREATE TABLE IF NOT EXISTS `licenses` (
    `id`              INT UNSIGNED     AUTO_INCREMENT PRIMARY KEY,
    `name`            VARCHAR(500)     NOT NULL,
    `vendor`          VARCHAR(255)     NULL,
    `product`         VARCHAR(255)     NULL,
    `type`            ENUM('perpetual','subscription','volume','oem','open_source','trial') NOT NULL DEFAULT 'subscription',
    `seats`           INT UNSIGNED     NULL,
    `seats_used`      INT UNSIGNED     NOT NULL DEFAULT 0,
    `valid_from`      DATE             NULL,
    `valid_until`     DATE             NULL,
    `cost_amount`     DECIMAL(12,2)    NULL,
    `cost_interval`   ENUM('monthly','yearly','one_time') NULL,
    `status`          ENUM('active','expired','cancelled','trial') NOT NULL DEFAULT 'active',
    `license_key_enc` TEXT             NULL COMMENT 'AES-256-CBC verschlüsselter Lizenzschlüssel',
    `license_file_path` VARCHAR(255)   NULL,
    `license_file_name` VARCHAR(255)   NULL,
    `notes`           TEXT             NULL,
    `created_by`      BIGINT UNSIGNED  NOT NULL,
    `created_at`      DATETIME         NOT NULL DEFAULT CURRENT_TIMESTAMP,
    `updated_at`      DATETIME         NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
    FOREIGN KEY (`created_by`) REFERENCES `users`(`id`),
    INDEX `idx_lic_status`      (`status`),
    INDEX `idx_lic_valid_until` (`valid_until`),
    INDEX `idx_lic_type`        (`type`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

CREATE TABLE IF NOT EXISTS `license_contracts` (
    `license_id`  INT UNSIGNED NOT NULL,
    `contract_id` INT UNSIGNED NOT NULL,
    `linked_at`   DATETIME     NOT NULL DEFAULT CURRENT_TIMESTAMP,
    PRIMARY KEY (`license_id`, `contract_id`),
    INDEX `idx_liccon_license`  (`license_id`),
    INDEX `idx_liccon_contract` (`contract_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

INSERT IGNORE INTO `capabilities` (`name`, `description`, `category`, `risk_level`) VALUES
    ('view_licenses',   'Lizenzen einsehen',                     'licenses', 'low'),
    ('manage_licenses', 'Lizenzen anlegen, bearbeiten, löschen', 'licenses', 'medium');

INSERT IGNORE INTO `role_capabilities` (`role_id`, `capability_id`)
    SELECT r.id, c.id FROM `roles` r, `capabilities` c
    WHERE r.is_admin = TRUE AND c.name IN ('view_licenses', 'manage_licenses')
      AND NOT EXISTS (SELECT 1 FROM `role_capabilities` rc WHERE rc.role_id = r.id AND rc.capability_id = c.id);

-- ============================================================
-- Migration 008: Lizenzschlüssel + Lizenzdatei (Spalten in licenses)
-- ============================================================

ALTER TABLE `licenses`
    ADD COLUMN `license_key_enc` TEXT NULL
        COMMENT 'AES-256-CBC verschlüsselter Lizenzschlüssel' AFTER `notes`;

ALTER TABLE `licenses`
    ADD COLUMN `license_file_path` VARCHAR(255) NULL AFTER `license_key_enc`;

ALTER TABLE `licenses`
    ADD COLUMN `license_file_name` VARCHAR(255) NULL AFTER `license_file_path`;

-- ============================================================
-- Migration 009: KI-Chat
-- ============================================================

CREATE TABLE IF NOT EXISTS `ai_chat_sessions` (
    `id`         INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    `user_id`    INT UNSIGNED NOT NULL,
    `title`      VARCHAR(200) DEFAULT NULL,
    `created_at` DATETIME     NOT NULL DEFAULT CURRENT_TIMESTAMP,
    `updated_at` DATETIME     NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
    INDEX `idx_ai_session_user` (`user_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

CREATE TABLE IF NOT EXISTS `ai_chat_messages` (
    `id`         INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    `session_id` INT UNSIGNED NOT NULL,
    `role`       ENUM('user','assistant','system') NOT NULL,
    `content`    TEXT         NOT NULL,
    `created_at` DATETIME     NOT NULL DEFAULT CURRENT_TIMESTAMP,
    INDEX `idx_ai_msg_session` (`session_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

-- ============================================================
-- Migration 010: Telefonbuch-Capability
-- ============================================================

INSERT IGNORE INTO `capabilities` (`name`, `description`, `category`, `risk_level`)
VALUES ('view_phonebook', 'Internes Telefonbuch einsehen', 'phonebook', 'low');

INSERT IGNORE INTO `role_capabilities` (`role_id`, `capability_id`)
SELECT r.id, c.id FROM `roles` r, `capabilities` c WHERE c.name = 'view_phonebook';

-- ============================================================
-- Migration 011: Mobilnummer für directory_persons
-- ============================================================

ALTER TABLE `directory_persons`
    ADD COLUMN `mobile` VARCHAR(100) NULL DEFAULT NULL AFTER `phone`;

-- ============================================================
-- Migration 012: KI-Aktionsprotokoll
-- ============================================================

CREATE TABLE IF NOT EXISTS `ai_action_log` (
    `id`          BIGINT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
    `user_id`     BIGINT UNSIGNED NOT NULL,
    `session_id`  BIGINT UNSIGNED NULL,
    `action_type` VARCHAR(50)  NOT NULL,
    `action_data` TEXT         NULL,
    `result`      TEXT         NULL,
    `created_at`  DATETIME     NOT NULL DEFAULT CURRENT_TIMESTAMP,
    INDEX `idx_ailog_user`    (`user_id`),
    INDEX `idx_ailog_action`  (`action_type`),
    INDEX `idx_ailog_created` (`created_at`),
    CONSTRAINT `fk_ailog_user` FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

-- ============================================================
-- Migration 013: KI-Token-Verbrauch & Kontext-Keywords
-- ============================================================

CREATE TABLE IF NOT EXISTS `ai_usage_log` (
    `id`                BIGINT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
    `user_id`           BIGINT UNSIGNED NOT NULL,
    `session_id`        BIGINT UNSIGNED NULL,
    `provider`          VARCHAR(20)  NOT NULL,
    `model`             VARCHAR(80)  NOT NULL,
    `prompt_tokens`     INT UNSIGNED NOT NULL DEFAULT 0,
    `completion_tokens` INT UNSIGNED NOT NULL DEFAULT 0,
    `total_tokens`      INT UNSIGNED NOT NULL DEFAULT 0,
    `cost_eur`          DECIMAL(10,6)        DEFAULT NULL,
    `created_at`        DATETIME     NOT NULL DEFAULT CURRENT_TIMESTAMP,
    INDEX `idx_aiusage_user`    (`user_id`),
    INDEX `idx_aiusage_created` (`created_at`),
    INDEX `idx_aiusage_model`   (`model`),
    CONSTRAINT `fk_aiusage_user` FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

CREATE TABLE IF NOT EXISTS `ai_context_keywords` (
    `id`          INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
    `context_key` VARCHAR(60)  NOT NULL,
    `keyword`     VARCHAR(100) NOT NULL,
    `created_at`  DATETIME     NOT NULL DEFAULT CURRENT_TIMESTAMP,
    UNIQUE KEY `uq_context_keyword` (`context_key`, `keyword`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

-- ============================================================
-- Migration 014: KI-Chat Sessions Erweiterung
-- ============================================================

ALTER TABLE `ai_chat_sessions`
    ADD COLUMN `title` VARCHAR(200) DEFAULT NULL;

ALTER TABLE `ai_chat_messages`
    MODIFY COLUMN `role` ENUM('user','assistant','system') NOT NULL;

-- ============================================================
-- Migration 015: KI-Chat Feedback
-- ============================================================

CREATE TABLE IF NOT EXISTS `ai_chat_feedback` (
    `id`         INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    `message_id` INT UNSIGNED NOT NULL,
    `user_id`    INT UNSIGNED NOT NULL,
    `rating`     TINYINT(1)   NOT NULL,
    `created_at` DATETIME     NOT NULL DEFAULT CURRENT_TIMESTAMP,
    UNIQUE KEY  `idx_feedback_msg_user` (`message_id`, `user_id`),
    INDEX       `idx_feedback_message`  (`message_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

-- ============================================================
-- Migration 016: WordPress API Tokens
-- ============================================================

CREATE TABLE IF NOT EXISTS `wordpress_api_tokens` (
    `id`           INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    `name`         VARCHAR(255) NOT NULL,
    `token_hash`   CHAR(64)     NOT NULL,
    `created_by`   BIGINT UNSIGNED NOT NULL,
    `created_at`   DATETIME     NOT NULL DEFAULT CURRENT_TIMESTAMP,
    `last_used_at` DATETIME     NULL,
    `is_active`    TINYINT(1)   NOT NULL DEFAULT 1,
    UNIQUE KEY `uk_token_hash` (`token_hash`),
    INDEX `idx_active` (`is_active`),
    CONSTRAINT `fk_wp_tokens_user` FOREIGN KEY (`created_by`) REFERENCES `users` (`id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

-- ============================================================
-- Migration 017: Loan Devices – WordPress Self-Service Flag
-- ============================================================

ALTER TABLE `loan_devices`
    ADD COLUMN `wp_requestable` TINYINT(1) NOT NULL DEFAULT 1
        COMMENT 'Ob das Gerät über das WordPress-Plugin selbst reserviert werden kann'
    AFTER `status`;

-- ============================================================
-- Migration 018: Dateityp-Kategorie für Firmendokumente
-- ============================================================

ALTER TABLE `company_files`
    ADD COLUMN `file_type` VARCHAR(20) NOT NULL DEFAULT 'document'
        COMMENT 'document = Firmendokument, quote = Angebot'
    AFTER `company_id`;

-- ============================================================