Enhance workshop assignment logic with minimum participant checks and improved reassignment handling
All checks were successful
Deploy Workshop-Wahlen (DEV / PROD) / deploy (push) Successful in 11s

This commit is contained in:
ProgrammGamer
2026-03-02 13:00:40 +01:00
parent 0f5d575cb9
commit 8a7b0d71ee

View File

@@ -48,7 +48,21 @@ function kc_run_zuteilung($wahl_id) {
// 2) Kopie der Kapazitäten für diese Phase (werden während Zuordnung reduziert) // 2) Kopie der Kapazitäten für diese Phase (werden während Zuordnung reduziert)
$caps = $workshop_caps; $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) // 3) Force-Zuteilungen anwenden (nur für diese Phase)
foreach($forces_all as $f) { foreach($forces_all as $f) {
if(intval($f->phase) !== $phase) continue; if(intval($f->phase) !== $phase) continue;
@@ -93,7 +107,7 @@ function kc_run_zuteilung($wahl_id) {
return true; return true;
}; };
// 5) Wunschrunden 1..3 // 5) Wunschrunden 1..3 (OHNE Minimalanzahl-Prüfung - volle Priorität!)
for($wunsch=1;$wunsch<=3;$wunsch++) { for($wunsch=1;$wunsch<=3;$wunsch++) {
$not_assigned = []; $not_assigned = [];
foreach($remaining as $tn) { foreach($remaining as $tn) {
@@ -110,8 +124,7 @@ function kc_run_zuteilung($wahl_id) {
if(count($remaining) > 1) shuffle($remaining); if(count($remaining) > 1) shuffle($remaining);
} }
// 6) Restliche zufällig verteilen (auf alle freie Workshops) // 6) Restliche zufällig verteilen (OHNE Minimalanzahl-Prüfung)
$freie = array_keys(array_filter($caps, function($c){return $c>0;}));
foreach($remaining as $tn) { foreach($remaining as $tn) {
$freie = array_keys(array_filter($caps, function($c){return $c>0;})); $freie = array_keys(array_filter($caps, function($c){return $c>0;}));
if(count($freie) === 0) { if(count($freie) === 0) {
@@ -131,21 +144,11 @@ function kc_run_zuteilung($wahl_id) {
$assign($tn, $ws_id, 99); $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); $ws_ids_in_wahl = array_keys($workshops);
if (!empty($ws_ids_in_wahl)) { 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 // Aktuelle Belegung zählen
$assigned_counts_raw = $wpdb->get_results($wpdb->prepare( $assigned_counts_raw = $wpdb->get_results($wpdb->prepare(
"SELECT workshop_id, COUNT(*) AS cnt "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[intval($ar['workshop_id'])] = intval($ar['cnt']);
} }
$assigned_counts = []; // ─────────────── DIAGNOSE VOR UMVERTEILUNG ───────────────
foreach($assigned_counts_raw as $ar) { error_log("=== DIAGNOSE Mindestanzahl Phase $phase (VOR Umverteilung) ===");
$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));
foreach ($ws_ids_in_wahl as $wsid) { foreach ($ws_ids_in_wahl as $wsid) {
$name = $workshops[$wsid]->name ?? 'ID '.$wsid; $name = $workshops[$wsid]->name ?? 'ID '.$wsid;
$min = $min_map[$wsid] ?? 0; $min = $min_map[$wsid] ?? 0;
$cnt = $assigned_counts[$wsid] ?? 0; $cnt = $assigned_counts[$wsid] ?? 0;
$cap = $caps[$wsid] ?? 'unbekannt';
$status = ($min > 0 && $cnt > 0 && $cnt < $min) ? 'UNTER MIN → wird umverteilt' : $status = ($min > 0 && $cnt > 0 && $cnt < $min) ? 'UNTER MIN → wird umverteilt' :
(($min > 0 && $cnt >= $min) ? 'OK' : (($min > 0 && $cnt >= $min) ? 'OK' : ($cnt == 0 ? 'leer' : 'OK (kein Min)'));
($cnt == 0 ? 'leer (ignoriert)' : 'keine min-Anforderung')); error_log("WS $wsid ($name) | min=$min | TN=$cnt | $status");
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");
} }
error_log("============================"); error_log("============================");
if (!empty($failing)) { // Unterbesetzte Workshops finden
error_log("Phase $phase: Folgende Workshops unter Mindestanzahl → Teilnehmer werden umverteilt: " . implode(', ', $failing)); $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 = []; $to_reassign = [];
foreach($failing as $fw) { foreach($failing as $fw) {
$rows = $wpdb->get_results($wpdb->prepare( $rows = $wpdb->get_results($wpdb->prepare(
@@ -213,6 +203,8 @@ function kc_run_zuteilung($wahl_id) {
} }
if (!empty($to_reassign)) { if (!empty($to_reassign)) {
error_log("Phase $phase: " . count($to_reassign) . " Teilnehmer werden umverteilt");
// Alte Zuweisungen löschen // Alte Zuweisungen löschen
$fw_list = implode(',', array_map('intval', $failing)); $fw_list = implode(',', array_map('intval', $failing));
$wpdb->query("DELETE FROM {$prefix}kc_zuteilung $wpdb->query("DELETE FROM {$prefix}kc_zuteilung
@@ -226,7 +218,22 @@ function kc_run_zuteilung($wahl_id) {
$caps[$fw] = ($caps[$fw] ?? 0) + $freed; $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) { foreach($to_reassign as $tid) {
$tn = $all_teilnehmer_by_id[$tid] ?? $wpdb->get_row($wpdb->prepare( $tn = $all_teilnehmer_by_id[$tid] ?? $wpdb->get_row($wpdb->prepare(
"SELECT * FROM {$prefix}kc_teilnehmer WHERE id = %d", $tid "SELECT * FROM {$prefix}kc_teilnehmer WHERE id = %d", $tid
@@ -235,10 +242,10 @@ function kc_run_zuteilung($wahl_id) {
$reassigned = false; $reassigned = false;
// Nochmal Wunsch 13 versuchen // Zuerst Wünsche versuchen (nur auf sichere Workshops)
for($w = 1; $w <= 3; $w++) { for($w = 1; $w <= 3; $w++) {
$choice = intval($tn->{"wunsch$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)) { if ($assign($tn, $choice, $w)) {
$reassigned = true; $reassigned = true;
break; break;
@@ -248,12 +255,19 @@ function kc_run_zuteilung($wahl_id) {
if ($reassigned) continue; if ($reassigned) continue;
// Falls kein Wunsch frei → irgendein freier Workshop // Falls kein Wunsch auf sicherer Liste frei → zufällig auf sichere verteilen
$available = array_keys(array_filter($caps, fn($c) => $c > 0)); if (!empty($safe_workshops)) {
if (!empty($available)) { $available = [];
$target = $available[array_rand($available)]; foreach($safe_workshops as $wsid) {
if ($assign($tn, $target, 99)) { if (isset($caps[$wsid]) && $caps[$wsid] > 0) {
$reassigned = true; $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) // 7) Kapazitätsprüfung (Debug / Log)
foreach($caps as $wsid=>$capleft) { foreach($caps as $wsid=>$capleft) {
if($capleft < 0) { if($capleft < 0) {