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::sync::Arc;
|
||||||
use std::time::{Duration, Instant};
|
use std::time::{Duration, Instant};
|
||||||
use super::base::{BaseWorker, Worker, WorkerState};
|
use super::base::{BaseWorker, Worker, WorkerState};
|
||||||
|
use crate::worker::falukant_certificate::political_office_name_rank;
|
||||||
use crate::worker::sql::{
|
use crate::worker::sql::{
|
||||||
QUERY_COUNT_OFFICES_PER_REGION,
|
QUERY_COUNT_OFFICES_PER_REGION,
|
||||||
QUERY_FIND_OFFICE_GAPS,
|
QUERY_FIND_OFFICE_GAPS,
|
||||||
@@ -22,6 +23,9 @@ use crate::worker::sql::{
|
|||||||
QUERY_GET_USERS_IN_REGIONS_WITH_ELECTIONS,
|
QUERY_GET_USERS_IN_REGIONS_WITH_ELECTIONS,
|
||||||
QUERY_GET_USERS_WITH_FILLED_OFFICES,
|
QUERY_GET_USERS_WITH_FILLED_OFFICES,
|
||||||
QUERY_PROCESS_ELECTIONS,
|
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_PLAYER_ELECTION_RESULT_ROWS,
|
||||||
QUERY_INSERT_NOTIFICATION,
|
QUERY_INSERT_NOTIFICATION,
|
||||||
QUERY_TRIM_EXCESS_OFFICES_GLOBAL,
|
QUERY_TRIM_EXCESS_OFFICES_GLOBAL,
|
||||||
@@ -143,6 +147,8 @@ impl PoliticsWorker {
|
|||||||
eprintln!("[PoliticsWorker] Fehler bei perform_church_office_task: {err}");
|
eprintln!("[PoliticsWorker] Fehler bei perform_church_office_task: {err}");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Self::normalize_existing_political_offices(pool)?;
|
||||||
|
|
||||||
// 1) Optional: Positionen evaluieren (aktuell nur Logging/Struktur)
|
// 1) Optional: Positionen evaluieren (aktuell nur Logging/Struktur)
|
||||||
Self::evaluate_political_positions(pool)?;
|
Self::evaluate_political_positions(pool)?;
|
||||||
|
|
||||||
@@ -169,7 +175,11 @@ impl PoliticsWorker {
|
|||||||
// 6) Bereits faellige Wahlen auswerten und neu besetzte Aemter melden.
|
// 6) Bereits faellige Wahlen auswerten und neu besetzte Aemter melden.
|
||||||
// Wichtig: Vor dem Anlegen neuer Wahlen ausfuehren, damit frisch
|
// Wichtig: Vor dem Anlegen neuer Wahlen ausfuehren, damit frisch
|
||||||
// angelegte Wahlen nicht im selben Tageslauf wieder verarbeitet werden.
|
// 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) {
|
if let Err(e) = Self::notify_player_election_results(pool, broker) {
|
||||||
eprintln!("[PoliticsWorker] notify_player_election_results: {e}");
|
eprintln!("[PoliticsWorker] notify_player_election_results: {e}");
|
||||||
}
|
}
|
||||||
@@ -616,6 +626,126 @@ impl PoliticsWorker {
|
|||||||
.collect())
|
.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> {
|
fn enforce_election_lead_time(pool: &ConnectionPool) -> Result<(), DbError> {
|
||||||
let mut conn = pool
|
let mut conn = pool
|
||||||
.get()
|
.get()
|
||||||
|
|||||||
@@ -1412,6 +1412,41 @@ pub const QUERY_PROCESS_ELECTIONS: &str = r#"
|
|||||||
FROM falukant_data.process_elections();
|
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`).
|
/// 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).
|
/// Läuft nach `process_elections()`; Deduplizierung über bestehende `election_result`-Notifications (`tr` JSON).
|
||||||
pub const QUERY_PLAYER_ELECTION_RESULT_ROWS: &str = r#"
|
pub const QUERY_PLAYER_ELECTION_RESULT_ROWS: &str = r#"
|
||||||
|
|||||||
Reference in New Issue
Block a user