prepare("SHOW TABLES LIKE :table"); $st->execute(['table' => $table]); return (bool)$st->fetchColumn(); } } if (!function_exists('impfTableHasColumn')) { function impfTableHasColumn(PDO $pdo, string $table, string $column): bool { static $cache = []; $cacheKey = $table . '.' . $column; if (array_key_exists($cacheKey, $cache)) { return $cache[$cacheKey]; } $st = $pdo->prepare("SHOW COLUMNS FROM `" . $table . "` LIKE :column"); $st->execute(['column' => $column]); $cache[$cacheKey] = (bool)$st->fetch(PDO::FETCH_ASSOC); return $cache[$cacheKey]; } } if (!function_exists('impfTableHasIndex')) { function impfTableHasIndex(PDO $pdo, string $table, string $index): bool { $st = $pdo->prepare("SHOW INDEX FROM `" . $table . "` WHERE Key_name = :index_name"); $st->execute(['index_name' => $index]); return (bool)$st->fetch(PDO::FETCH_ASSOC); } } if (!function_exists('impfWeekdayName')) { function impfWeekdayName(int $day): string { $map = [ 1 => 'Montag', 2 => 'Dienstag', 3 => 'Mittwoch', 4 => 'Donnerstag', 5 => 'Freitag', 6 => 'Samstag', 7 => 'Sonntag', ]; return $map[$day] ?? 'Unbekannt'; } } if (!function_exists('impfWorkflowEnsureTables')) { function impfWorkflowEnsureTables(PDO $pdo): void { $pdo->exec("CREATE TABLE IF NOT EXISTS impf_workflow_meta ( meta_key VARCHAR(100) NOT NULL, meta_value VARCHAR(255) NOT NULL DEFAULT '', updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (meta_key) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3"); $pdo->exec("CREATE TABLE IF NOT EXISTS impfstoff_workflow ( impfstoff_id INT NOT NULL, dosen_pro_flasche INT NOT NULL, updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (impfstoff_id) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3"); $pdo->exec("CREATE TABLE IF NOT EXISTS impfstoff_wochenplan ( plan_id INT NOT NULL AUTO_INCREMENT, impfstoff_id INT NOT NULL, wochentag TINYINT NOT NULL, start TIME NOT NULL, ende TIME NOT NULL, impfortid INT NOT NULL, aktiv TINYINT NOT NULL DEFAULT 1, created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (plan_id), INDEX idx_impfstoff_wochenplan_impfstoff (impfstoff_id), INDEX idx_impfstoff_wochenplan_wochentag (wochentag) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3"); $pdo->exec("CREATE TABLE IF NOT EXISTS impf_zeitraum ( zeitraum_id INT NOT NULL AUTO_INCREMENT, bezeichnung VARCHAR(120) NOT NULL DEFAULT '', wochentag TINYINT NOT NULL, start TIME NOT NULL, ende TIME NOT NULL, impfortid INT NOT NULL, aktiv TINYINT NOT NULL DEFAULT 1, created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (zeitraum_id), INDEX idx_impf_zeitraum_wochentag (wochentag), INDEX idx_impf_zeitraum_impfort (impfortid) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3"); if (!impfTableHasColumn($pdo, 'impf_zeitraum', 'bezeichnung')) { $pdo->exec("ALTER TABLE impf_zeitraum ADD COLUMN bezeichnung VARCHAR(120) NOT NULL DEFAULT '' AFTER zeitraum_id"); } $pdo->exec("CREATE TABLE IF NOT EXISTS impf_zeitraum_impfstoff ( zeitraum_id INT NOT NULL, impfstoff_id INT NOT NULL, created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (zeitraum_id, impfstoff_id), INDEX idx_impf_zeitraum_impfstoff_impfstoff (impfstoff_id) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3"); if (impfTableExists($pdo, 'warteliste') && !impfTableHasColumn($pdo, 'warteliste', 'zeitraum_id')) { $pdo->exec("ALTER TABLE warteliste ADD COLUMN zeitraum_id INT NULL AFTER impfenzeitraum"); } if (impfTableExists($pdo, 'warteliste') && !impfTableHasIndex($pdo, 'warteliste', 'idx_warteliste_zeitraum')) { $pdo->exec("ALTER TABLE warteliste ADD INDEX idx_warteliste_zeitraum (zeitraum_id)"); } impfWorkflowMigrateLegacyPlans($pdo); } } if (!function_exists('impfWorkflowGetMeta')) { function impfWorkflowGetMeta(PDO $pdo, string $key): ?string { $st = $pdo->prepare("SELECT meta_value FROM impf_workflow_meta WHERE meta_key = :meta_key LIMIT 1"); $st->execute(['meta_key' => $key]); $value = $st->fetchColumn(); return ($value === false) ? null : (string)$value; } } if (!function_exists('impfWorkflowSetMeta')) { function impfWorkflowSetMeta(PDO $pdo, string $key, string $value): void { $st = $pdo->prepare("INSERT INTO impf_workflow_meta (meta_key, meta_value) VALUES (:meta_key, :meta_value) ON DUPLICATE KEY UPDATE meta_value = VALUES(meta_value)"); $st->execute([ 'meta_key' => $key, 'meta_value' => $value, ]); } } if (!function_exists('impfWorkflowMigrateLegacyPlans')) { function impfWorkflowMigrateLegacyPlans(PDO $pdo): void { if (impfWorkflowGetMeta($pdo, 'legacy_wochenplan_migrated') === '1') { return; } if (!impfTableExists($pdo, 'impfstoff_wochenplan')) { return; } $stLegacy = $pdo->query("SELECT plan_id, impfstoff_id, wochentag, start, ende, impfortid, aktiv FROM impfstoff_wochenplan"); $legacyRows = $stLegacy ? $stLegacy->fetchAll(PDO::FETCH_ASSOC) : []; if (empty($legacyRows)) { impfWorkflowSetMeta($pdo, 'legacy_wochenplan_migrated', '1'); return; } $manageTransaction = !$pdo->inTransaction(); if ($manageTransaction) { $pdo->beginTransaction(); } try { $stFindZeitraum = $pdo->prepare("SELECT zeitraum_id FROM impf_zeitraum WHERE wochentag = :wochentag AND start = :start AND ende = :ende AND impfortid = :impfortid AND aktiv = :aktiv LIMIT 1"); $stInsertZeitraum = $pdo->prepare("INSERT INTO impf_zeitraum (bezeichnung, wochentag, start, ende, impfortid, aktiv) VALUES ('', :wochentag, :start, :ende, :impfortid, :aktiv)"); $stInsertMap = $pdo->prepare("INSERT INTO impf_zeitraum_impfstoff (zeitraum_id, impfstoff_id) VALUES (:zeitraum_id, :impfstoff_id) ON DUPLICATE KEY UPDATE impfstoff_id = VALUES(impfstoff_id)"); foreach ($legacyRows as $row) { $params = [ 'wochentag' => (int)$row['wochentag'], 'start' => (string)$row['start'], 'ende' => (string)$row['ende'], 'impfortid' => (int)$row['impfortid'], 'aktiv' => (int)$row['aktiv'], ]; $stFindZeitraum->execute($params); $zeitraumId = (int)($stFindZeitraum->fetchColumn() ?: 0); if ($zeitraumId <= 0) { $stInsertZeitraum->execute($params); $zeitraumId = (int)$pdo->lastInsertId(); } if ($zeitraumId > 0 && (int)$row['impfstoff_id'] > 0) { $stInsertMap->execute([ 'zeitraum_id' => $zeitraumId, 'impfstoff_id' => (int)$row['impfstoff_id'], ]); } } impfWorkflowSetMeta($pdo, 'legacy_wochenplan_migrated', '1'); if ($manageTransaction) { $pdo->commit(); } } catch (Throwable $e) { if ($manageTransaction && $pdo->inTransaction()) { $pdo->rollBack(); } throw $e; } } } if (!function_exists('impfCsvToIntList')) { function impfCsvToIntList(?string $csv): array { if ($csv === null || trim($csv) === '') { return []; } $werte = array_filter(array_map('trim', explode(',', $csv)), static function ($wert): bool { return $wert !== ''; }); return array_values(array_map('intval', $werte)); } } if (!function_exists('impfZeitraumLabel')) { function impfZeitraumLabel(array $zeitraum): string { $zeitText = impfWeekdayName((int)$zeitraum['wochentag']) . ' ' . substr((string)$zeitraum['start'], 0, 5) . '-' . substr((string)$zeitraum['ende'], 0, 5); $ort = trim((string)($zeitraum['anzeigename'] ?? '') . ' - ' . (string)($zeitraum['adresse'] ?? '')); $ortText = trim($ort, " -"); if ($ortText !== '') { $zeitText .= ' (' . $ortText . ')'; } $bezeichnung = trim((string)($zeitraum['bezeichnung'] ?? '')); if ($bezeichnung !== '') { return $bezeichnung . ': ' . $zeitText; } return $zeitText; } } if (!function_exists('impfGetZeitraumRows')) { function impfGetZeitraumRows(PDO $pdo, bool $onlyActive = true): array { $sql = "SELECT z.zeitraum_id, z.bezeichnung, z.wochentag, z.start, z.ende, z.impfortid, z.aktiv, z.created_at, o.anzeigename, o.adresse, GROUP_CONCAT(DISTINCT m.impfstoff_id ORDER BY m.impfstoff_id SEPARATOR ',') AS impfstoff_ids, GROUP_CONCAT(DISTINCT i.impfname ORDER BY i.impfname SEPARATOR '||') AS impfstoff_namen FROM impf_zeitraum z LEFT JOIN impfort o ON o.ortid = z.impfortid LEFT JOIN impf_zeitraum_impfstoff m ON m.zeitraum_id = z.zeitraum_id LEFT JOIN impfstoff i ON i.impfid = m.impfstoff_id"; if ($onlyActive) { $sql .= " WHERE z.aktiv = 1"; } $sql .= " GROUP BY z.zeitraum_id, z.bezeichnung, z.wochentag, z.start, z.ende, z.impfortid, z.aktiv, z.created_at, o.anzeigename, o.adresse ORDER BY z.wochentag, z.start, z.ende, z.bezeichnung, z.zeitraum_id"; $st = $pdo->prepare($sql); $st->execute(); $rows = $st->fetchAll(PDO::FETCH_ASSOC); foreach ($rows as &$row) { $row['impfstoff_id_list'] = impfCsvToIntList($row['impfstoff_ids'] ?? null); $row['impfstoff_name_list'] = []; if (!empty($row['impfstoff_namen'])) { $row['impfstoff_name_list'] = array_values(array_filter(explode('||', (string)$row['impfstoff_namen']), static function ($name): bool { return trim((string)$name) !== ''; })); } $row['label'] = impfZeitraumLabel($row); } unset($row); return $rows; } } if (!function_exists('impfLoadZeitraumById')) { function impfLoadZeitraumById(PDO $pdo, int $zeitraumId, bool $onlyActive = true): ?array { if ($zeitraumId <= 0) { return null; } $sql = "SELECT z.zeitraum_id, z.bezeichnung, z.wochentag, z.start, z.ende, z.impfortid, z.aktiv, z.created_at, o.anzeigename, o.adresse, GROUP_CONCAT(DISTINCT m.impfstoff_id ORDER BY m.impfstoff_id SEPARATOR ',') AS impfstoff_ids, GROUP_CONCAT(DISTINCT i.impfname ORDER BY i.impfname SEPARATOR '||') AS impfstoff_namen FROM impf_zeitraum z LEFT JOIN impfort o ON o.ortid = z.impfortid LEFT JOIN impf_zeitraum_impfstoff m ON m.zeitraum_id = z.zeitraum_id LEFT JOIN impfstoff i ON i.impfid = m.impfstoff_id WHERE z.zeitraum_id = :zeitraum_id"; if ($onlyActive) { $sql .= " AND z.aktiv = 1"; } $sql .= " GROUP BY z.zeitraum_id, z.bezeichnung, z.wochentag, z.start, z.ende, z.impfortid, z.aktiv, z.created_at, o.anzeigename, o.adresse LIMIT 1"; $st = $pdo->prepare($sql); $st->execute(['zeitraum_id' => $zeitraumId]); $row = $st->fetch(PDO::FETCH_ASSOC); if (!$row) { return null; } $row['impfstoff_id_list'] = impfCsvToIntList($row['impfstoff_ids'] ?? null); $row['impfstoff_name_list'] = []; if (!empty($row['impfstoff_namen'])) { $row['impfstoff_name_list'] = array_values(array_filter(explode('||', (string)$row['impfstoff_namen']), static function ($name): bool { return trim((string)$name) !== ''; })); } $row['label'] = impfZeitraumLabel($row); return $row; } } if (!function_exists('impfGetZeitraeumeByImpfstoff')) { function impfGetZeitraeumeByImpfstoff(PDO $pdo, bool $onlyActive = true): array { $zeitraeume = impfGetZeitraumRows($pdo, $onlyActive); $result = []; foreach ($zeitraeume as $zeitraum) { foreach ($zeitraum['impfstoff_id_list'] as $impfstoffId) { if (!isset($result[$impfstoffId])) { $result[$impfstoffId] = []; } $result[$impfstoffId][] = $zeitraum; } } return $result; } }