diff --git a/includes/zuteilungslogik.php b/includes/zuteilungslogik.php index c027878..fddc499 100644 --- a/includes/zuteilungslogik.php +++ b/includes/zuteilungslogik.php @@ -48,7 +48,21 @@ function kc_run_zuteilung($wahl_id) { // 2) Kopie der Kapazitäten für diese Phase (werden während Zuordnung reduziert) $caps = $workshop_caps; - + + // Minimalanzahlen einmalig laden + $ws_ids_in_wahl = array_keys($workshops); + $min_map = []; + if (!empty($ws_ids_in_wahl)) { + $placeholders = implode(',', array_fill(0, count($ws_ids_in_wahl), '%d')); + $min_rows = $wpdb->get_results($wpdb->prepare( + "SELECT id, min_teilnehmer FROM {$prefix}kc_workshops WHERE id IN ($placeholders)", + $ws_ids_in_wahl + )); + foreach($min_rows as $r) { + $min_map[intval($r->id)] = intval($r->min_teilnehmer); + } + } + // 3) Force-Zuteilungen anwenden (nur für diese Phase) foreach($forces_all as $f) { if(intval($f->phase) !== $phase) continue; @@ -93,7 +107,7 @@ function kc_run_zuteilung($wahl_id) { return true; }; - // 5) Wunschrunden 1..3 + // 5) Wunschrunden 1..3 (OHNE Minimalanzahl-Prüfung - volle Priorität!) for($wunsch=1;$wunsch<=3;$wunsch++) { $not_assigned = []; foreach($remaining as $tn) { @@ -110,8 +124,7 @@ function kc_run_zuteilung($wahl_id) { if(count($remaining) > 1) shuffle($remaining); } - // 6) Restliche zufällig verteilen (auf alle freie Workshops) - $freie = array_keys(array_filter($caps, function($c){return $c>0;})); + // 6) Restliche zufällig verteilen (OHNE Minimalanzahl-Prüfung) foreach($remaining as $tn) { $freie = array_keys(array_filter($caps, function($c){return $c>0;})); if(count($freie) === 0) { @@ -131,21 +144,11 @@ function kc_run_zuteilung($wahl_id) { $assign($tn, $ws_id, 99); } - // 6.5 Verbesserte Mindestanzahl-Sicherung: Unterbesetzte Workshops auflösen + // 6.5 NEUE LOGIK: NACH Zufallsverteilung Minimalanzahlen überprüfen + // Unterbesetzte Workshops auflösen und TN nur auf "sichere" Workshops verteilen $ws_ids_in_wahl = array_keys($workshops); + if (!empty($ws_ids_in_wahl)) { - - // min_teilnehmer laden (einmalig) - $placeholders = implode(',', array_fill(0, count($ws_ids_in_wahl), '%d')); - $min_rows = $wpdb->get_results($wpdb->prepare( - "SELECT id, min_teilnehmer FROM {$prefix}kc_workshops WHERE id IN ($placeholders)", - $ws_ids_in_wahl - )); - $min_map = []; - foreach($min_rows as $r) { - $min_map[intval($r->id)] = intval($r->min_teilnehmer); - } - // Aktuelle Belegung zählen $assigned_counts_raw = $wpdb->get_results($wpdb->prepare( "SELECT workshop_id, COUNT(*) AS cnt @@ -160,46 +163,33 @@ function kc_run_zuteilung($wahl_id) { $assigned_counts[intval($ar['workshop_id'])] = intval($ar['cnt']); } - $assigned_counts = []; - foreach($assigned_counts_raw as $ar) { - $assigned_counts[intval($ar['workshop_id'])] = intval($ar['cnt']); - } - - // Unterbesetzte Workshops finden (mit Zuweisung > 0 aber < min) - $failing = []; - foreach($ws_ids_in_wahl as $wsid) { - $min_req = $min_map[$wsid] ?? 0; - $cnt = $assigned_counts[$wsid] ?? 0; - if ($min_req > 0 && $cnt < $min_req) { // ← auch wenn cnt == 0 ! - $failing[] = $wsid; - } - } - - // ──────────────── WICHTIGE DIAGNOSE ──────────────── - error_log("=== DIAGNOSE Mindestanzahl Phase $phase ==="); - error_log("Workshops insgesamt: " . count($ws_ids_in_wahl)); + // ─────────────── DIAGNOSE VOR UMVERTEILUNG ─────────────── + error_log("=== DIAGNOSE Mindestanzahl Phase $phase (VOR Umverteilung) ==="); foreach ($ws_ids_in_wahl as $wsid) { $name = $workshops[$wsid]->name ?? 'ID '.$wsid; $min = $min_map[$wsid] ?? 0; $cnt = $assigned_counts[$wsid] ?? 0; - $cap = $caps[$wsid] ?? 'unbekannt'; $status = ($min > 0 && $cnt > 0 && $cnt < $min) ? 'UNTER MIN → wird umverteilt' : - (($min > 0 && $cnt >= $min) ? 'OK' : - ($cnt == 0 ? 'leer (ignoriert)' : 'keine min-Anforderung')); - error_log("WS $wsid ($name) | min=$min | jetzt=$cnt | Restkap=$cap | $status"); - } - error_log("Anzahl failing Workshops: " . count($failing)); - if (!empty($failing)) { - error_log("Umverteilung startet für WS: " . implode(', ', $failing)); - } else { - error_log("Keine Workshops unter Mindestanzahl mit Teilnehmern → nichts zu tun"); + (($min > 0 && $cnt >= $min) ? 'OK ✓' : ($cnt == 0 ? 'leer' : 'OK (kein Min)')); + error_log("WS $wsid ($name) | min=$min | TN=$cnt | $status"); } error_log("============================"); - if (!empty($failing)) { - error_log("Phase $phase: Folgende Workshops unter Mindestanzahl → Teilnehmer werden umverteilt: " . implode(', ', $failing)); + // Unterbesetzte Workshops finden + $failing = []; + foreach($ws_ids_in_wahl as $wsid) { + $min_req = $min_map[$wsid] ?? 0; + $cnt = $assigned_counts[$wsid] ?? 0; + // Workshop ist unterbesetzt wenn: er hat Teilnehmer, aber weniger als Minimum + if ($min_req > 0 && $cnt > 0 && $cnt < $min_req) { + $failing[] = $wsid; + } + } - // Alle betroffenen Teilnehmer sammeln + if (!empty($failing)) { + error_log("Phase $phase: Workshops UNTER Mindestanzahl: " . implode(', ', $failing) . " → Umverteilung startet"); + + // Alle TN aus unterbesetzten Workshops sammeln $to_reassign = []; foreach($failing as $fw) { $rows = $wpdb->get_results($wpdb->prepare( @@ -213,6 +203,8 @@ function kc_run_zuteilung($wahl_id) { } if (!empty($to_reassign)) { + error_log("Phase $phase: " . count($to_reassign) . " Teilnehmer werden umverteilt"); + // Alte Zuweisungen löschen $fw_list = implode(',', array_map('intval', $failing)); $wpdb->query("DELETE FROM {$prefix}kc_zuteilung @@ -226,7 +218,22 @@ function kc_run_zuteilung($wahl_id) { $caps[$fw] = ($caps[$fw] ?? 0) + $freed; } - // Teilnehmer erneut versuchen zuzuweisen – zuerst Wünsche, dann Rest + // "Sichere" Workshops: Nur die, die KEIN Minimum haben ODER ihr Minimum BEREITS erfüllt haben + $safe_workshops = []; + foreach($ws_ids_in_wahl as $wsid) { + if (in_array($wsid, $failing)) continue; // Unterbesetzte raus + $min_req = $min_map[$wsid] ?? 0; + $cnt = $assigned_counts[$wsid] ?? 0; + // Sicher wenn: kein Min UND Kapazität übrig OR Min erfüllt und noch Platz + if (($min_req == 0 && isset($caps[$wsid]) && $caps[$wsid] > 0) || + ($min_req > 0 && $cnt >= $min_req && isset($caps[$wsid]) && $caps[$wsid] > 0)) { + $safe_workshops[] = $wsid; + } + } + + error_log("Phase $phase: " . count($safe_workshops) . " 'sichere' Workshops für Umverteilung verfügbar"); + + // Teilnehmer auf sichere Workshops verteilen foreach($to_reassign as $tid) { $tn = $all_teilnehmer_by_id[$tid] ?? $wpdb->get_row($wpdb->prepare( "SELECT * FROM {$prefix}kc_teilnehmer WHERE id = %d", $tid @@ -235,10 +242,10 @@ function kc_run_zuteilung($wahl_id) { $reassigned = false; - // Nochmal Wunsch 1–3 versuchen + // Zuerst Wünsche versuchen (nur auf sichere Workshops) for($w = 1; $w <= 3; $w++) { $choice = intval($tn->{"wunsch$w"}); - if ($choice > 0 && isset($caps[$choice]) && $caps[$choice] > 0) { + if ($choice > 0 && in_array($choice, $safe_workshops) && isset($caps[$choice]) && $caps[$choice] > 0) { if ($assign($tn, $choice, $w)) { $reassigned = true; break; @@ -248,12 +255,19 @@ function kc_run_zuteilung($wahl_id) { if ($reassigned) continue; - // Falls kein Wunsch frei → irgendein freier Workshop - $available = array_keys(array_filter($caps, fn($c) => $c > 0)); - if (!empty($available)) { - $target = $available[array_rand($available)]; - if ($assign($tn, $target, 99)) { - $reassigned = true; + // Falls kein Wunsch auf sicherer Liste frei → zufällig auf sichere verteilen + if (!empty($safe_workshops)) { + $available = []; + foreach($safe_workshops as $wsid) { + if (isset($caps[$wsid]) && $caps[$wsid] > 0) { + $available[] = $wsid; + } + } + if (!empty($available)) { + $target = $available[array_rand($available)]; + if ($assign($tn, $target, 99)) { + $reassigned = true; + } } } @@ -270,10 +284,18 @@ function kc_run_zuteilung($wahl_id) { ]); } } + + // Unterbesetzte Workshops sind nun aus dem Pool raus + foreach($failing as $fw) { + error_log("Workshop $fw wird aus dem Pool entfernt (konnte nicht mit Minimum befüllt werden)"); + } } + } else { + error_log("Phase $phase: Alle Workshops erfüllen ihre Minimalanzahl ✓"); } } + // 7) Kapazitätsprüfung (Debug / Log) foreach($caps as $wsid=>$capleft) { if($capleft < 0) {