Füge SQL-Abfragen für politische Ämter hinzu und implementiere Normalisierung bestehender Ämter
All checks were successful
Deploy yourpart (blue-green) / deploy (push) Successful in 4m1s
All checks were successful
Deploy yourpart (blue-green) / deploy (push) Successful in 4m1s
This commit is contained in:
@@ -6,6 +6,7 @@ use std::sync::atomic::Ordering;
|
||||
use std::sync::Arc;
|
||||
use std::time::{Duration, Instant};
|
||||
use super::base::{BaseWorker, Worker, WorkerState};
|
||||
use crate::worker::falukant_certificate::political_office_name_rank;
|
||||
use crate::worker::sql::{
|
||||
QUERY_COUNT_OFFICES_PER_REGION,
|
||||
QUERY_FIND_OFFICE_GAPS,
|
||||
@@ -22,6 +23,9 @@ use crate::worker::sql::{
|
||||
QUERY_GET_USERS_IN_REGIONS_WITH_ELECTIONS,
|
||||
QUERY_GET_USERS_WITH_FILLED_OFFICES,
|
||||
QUERY_PROCESS_ELECTIONS,
|
||||
QUERY_GET_POLITICAL_OFFICES_FOR_CHARACTER,
|
||||
QUERY_GET_ALL_POLITICAL_OFFICES,
|
||||
QUERY_REMOVE_POLITICAL_OFFICE_BY_ID,
|
||||
QUERY_PLAYER_ELECTION_RESULT_ROWS,
|
||||
QUERY_INSERT_NOTIFICATION,
|
||||
QUERY_TRIM_EXCESS_OFFICES_GLOBAL,
|
||||
@@ -143,6 +147,8 @@ impl PoliticsWorker {
|
||||
eprintln!("[PoliticsWorker] Fehler bei perform_church_office_task: {err}");
|
||||
}
|
||||
|
||||
Self::normalize_existing_political_offices(pool)?;
|
||||
|
||||
// 1) Optional: Positionen evaluieren (aktuell nur Logging/Struktur)
|
||||
Self::evaluate_political_positions(pool)?;
|
||||
|
||||
@@ -169,7 +175,11 @@ impl PoliticsWorker {
|
||||
// 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)?;
|
||||
let new_offices_from_elections =
|
||||
Self::normalize_elected_political_offices(pool, Self::process_elections(pool)?)?;
|
||||
// Beförderungen geben das vorherige Amt wieder frei; die Ersatzwahl
|
||||
// wird noch in diesem Tageslauf angelegt.
|
||||
Self::sync_offices_with_types(pool)?;
|
||||
if let Err(e) = Self::notify_player_election_results(pool, broker) {
|
||||
eprintln!("[PoliticsWorker] notify_player_election_results: {e}");
|
||||
}
|
||||
@@ -616,6 +626,126 @@ impl PoliticsWorker {
|
||||
.collect())
|
||||
}
|
||||
|
||||
/// Bereinigt Bestandsdaten nach derselben Regel wie neue Wahlergebnisse.
|
||||
/// Für mehrere Einträge desselben Amts bleibt der jüngste (größte ID) übrig.
|
||||
fn normalize_existing_political_offices(pool: &ConnectionPool) -> Result<(), DbError> {
|
||||
let mut conn = pool
|
||||
.get()
|
||||
.map_err(|e| DbError::new(format!("DB-Verbindung fehlgeschlagen: {e}")))?;
|
||||
conn.prepare(
|
||||
"get_all_political_offices",
|
||||
QUERY_GET_ALL_POLITICAL_OFFICES,
|
||||
)?;
|
||||
conn.prepare(
|
||||
"remove_political_office_by_id",
|
||||
QUERY_REMOVE_POLITICAL_OFFICE_BY_ID,
|
||||
)?;
|
||||
let rows = conn.execute("get_all_political_offices", &[])?;
|
||||
|
||||
let mut by_character: HashMap<i32, Vec<(i32, i32, i32)>> = HashMap::new();
|
||||
for row in rows {
|
||||
let character_id = parse_i32(&row, "character_id", -1);
|
||||
let office_id = parse_i32(&row, "office_id", -1);
|
||||
let office_type_id = parse_i32(&row, "office_type_id", -1);
|
||||
if character_id < 0 || office_id < 0 || office_type_id < 0 {
|
||||
continue;
|
||||
}
|
||||
let rank = political_office_name_rank(
|
||||
row.get("office_name").map(String::as_str).unwrap_or(""),
|
||||
);
|
||||
by_character
|
||||
.entry(character_id)
|
||||
.or_default()
|
||||
.push((office_id, office_type_id, rank));
|
||||
}
|
||||
|
||||
for offices in by_character.values() {
|
||||
let highest_rank = offices.iter().map(|(_, _, rank)| *rank).max().unwrap_or(0);
|
||||
let mut newest_by_type: HashMap<i32, i32> = HashMap::new();
|
||||
for (office_id, office_type_id, _) in offices {
|
||||
newest_by_type
|
||||
.entry(*office_type_id)
|
||||
.and_modify(|current| *current = (*current).max(*office_id))
|
||||
.or_insert(*office_id);
|
||||
}
|
||||
|
||||
for (office_id, office_type_id, rank) in offices {
|
||||
let duplicate = newest_by_type.get(office_type_id) != Some(office_id);
|
||||
if *rank < highest_rank || duplicate {
|
||||
conn.execute("remove_political_office_by_id", &[office_id])?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Wendet die Ein-Amts-Regel auf jedes soeben durch eine Wahl erzeugte Amt an.
|
||||
/// Höhere Ämter ersetzen niedrigere, Wiederwahlen ersetzen das alte gleiche
|
||||
/// Amt und eine niedrigere Wahl wird nicht als zusätzliches Amt behalten.
|
||||
fn normalize_elected_political_offices(
|
||||
pool: &ConnectionPool,
|
||||
elected_offices: Vec<Office>,
|
||||
) -> Result<Vec<Office>, DbError> {
|
||||
let mut retained = Vec::new();
|
||||
|
||||
for elected in elected_offices {
|
||||
let mut conn = pool
|
||||
.get()
|
||||
.map_err(|e| DbError::new(format!("DB-Verbindung fehlgeschlagen: {e}")))?;
|
||||
conn.prepare(
|
||||
"get_character_political_offices",
|
||||
QUERY_GET_POLITICAL_OFFICES_FOR_CHARACTER,
|
||||
)?;
|
||||
conn.prepare(
|
||||
"remove_political_office_by_id",
|
||||
QUERY_REMOVE_POLITICAL_OFFICE_BY_ID,
|
||||
)?;
|
||||
|
||||
let offices = conn.execute("get_character_political_offices", &[&elected.character_id])?;
|
||||
let Some(new_row) = offices.iter().find(|row| {
|
||||
parse_i32(row, "office_id", -1) == elected.office_id
|
||||
}) else {
|
||||
continue;
|
||||
};
|
||||
let new_rank = political_office_name_rank(
|
||||
new_row.get("office_name").map(String::as_str).unwrap_or(""),
|
||||
);
|
||||
|
||||
let mut remove_new_office = false;
|
||||
let mut offices_to_remove = Vec::new();
|
||||
for existing in &offices {
|
||||
let existing_id = parse_i32(existing, "office_id", -1);
|
||||
if existing_id < 0 || existing_id == elected.office_id {
|
||||
continue;
|
||||
}
|
||||
let existing_type_id = parse_i32(existing, "office_type_id", -1);
|
||||
let existing_rank = political_office_name_rank(
|
||||
existing.get("office_name").map(String::as_str).unwrap_or(""),
|
||||
);
|
||||
|
||||
if existing_type_id == elected.office_type_id || existing_rank < new_rank {
|
||||
offices_to_remove.push(existing_id);
|
||||
} else if existing_rank > new_rank {
|
||||
remove_new_office = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if remove_new_office {
|
||||
conn.execute("remove_political_office_by_id", &[&elected.office_id])?;
|
||||
continue;
|
||||
}
|
||||
|
||||
for office_id in offices_to_remove {
|
||||
conn.execute("remove_political_office_by_id", &[&office_id])?;
|
||||
}
|
||||
retained.push(elected);
|
||||
}
|
||||
|
||||
Ok(retained)
|
||||
}
|
||||
|
||||
fn enforce_election_lead_time(pool: &ConnectionPool) -> Result<(), DbError> {
|
||||
let mut conn = pool
|
||||
.get()
|
||||
|
||||
@@ -1412,6 +1412,41 @@ pub const QUERY_PROCESS_ELECTIONS: &str = r#"
|
||||
FROM falukant_data.process_elections();
|
||||
"#;
|
||||
|
||||
/// Aktuelle politische Ämter eines Charakters; die Rangfolge wird im Daemon
|
||||
/// einheitlich über `political_office_name_rank` bestimmt.
|
||||
pub const QUERY_GET_POLITICAL_OFFICES_FOR_CHARACTER: &str = r#"
|
||||
SELECT po.id AS office_id,
|
||||
po.office_type_id,
|
||||
COALESCE(pot.name, '') AS office_name
|
||||
FROM falukant_data.political_office po
|
||||
JOIN falukant_type.political_office_type pot ON pot.id = po.office_type_id
|
||||
WHERE po.character_id = $1::int;
|
||||
"#;
|
||||
|
||||
pub const QUERY_GET_ALL_POLITICAL_OFFICES: &str = r#"
|
||||
SELECT po.id AS office_id,
|
||||
po.character_id,
|
||||
po.office_type_id,
|
||||
COALESCE(pot.name, '') AS office_name
|
||||
FROM falukant_data.political_office po
|
||||
JOIN falukant_type.political_office_type pot ON pot.id = po.office_type_id
|
||||
ORDER BY po.character_id, po.id;
|
||||
"#;
|
||||
|
||||
/// Entfernt ein einzelnes Amt bei Beförderung bzw. unzulässiger niedriger Wahl
|
||||
/// und schreibt es wie alle regulären Amtsenden in die Historie.
|
||||
pub const QUERY_REMOVE_POLITICAL_OFFICE_BY_ID: &str = r#"
|
||||
WITH removed AS (
|
||||
DELETE FROM falukant_data.political_office
|
||||
WHERE id = $1::int
|
||||
RETURNING character_id, office_type_id, region_id, created_at
|
||||
)
|
||||
INSERT INTO falukant_log.political_office_history
|
||||
(character_id, office_type_id, region_id, start_date, end_date, created_at, updated_at)
|
||||
SELECT character_id, office_type_id, region_id, created_at, NOW(), NOW(), NOW()
|
||||
FROM removed;
|
||||
"#;
|
||||
|
||||
/// Wahlergebnis für Spieler-Kandidaten (`falukant_data.candidate` + `character.user_id IS NOT NULL`).
|
||||
/// Läuft nach `process_elections()`; Deduplizierung über bestehende `election_result`-Notifications (`tr` JSON).
|
||||
pub const QUERY_PLAYER_ELECTION_RESULT_ROWS: &str = r#"
|
||||
|
||||
Reference in New Issue
Block a user