From 0cabca874d5786d6cae9727c51879fdd57febc2b Mon Sep 17 00:00:00 2001 From: ProgrammGamer Date: Mon, 2 Mar 2026 13:08:02 +0100 Subject: [PATCH] Implement iterative minimum participant checks for workshop assignments --- includes/zuteilungslogik.php | 268 +++++++++++++++++++---------------- 1 file changed, 144 insertions(+), 124 deletions(-) diff --git a/includes/zuteilungslogik.php b/includes/zuteilungslogik.php index fddc499..c097f48 100644 --- a/includes/zuteilungslogik.php +++ b/includes/zuteilungslogik.php @@ -144,50 +144,47 @@ function kc_run_zuteilung($wahl_id) { $assign($tn, $ws_id, 99); } - // 6.5 NEUE LOGIK: NACH Zufallsverteilung Minimalanzahlen überprüfen - // Unterbesetzte Workshops auflösen und TN nur auf "sichere" Workshops verteilen + // 6.5 ITERATIVE MINIMALANZAHL-SICHERUNG: Bis alle erfüllt sind $ws_ids_in_wahl = array_keys($workshops); + $iteration = 0; + $max_iterations = 10; - if (!empty($ws_ids_in_wahl)) { - // Aktuelle Belegung zählen - $assigned_counts_raw = $wpdb->get_results($wpdb->prepare( - "SELECT workshop_id, COUNT(*) AS cnt - FROM {$prefix}kc_zuteilung - WHERE wahl_id=%d AND phase=%d AND workshop_id IS NOT NULL - GROUP BY workshop_id", - $wahl_id, $phase - ), 'ARRAY_A'); + while($iteration < $max_iterations) { + $iteration++; + error_log("Phase $phase: Minimalanzahl-Check Iteration $iteration"); + + if (!empty($ws_ids_in_wahl)) { + // Aktuelle Belegung zählen + $assigned_counts_raw = $wpdb->get_results($wpdb->prepare( + "SELECT workshop_id, COUNT(*) AS cnt + FROM {$prefix}kc_zuteilung + WHERE wahl_id=%d AND phase=%d AND workshop_id IS NOT NULL + GROUP BY workshop_id", + $wahl_id, $phase + ), 'ARRAY_A'); - $assigned_counts = []; - foreach($assigned_counts_raw as $ar) { - $assigned_counts[intval($ar['workshop_id'])] = intval($ar['cnt']); - } - - // ─────────────── 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; - $status = ($min > 0 && $cnt > 0 && $cnt < $min) ? 'UNTER MIN → wird umverteilt' : - (($min > 0 && $cnt >= $min) ? 'OK ✓' : ($cnt == 0 ? 'leer' : 'OK (kein Min)')); - error_log("WS $wsid ($name) | min=$min | TN=$cnt | $status"); - } - error_log("============================"); - - // 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; + $assigned_counts = []; + foreach($assigned_counts_raw as $ar) { + $assigned_counts[intval($ar['workshop_id'])] = intval($ar['cnt']); } - } - if (!empty($failing)) { - error_log("Phase $phase: Workshops UNTER Mindestanzahl: " . implode(', ', $failing) . " → Umverteilung startet"); + // Unterbesetzte Workshops finden (NUR die mit Teilnehmern, 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 > 0 && $cnt < $min_req) { + $failing[] = $wsid; + } + } + + if (empty($failing)) { + // Alle erfüllt! + error_log("Phase $phase: ✓ Alle Workshops erfüllen ihre Minimalanzahl (Iteration $iteration)"); + break; + } + + error_log("Phase $phase [Iter $iteration]: $" . count($failing) . " Workshops UNTER Mindestanzahl → umverteilen"); // Alle TN aus unterbesetzten Workshops sammeln $to_reassign = []; @@ -202,98 +199,121 @@ function kc_run_zuteilung($wahl_id) { } } - if (!empty($to_reassign)) { - error_log("Phase $phase: " . count($to_reassign) . " Teilnehmer werden umverteilt"); + if (empty($to_reassign)) { + // Keine TN zu umverteilen + error_log("Phase $phase [Iter $iteration]: Keine Teilnehmer in unterbesetzten Workshops gefunden"); + break; + } + + error_log("Phase $phase [Iter $iteration]: " . count($to_reassign) . " Teilnehmer werden entfernt und neu verteilt"); + + // Alle Zuweisungen aus unterbesetzten Workshops löschen + $fw_list = implode(',', array_map('intval', $failing)); + $wpdb->query("DELETE FROM {$prefix}kc_zuteilung + WHERE wahl_id = " . intval($wahl_id) . " + AND phase = " . intval($phase) . " + AND workshop_id IN ($fw_list)"); + + // Kapazitäten wieder freigeben + foreach($failing as $fw) { + $freed = $assigned_counts[$fw] ?? 0; + $caps[$fw] = $workshop_caps[$fw]; // Auf max zurücksetzen + } + + // "Sichere" Workshops für Umverteilung: Kein Min ODER Min erfüllt (OHNE underbesetzte) + $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; - // Alte Zuweisungen löschen - $fw_list = implode(',', array_map('intval', $failing)); - $wpdb->query("DELETE FROM {$prefix}kc_zuteilung - WHERE wahl_id = " . intval($wahl_id) . " - AND phase = " . intval($phase) . " - AND workshop_id IN ($fw_list)"); - - // Kapazitäten wieder freigeben - foreach($failing as $fw) { - $freed = $assigned_counts[$fw] ?? 0; - $caps[$fw] = ($caps[$fw] ?? 0) + $freed; - } - - // "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; + // Sicher wenn: + // A) Kein Minimum UND noch Platz, ODER + // B) Min > 0 UND Min schon erreicht UND noch Platz, ODER + // C) Min > 0 UND alles folgenden TN passen rein (reicht für Min) + $remaining_tns_to_reassign = count($to_reassign); + $cap_left = $caps[$wsid] ?? 0; + + $can_fit = false; + if ($min_req == 0) { + $can_fit = $cap_left > 0; + } else { + // Wenn Min schon erfüllt, kann man noch hinzufügen + $current_cnt = $assigned_counts[$wsid] ?? 0; + if ($current_cnt >= $min_req && $cap_left > 0) { + $can_fit = true; + } + // ODER: Wenn noch Platz für den Rest + Min + elseif ($cap_left >= $min_req) { + $can_fit = true; } } - - 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 - )); - if (!$tn) continue; - - $reassigned = false; - - // Zuerst Wünsche versuchen (nur auf sichere Workshops) - for($w = 1; $w <= 3; $w++) { - $choice = intval($tn->{"wunsch$w"}); - if ($choice > 0 && in_array($choice, $safe_workshops) && isset($caps[$choice]) && $caps[$choice] > 0) { - if ($assign($tn, $choice, $w)) { - $reassigned = true; - break; - } - } - } - - if ($reassigned) continue; - - // 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; - } - } - } - - // Wenn immer noch nichts frei → unassigned - if (!$reassigned) { - $wpdb->insert("{$prefix}kc_zuteilung", [ - 'wahl_id' => $wahl_id, - 'teilnehmer_id' => $tn->id, - 'vorname' => $tn->vorname, - 'nachname' => $tn->nachname, - 'phase' => $phase, - 'workshop_id' => null, - 'wunsch_rang' => -1 - ]); - } - } - - // 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)"); + + if ($can_fit) { + $safe_workshops[] = $wsid; + } + } + + error_log("Phase $phase [Iter $iteration]: " . count($safe_workshops) . " sichere Workshops verfügbar"); + + // Teilnehmer auf sichere Workshops neu 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 + )); + if (!$tn) continue; + + $reassigned = false; + + // Zuerst Wünsche auf sichere Workshops + for($w = 1; $w <= 3; $w++) { + $choice = intval($tn->{"wunsch$w"}); + if ($choice > 0 && in_array($choice, $safe_workshops) && isset($caps[$choice]) && $caps[$choice] > 0) { + if ($assign($tn, $choice, $w)) { + $reassigned = true; + break; + } + } + } + + if ($reassigned) continue; + + // Zufällig auf sichere Workshops + 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; + } + } + } + + // Falls immer noch nichts frei → unzugewiesen + if (!$reassigned) { + $wpdb->insert("{$prefix}kc_zuteilung", [ + 'wahl_id' => $wahl_id, + 'teilnehmer_id' => $tn->id, + 'vorname' => $tn->vorname, + 'nachname' => $tn->nachname, + 'phase' => $phase, + 'workshop_id' => null, + 'wunsch_rang' => -1 + ]); } } - } else { - error_log("Phase $phase: Alle Workshops erfüllen ihre Minimalanzahl ✓"); } } + + if ($iteration >= $max_iterations) { + error_log("Phase $phase: WARNUNG - Max Iterationen ($max_iterations) erreicht. Es könnten noch unterbesetzte Workshops existieren."); + } + // 7) Kapazitätsprüfung (Debug / Log)