From 1adff4e7160b74f37a33f51872bdccc9871145a8 Mon Sep 17 00:00:00 2001 From: "Torsten Schulz (local)" Date: Wed, 26 Nov 2025 17:02:36 +0100 Subject: [PATCH] Add functionality to identify elections needing candidates: Introduced a new SQL query to select elections without candidates, including both newly registered and manually created elections. Updated the PoliticsWorker to schedule elections and insert candidates accordingly, ensuring all relevant elections are processed. --- src/worker/politics.rs | 78 ++++++++++++++++++++++++++++++++++++++---- 1 file changed, 71 insertions(+), 7 deletions(-) diff --git a/src/worker/politics.rs b/src/worker/politics.rs index 44d7d2e..21bdc56 100644 --- a/src/worker/politics.rs +++ b/src/worker/politics.rs @@ -213,6 +213,27 @@ const QUERY_INSERT_CANDIDATES: &str = r#" ) AS sub(id); "#; +/// Wahlen finden, für die noch keine Kandidaten existieren. +/// Das umfasst sowohl frisch eingetragene Wahlen als auch manuell +/// angelegte Wahlen, solange: +/// - das Wahl-Datum heute oder in der Zukunft liegt und +/// - noch kein Eintrag in `falukant_data.candidate` existiert. +const QUERY_SELECT_ELECTIONS_NEEDING_CANDIDATES: &str = r#" + SELECT + e.id AS election_id, + e.region_id AS region_id, + e.posts_to_fill + FROM falukant_data.election AS e + WHERE e.region_id IS NOT NULL + AND e.posts_to_fill > 0 + AND e.date::date >= CURRENT_DATE + AND NOT EXISTS ( + SELECT 1 + FROM falukant_data.candidate AS c + WHERE c.election_id = e.id + ); +"#; + const QUERY_PROCESS_EXPIRED_AND_FILL: &str = r#" WITH expired_offices AS ( @@ -536,26 +557,39 @@ impl PoliticsWorker { Self::notify_office_filled(pool, broker, &new_offices_direct)?; } - // 5) Neue Wahlen planen und Kandidaten eintragen - let elections = Self::schedule_elections(pool)?; - if !elections.is_empty() { - Self::insert_candidates_for_elections(pool, &elections)?; + // 5) Neue Wahlen planen + let new_elections = Self::schedule_elections(pool)?; + + // 6) Für alle Wahlen ohne Kandidaten (inkl. manuell + // angelegter) Kandidaten eintragen. + let mut elections_to_fill = Self::find_elections_needing_candidates(pool)?; + // Die gerade neu angelegten Wahlen sind typischerweise auch + // in obiger Liste enthalten. Falls nicht, fügen wir sie + // sicherheitshalber hinzu. + for e in new_elections.iter() { + if !elections_to_fill.iter().any(|x| x.election_id == e.election_id) { + elections_to_fill.push(e.clone()); + } + } + + if !elections_to_fill.is_empty() { + Self::insert_candidates_for_elections(pool, &elections_to_fill)?; // Benachrichtige User in betroffenen Regionen let region_ids: HashSet = - elections.iter().map(|e| e.region_id).collect(); + elections_to_fill.iter().map(|e| e.region_id).collect(); let user_ids = Self::get_user_ids_in_cities_of_regions(pool, ®ion_ids)?; Self::notify_election_created(pool, broker, &user_ids)?; } - // 6) Wahlen auswerten und neu besetzte Ämter melden + // 7) Wahlen auswerten und neu besetzte Ämter melden let new_offices_from_elections = Self::process_elections(pool)?; if !new_offices_from_elections.is_empty() { Self::notify_office_filled(pool, broker, &new_offices_from_elections)?; } - // 7) Als letzter Schritt sicherstellen, dass es für keinen + // 8) Als letzter Schritt sicherstellen, dass es für keinen // Amtstyp/Region-Kombi mehr besetzte Ämter gibt als laut // `seats_per_region` erlaubt. Dieser Abgleich wird nach allen // Lösch- und Besetzungsvorgängen ausgeführt. @@ -741,6 +775,36 @@ impl PoliticsWorker { Ok(elections) } + /// Findet alle existierenden Wahlen (inkl. manuell angelegter), + /// für die noch keine Kandidaten eingetragen wurden. + fn find_elections_needing_candidates(pool: &ConnectionPool) -> Result, DbError> { + let mut conn = pool + .get() + .map_err(|e| DbError::new(format!("DB-Verbindung fehlgeschlagen: {e}")))?; + + conn.prepare( + "select_elections_needing_candidates", + QUERY_SELECT_ELECTIONS_NEEDING_CANDIDATES, + )?; + let rows = conn.execute("select_elections_needing_candidates", &[])?; + + let mut elections = Vec::with_capacity(rows.len()); + for row in rows { + let election_id = parse_i32(&row, "election_id", -1); + let region_id = parse_i32(&row, "region_id", -1); + let posts_to_fill = parse_i32(&row, "posts_to_fill", 0); + if election_id >= 0 && region_id >= 0 && posts_to_fill > 0 { + elections.push(Election { + election_id, + region_id, + posts_to_fill, + }); + } + } + + Ok(elections) + } + fn insert_candidates_for_elections( pool: &ConnectionPool, elections: &[Election],