prefix; $wahl_id = intval($wahl_id); error_log('kc_run_zuteilung: Start für Wahl '.$wahl_id); // 0. Entferne alte Zuteilungen für diese Wahl $wpdb->delete("{$prefix}kc_zuteilung", ['wahl_id' => $wahl_id]); // 1. Teilnehmer einlesen (alle Phasen werden getrennt verarbeitet) $phasen = intval($wpdb->get_var($wpdb->prepare("SELECT anzahl_einheiten FROM {$prefix}kc_wahlen WHERE id=%d", $wahl_id))); if($phasen < 1) $phasen = 1; // 2. Workshops einlesen (inkl. Kapazität) $workshops_rows = $wpdb->get_results($wpdb->prepare( "SELECT ws.id, ws.name, ws.max_teilnehmer FROM {$prefix}kc_workshops ws JOIN {$prefix}kc_wahl_workshops ww ON ws.id = ww.workshop_id WHERE ww.wahl_id=%d", $wahl_id )); $workshops = []; $workshop_caps = []; foreach($workshops_rows as $w) { $workshops[$w->id] = $w; $workshop_caps[$w->id] = intval($w->max_teilnehmer); } // 3. Force-Zuteilungen lesen (global, nach Phase filtern) $forces_all = $wpdb->get_results($wpdb->prepare("SELECT * FROM {$prefix}kc_force_zuteilung WHERE wahl_id=%d", $wahl_id)); // Prozessiere jede Phase einzeln for($phase=1; $phase<=$phasen; $phase++) { error_log("kc_run_zuteilung: Verarbeite Phase $phase"); // 1) Teilnehmer einlesen (alle TN dieser Wahl+Phase) $teilnehmer = $wpdb->get_results($wpdb->prepare( "SELECT * FROM {$prefix}kc_teilnehmer WHERE wahl_id=%d AND phase=%d", $wahl_id, $phase )); // Prepare helper maps $teilnehmer_by_id = []; foreach($teilnehmer as $t) $teilnehmer_by_id[$t->id] = $t; // keep a full copy for later reassignments if needed $all_teilnehmer_by_id = $teilnehmer_by_id; // 2) Kopie der Kapazitäten für diese Phase (werden während Zuordnung reduziert) $caps = $workshop_caps; // 3) Force-Zuteilungen anwenden (nur für diese Phase) foreach($forces_all as $f) { if(intval($f->phase) !== $phase) continue; $tn_id = intval($f->teilnehmer_id); $ws_id = intval($f->workshop_id); if(!isset($teilnehmer_by_id[$tn_id])) continue; // Teilnehmer nicht in dieser Phase // Nur wenn Workshop existiert und noch Kapazität >=1 if(isset($caps[$ws_id]) && $caps[$ws_id] > 0) { $t = $teilnehmer_by_id[$tn_id]; $wpdb->insert("{$prefix}kc_zuteilung", [ 'wahl_id' => $wahl_id, 'teilnehmer_id' => $t->id, 'vorname' => $t->vorname, 'nachname' => $t->nachname, 'phase' => $phase, 'workshop_id' => $ws_id, 'wunsch_rang' => 0 ]); $caps[$ws_id]--; // Entferne TN aus Liste (er ist bereits zugeteilt) unset($teilnehmer_by_id[$tn_id]); } } // 4) Teilnehmer mischen (zufällige Reihenfolge) $remaining = array_values($teilnehmer_by_id); if(count($remaining) > 1) shuffle($remaining); // Hilfsfunktion: Try assign participant to workshop if cap>0 $assign = function($tn, $ws_id, $rang) use (&$caps, $wpdb, $prefix, $wahl_id, $phase, &$assigned_ids) { if(!isset($caps[$ws_id]) || $caps[$ws_id] <= 0) return false; $wpdb->insert("{$prefix}kc_zuteilung", [ 'wahl_id' => $wahl_id, 'teilnehmer_id' => $tn->id, 'vorname' => $tn->vorname, 'nachname' => $tn->nachname, 'phase' => $phase, 'workshop_id' => $ws_id, 'wunsch_rang' => $rang ]); $caps[$ws_id]--; return true; }; // 5) Wunschrunden 1..3 for($wunsch=1;$wunsch<=3;$wunsch++) { $not_assigned = []; foreach($remaining as $tn) { $ws_choice = intval($tn->{"wunsch$wunsch"}); if($ws_choice > 0 && isset($caps[$ws_choice]) && $caps[$ws_choice] > 0) { $assigned = $assign($tn, $ws_choice, $wunsch); if(!$assigned) $not_assigned[] = $tn; } else { $not_assigned[] = $tn; } } $remaining = $not_assigned; // optional: reshuffle after each round to keep fairness 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;})); foreach($remaining as $tn) { $freie = array_keys(array_filter($caps, function($c){return $c>0;})); if(count($freie) === 0) { // Kein Platz mehr: TN bleibt unzugeordnet $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 ]); continue; } $ws_id = $freie[array_rand($freie)]; $assign($tn, $ws_id, 99); } // 6.5 Consolidate workshops that did not reach their minimal Teilnehmerzahl // Load minimal requirements for workshops in this Wahl $ws_ids_in_wahl = array_keys($workshops); 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)); $min_map = []; foreach($min_rows as $r) $min_map[intval($r->id)] = intval($r->min_teilnehmer); // Count current assignments per workshop $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']); // Find failing workshops (assigned >0 but < min) $failing = []; foreach($ws_ids_in_wahl as $wsid) { $min_req = intval($min_map[$wsid] ?? 0); $cnt = intval($assigned_counts[$wsid] ?? 0); if ($cnt > 0 && $min_req > 0 && $cnt < $min_req) { $failing[] = $wsid; } } if (!empty($failing)) { // collect participants from failing workshops $to_reassign = []; foreach($failing as $fw) { $rows = $wpdb->get_results($wpdb->prepare("SELECT teilnehmer_id FROM {$prefix}kc_zuteilung WHERE wahl_id=%d AND phase=%d AND workshop_id=%d", $wahl_id, $phase, $fw)); foreach($rows as $r) $to_reassign[] = intval($r->teilnehmer_id); } if (!empty($to_reassign)) { // remove those assignments $fw_placeholders = 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_placeholders)"); // free capacity for the failing workshops foreach($failing as $fw) { $freed = intval($assigned_counts[$fw] ?? 0); if (!isset($caps[$fw])) $caps[$fw] = 0; $caps[$fw] += $freed; } // Try to reassign each participant preferring their wishes 1..3 foreach($to_reassign as $tid) { $tn = isset($all_teilnehmer_by_id[$tid]) ? $all_teilnehmer_by_id[$tid] : $wpdb->get_row($wpdb->prepare("SELECT * FROM {$prefix}kc_teilnehmer WHERE id=%d", $tid)); if (!$tn) continue; $reassigned = false; for($w=1;$w<=3;$w++) { $choice = intval($tn->{"wunsch$w"}); if ($choice > 0 && isset($caps[$choice]) && $caps[$choice] > 0) { $assigned = $assign($tn, $choice, $w); if ($assigned) { $reassigned = true; break; } } } if ($reassigned) continue; // otherwise assign to any workshop with free capacity $available = array_keys(array_filter($caps, function($c){return $c>0;})); if (!empty($available)) { $target = $available[array_rand($available)]; $assign($tn, $target, 99); continue; } // lastly, mark as unassigned $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 ]); } } } } // 7) Kapazitätsprüfung (Debug / Log) foreach($caps as $wsid=>$capleft) { if($capleft < 0) { error_log("kc_run_zuteilung: Überbuchung Workshop $wsid in Wahl $wahl_id Phase $phase (restcap=$capleft)"); } } // 8) Ergebnis: alles in DB geschrieben (kc_zuteilung). Logge Zusammenfassung $total_assigned = $wpdb->get_var($wpdb->prepare("SELECT COUNT(*) FROM {$prefix}kc_zuteilung WHERE wahl_id=%d AND phase=%d AND workshop_id IS NOT NULL", $wahl_id, $phase)); $total_unassigned = $wpdb->get_var($wpdb->prepare("SELECT COUNT(*) FROM {$prefix}kc_zuteilung WHERE wahl_id=%d AND phase=%d AND workshop_id IS NULL", $wahl_id, $phase)); error_log("kc_run_zuteilung: Phase $phase - zugeteilt: $total_assigned, ohne Platz: $total_unassigned"); } error_log('kc_run_zuteilung: Fertig für Wahl '.$wahl_id); } function kc_execute_blocks($blocks, &$context, &$i = 0) { $count = count($blocks); while($i < $count) { $block = $blocks[$i]; $block_name = is_array($block) && isset($block['block']) ? $block['block'] : (is_string($block) && strpos($block, ':') !== false ? explode(':', $block, 2)[0] : $block); switch($block_name) { case 'repeat': $repeat_count = 3; if(is_array($block) && isset($block['repeat_count'])) { $repeat_count = intval($block['repeat_count']); } elseif(is_string($block) && strpos($block, ':') !== false) { $parts = explode(':', $block, 2); $repeat_count = intval($parts[1]); } $i++; $start = $i; $inner = []; $depth = 1; while($i < $count && $depth > 0) { $inner_block = $blocks[$i]; $inner_block_name = is_array($inner_block) && isset($inner_block['block']) ? $inner_block['block'] : (is_string($inner_block) && strpos($inner_block, ':') !== false ? explode(':', $inner_block, 2)[0] : $inner_block); if($inner_block_name === 'repeat') $depth++; if($inner_block_name === 'endrepeat') $depth--; if($depth > 0) $inner[] = $inner_block; $i++; } for($r=0;$r<$repeat_count;$r++) { $j = 0; kc_execute_blocks($inner, $context, $j); } break; case 'for_teilnehmer': $i++; $start = $i; $inner = []; $depth = 1; while($i < $count && $depth > 0) { if($blocks[$i] === 'for_teilnehmer') $depth++; if($blocks[$i] === 'endfor_teilnehmer') $depth--; if($depth > 0) $inner[] = $blocks[$i]; $i++; } foreach($context['teilnehmer'] as $tn) { $context['tn'] = $tn; $j = 0; kc_execute_blocks($inner, $context, $j); } break; case 'for_workshop': $i++; $start = $i; $inner = []; $depth = 1; while($i < $count && $depth > 0) { if($blocks[$i] === 'for_workshop') $depth++; if($blocks[$i] === 'endfor_workshop') $depth--; if($depth > 0) $inner[] = $blocks[$i]; $i++; } foreach($context['workshops'] as $ws) { $context['ws'] = $ws; $j = 0; kc_execute_blocks($inner, $context, $j); } break; case 'for_wunsch': $i++; $start = $i; $inner = []; $depth = 1; while($i < $count && $depth > 0) { if($blocks[$i] === 'for_wunsch') $depth++; if($blocks[$i] === 'endfor_wunsch') $depth--; if($depth > 0) $inner[] = $blocks[$i]; $i++; } for($w=1;$w<=3;$w++) { $context['wunsch_nr'] = $w; $j = 0; kc_execute_blocks($inner, $context, $j); } break; // --- Hier die eigentliche Blocklogik einfügen --- case 'shuffle': $rest = array_filter($context['teilnehmer'], function($tn) use ($context) { return !in_array($tn->id, $context['zugeteilt_ids']); }); $rest = array_values($rest); if(count($rest) > 1) { shuffle($rest); $neu = []; $i2 = 0; foreach($context['teilnehmer'] as $tn) { if(!in_array($tn->id, $context['zugeteilt_ids'])) { $neu[] = $rest[$i2++]; } else { $neu[] = $tn; } } $context['teilnehmer'] = $neu; } $i++; break; case 'force': foreach($context['forces'] as $f) { $tn = $context['wpdb']->get_row("SELECT * FROM {$context['prefix']}kc_teilnehmer WHERE id=".intval($f->teilnehmer_id)); if(!$tn) continue; if(isset($context['workshop_caps'][$f->workshop_id]) && $context['workshop_caps'][$f->workshop_id] > 0 && !in_array($tn->id, $context['zugeteilt_ids'])) { $context['wpdb']->insert("{$context['prefix']}kc_zuteilung", [ 'wahl_id' => $context['wahl_id'], 'teilnehmer_id' => $tn->id, 'vorname' => $tn->vorname, 'nachname' => $tn->nachname, 'phase' => $context['phase'], 'workshop_id' => $f->workshop_id, 'wunsch_rang' => 0 ]); $context['workshop_caps'][$f->workshop_id]--; $context['zugeteilt_ids'][] = $tn->id; } } $i++; break; case 'wunsch1': case 'wunsch1_kapazitaet': case 'wunsch2': case 'wunsch2_kapazitaet': case 'wunsch3': case 'wunsch3_kapazitaet': $wunsch_num = ($block === 'wunsch1' || $block === 'wunsch1_kapazitaet') ? 1 : (($block === 'wunsch2' || $block === 'wunsch2_kapazitaet') ? 2 : 3); foreach($context['teilnehmer'] as $tn) { if(in_array($tn->id, $context['zugeteilt_ids'])) continue; $ws_id = intval($tn->{"wunsch$wunsch_num"}); if(isset($context['workshop_caps'][$ws_id]) && $context['workshop_caps'][$ws_id] > 0) { $context['wpdb']->insert("{$context['prefix']}kc_zuteilung", [ 'wahl_id' => $context['wahl_id'], 'teilnehmer_id' => $tn->id, 'vorname' => $tn->vorname, 'nachname' => $tn->nachname, 'phase' => $context['phase'], 'workshop_id' => $ws_id, 'wunsch_rang' => $wunsch_num ]); $context['workshop_caps'][$ws_id]--; $context['zugeteilt_ids'][] = $tn->id; } } $i++; break; case 'random': $freie_workshops = array_keys(array_filter($context['workshop_caps'], function($cap){return $cap>0;})); $rest = array_filter($context['teilnehmer'], function($tn) use ($context) { return !in_array($tn->id, $context['zugeteilt_ids']); }); $rest = array_values($rest); foreach($rest as $tn) { if(count($freie_workshops)>0) { $ws_id = $freie_workshops[array_rand($freie_workshops)]; $context['wpdb']->insert("{$context['prefix']}kc_zuteilung", [ 'wahl_id' => $context['wahl_id'], 'teilnehmer_id' => $tn->id, 'vorname' => $tn->vorname, 'nachname' => $tn->nachname, 'phase' => $context['phase'], 'workshop_id' => $ws_id, 'wunsch_rang' => 99 ]); $context['workshop_caps'][$ws_id]--; $context['zugeteilt_ids'][] = $tn->id; $freie_workshops = array_keys(array_filter($context['workshop_caps'], function($cap){return $cap>0;})); } else { $context['wpdb']->insert("{$context['prefix']}kc_zuteilung", [ 'wahl_id' => $context['wahl_id'], 'teilnehmer_id' => $tn->id, 'vorname' => $tn->vorname, 'nachname' => $tn->nachname, 'phase' => $context['phase'], 'workshop_id' => null, 'wunsch_rang' => -1 ]); } } $i++; break; default: $i++; break; } } }