Füge Logik zur Durchsetzung der Wahlvorlaufzeit hinzu: Implementiere die Funktion enforce_election_lead_time, um sicherzustellen, dass Wahlen erst nach Ablauf von 2 Tagen seit ihrer Erstellung ausgewertet werden. Aktualisiere SQL-Abfragen zur Berücksichtigung des Vorlaufs bei der Planung neuer Wahlen.
All checks were successful
Deploy yourpart (blue-green) / deploy (push) Successful in 1m41s

This commit is contained in:
Torsten Schulz (local)
2026-05-26 10:04:37 +02:00
parent 88eda493ff
commit 86d79c90a5
2 changed files with 51 additions and 17 deletions

View File

@@ -10,6 +10,7 @@ use crate::worker::sql::{
QUERY_COUNT_OFFICES_PER_REGION, QUERY_COUNT_OFFICES_PER_REGION,
QUERY_FIND_OFFICE_GAPS, QUERY_FIND_OFFICE_GAPS,
QUERY_SELECT_NEEDED_ELECTIONS, QUERY_SELECT_NEEDED_ELECTIONS,
QUERY_ENFORCE_ELECTION_LEAD_TIME,
QUERY_INSERT_CANDIDATES, QUERY_INSERT_CANDIDATES,
QUERY_SELECT_ELECTIONS_NEEDING_CANDIDATES, QUERY_SELECT_ELECTIONS_NEEDING_CANDIDATES,
QUERY_PROCESS_EXPIRED_AND_FILL, QUERY_PROCESS_EXPIRED_AND_FILL,
@@ -181,10 +182,31 @@ impl PoliticsWorker {
Self::notify_office_filled(pool, broker, &new_offices_direct)?; Self::notify_office_filled(pool, broker, &new_offices_direct)?;
} }
// 5) Neue Wahlen planen // 4b) Nach dem Entfernen abgelaufener Aemter erneut Luecken
// synchronisieren. Das behebt Rueckstaende, wenn Aemter schon vor
// Tagen/Wochen abgelaufen sind und dadurch erst jetzt echte Gaps
// entstehen.
Self::sync_offices_with_types(pool)?;
// 5) Sicherstellen, dass Wahlen niemals vor Ablauf von 2 Tagen seit
// Anlegen ausgewertet werden.
Self::enforce_election_lead_time(pool)?;
// 6) Bereits faellige Wahlen auswerten und neu besetzte Aemter melden.
// Wichtig: Vor dem Anlegen neuer Wahlen ausfuehren, damit frisch
// angelegte Wahlen nicht im selben Tageslauf wieder verarbeitet werden.
let new_offices_from_elections = Self::process_elections(pool)?;
if let Err(e) = Self::notify_player_election_results(pool, broker) {
eprintln!("[PoliticsWorker] notify_player_election_results: {e}");
}
if !new_offices_from_elections.is_empty() {
Self::notify_office_filled(pool, broker, &new_offices_from_elections)?;
}
// 7) Neue Wahlen planen
let new_elections = Self::schedule_elections(pool)?; let new_elections = Self::schedule_elections(pool)?;
// 6) Für alle Wahlen ohne Kandidaten (inkl. manuell // 8) Fuer alle Wahlen ohne Kandidaten (inkl. manuell
// angelegter) Kandidaten eintragen. // angelegter) Kandidaten eintragen.
let mut elections_to_fill = Self::find_elections_needing_candidates(pool)?; let mut elections_to_fill = Self::find_elections_needing_candidates(pool)?;
// Die gerade neu angelegten Wahlen sind typischerweise auch // Die gerade neu angelegten Wahlen sind typischerweise auch
@@ -207,16 +229,7 @@ impl PoliticsWorker {
Self::notify_election_created(pool, broker, &user_ids)?; Self::notify_election_created(pool, broker, &user_ids)?;
} }
// 7) Wahlen auswerten und neu besetzte Ämter melden // 9) Als letzter Schritt sicherstellen, dass es fuer keinen
let new_offices_from_elections = Self::process_elections(pool)?;
if let Err(e) = Self::notify_player_election_results(pool, broker) {
eprintln!("[PoliticsWorker] notify_player_election_results: {e}");
}
if !new_offices_from_elections.is_empty() {
Self::notify_office_filled(pool, broker, &new_offices_from_elections)?;
}
// 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.
@@ -337,8 +350,8 @@ impl PoliticsWorker {
return Ok(()); return Ok(());
} }
// Für jede Lücke eine Wahl am aktuellen Tag anlegen, sofern es nicht // Für jede Lücke eine Wahl mit 2 Tagen Vorlauf anlegen, sofern es nicht
// bereits eine Wahl für diesen Amtstyp/Region an einem heutigen oder // bereits eine Wahl für diesen Amtstyp/Region an einem gleich späten oder
// zukünftigen Datum gibt. Das eigentliche Anlegen der Wahl und das // zukünftigen Datum gibt. Das eigentliche Anlegen der Wahl und das
// Eintragen von Kandidaten läuft weiterhin über die bestehende // Eintragen von Kandidaten läuft weiterhin über die bestehende
// Logik in der Datenbank (process_elections / schedule_elections). // Logik in der Datenbank (process_elections / schedule_elections).
@@ -353,7 +366,7 @@ impl PoliticsWorker {
(office_type_id, date, posts_to_fill, created_at, updated_at, region_id) (office_type_id, date, posts_to_fill, created_at, updated_at, region_id)
SELECT SELECT
$1::int AS office_type_id, $1::int AS office_type_id,
CURRENT_DATE AS date, (CURRENT_DATE + INTERVAL '2 days')::date AS date,
$2::int AS posts_to_fill, $2::int AS posts_to_fill,
NOW() AS created_at, NOW() AS created_at,
NOW() AS updated_at, NOW() AS updated_at,
@@ -363,7 +376,7 @@ impl PoliticsWorker {
FROM falukant_data.election e FROM falukant_data.election e
WHERE e.office_type_id = $1::int WHERE e.office_type_id = $1::int
AND e.region_id = $3::int AND e.region_id = $3::int
AND e.date::date >= CURRENT_DATE AND e.date::date >= (CURRENT_DATE + INTERVAL '2 days')::date
); );
"#; "#;
@@ -634,6 +647,19 @@ impl PoliticsWorker {
.collect()) .collect())
} }
fn enforce_election_lead_time(pool: &ConnectionPool) -> Result<(), DbError> {
let mut conn = pool
.get()
.map_err(|e| DbError::new(format!("DB-Verbindung fehlgeschlagen: {e}")))?;
conn.prepare("enforce_election_lead_time", QUERY_ENFORCE_ELECTION_LEAD_TIME)
.map_err(|e| DbError::new(format!("[PoliticsWorker] prepare enforce_election_lead_time: {e}")))?;
conn.execute("enforce_election_lead_time", &[])
.map_err(|e| DbError::new(format!("[PoliticsWorker] exec enforce_election_lead_time: {e}")))?;
Ok(())
}
/// Benachrichtigt Spieler-Kandidaten mit Wahlergebnis (Stimmen, Rang, Gewinner, gewonnen?). /// Benachrichtigt Spieler-Kandidaten mit Wahlergebnis (Stimmen, Rang, Gewinner, gewonnen?).
/// `tr` = JSON mit `event: "election_result"` (siehe `QUERY_PLAYER_ELECTION_RESULT_ROWS`). /// `tr` = JSON mit `event: "election_result"` (siehe `QUERY_PLAYER_ELECTION_RESULT_ROWS`).
fn notify_player_election_results( fn notify_player_election_results(

View File

@@ -1113,7 +1113,7 @@ pub const QUERY_FIND_OFFICE_GAPS: &str = r#"
pub const QUERY_SELECT_NEEDED_ELECTIONS: &str = r#" pub const QUERY_SELECT_NEEDED_ELECTIONS: &str = r#"
WITH WITH
target_date AS ( target_date AS (
SELECT NOW()::date AS election_date SELECT (NOW()::date + INTERVAL '2 days')::date AS election_date
), ),
offices_ending_today AS ( offices_ending_today AS (
SELECT po.id, SELECT po.id,
@@ -1186,6 +1186,14 @@ pub const QUERY_SELECT_NEEDED_ELECTIONS: &str = r#"
ORDER BY ne.region_id, ne.election_id; ORDER BY ne.region_id, ne.election_id;
"#; "#;
pub const QUERY_ENFORCE_ELECTION_LEAD_TIME: &str = r#"
UPDATE falukant_data.election AS e
SET date = (e.created_at::date + INTERVAL '2 days')::date,
updated_at = NOW()
WHERE e.created_at IS NOT NULL
AND e.date::date < (e.created_at::date + INTERVAL '2 days')::date;
"#;
pub const QUERY_INSERT_CANDIDATES: &str = r#" pub const QUERY_INSERT_CANDIDATES: &str = r#"
INSERT INTO falukant_data.candidate INSERT INTO falukant_data.candidate
(election_id, character_id, created_at, updated_at) (election_id, character_id, created_at, updated_at)