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.

This commit is contained in:
Torsten Schulz (local)
2025-11-26 17:02:36 +01:00
parent 407cdd9bdc
commit 1adff4e716

View File

@@ -213,6 +213,27 @@ const QUERY_INSERT_CANDIDATES: &str = r#"
) AS sub(id); ) 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#" const QUERY_PROCESS_EXPIRED_AND_FILL: &str = r#"
WITH WITH
expired_offices AS ( expired_offices AS (
@@ -536,26 +557,39 @@ impl PoliticsWorker {
Self::notify_office_filled(pool, broker, &new_offices_direct)?; Self::notify_office_filled(pool, broker, &new_offices_direct)?;
} }
// 5) Neue Wahlen planen und Kandidaten eintragen // 5) Neue Wahlen planen
let elections = Self::schedule_elections(pool)?; let new_elections = Self::schedule_elections(pool)?;
if !elections.is_empty() {
Self::insert_candidates_for_elections(pool, &elections)?; // 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 // Benachrichtige User in betroffenen Regionen
let region_ids: HashSet<i32> = let region_ids: HashSet<i32> =
elections.iter().map(|e| e.region_id).collect(); elections_to_fill.iter().map(|e| e.region_id).collect();
let user_ids = let user_ids =
Self::get_user_ids_in_cities_of_regions(pool, &region_ids)?; Self::get_user_ids_in_cities_of_regions(pool, &region_ids)?;
Self::notify_election_created(pool, broker, &user_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)?; let new_offices_from_elections = Self::process_elections(pool)?;
if !new_offices_from_elections.is_empty() { if !new_offices_from_elections.is_empty() {
Self::notify_office_filled(pool, broker, &new_offices_from_elections)?; 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 // Amtstyp/Region-Kombi mehr besetzte Ämter gibt als laut
// `seats_per_region` erlaubt. Dieser Abgleich wird nach allen // `seats_per_region` erlaubt. Dieser Abgleich wird nach allen
// Lösch- und Besetzungsvorgängen ausgeführt. // Lösch- und Besetzungsvorgängen ausgeführt.
@@ -741,6 +775,36 @@ impl PoliticsWorker {
Ok(elections) 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<Vec<Election>, 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( fn insert_candidates_for_elections(
pool: &ConnectionPool, pool: &ConnectionPool,
elections: &[Election], elections: &[Election],