From 6e931c1069686fc6c8957a659b8281a7564eee4c Mon Sep 17 00:00:00 2001 From: "Torsten Schulz (local)" Date: Mon, 13 Apr 2026 15:55:34 +0200 Subject: [PATCH] Implement director resignation processing in DirectorWorker: Added logic to check for director resignations based on satisfaction levels, including new SQL queries for retrieving candidates and deleting directors. Enhanced notification system to alert users of high resignation risks, improving overall management of director statuses. --- src/worker/director.rs | 101 +++++++++++++++++++++++++++++++++++++++++ src/worker/sql.rs | 15 ++++++ 2 files changed, 116 insertions(+) diff --git a/src/worker/director.rs b/src/worker/director.rs index 3a6d14b..daec39d 100644 --- a/src/worker/director.rs +++ b/src/worker/director.rs @@ -1,4 +1,5 @@ use crate::db::{DbConnection, DbError, Row}; +use rand::Rng; use std::collections::{HashMap, HashSet}; use crate::message_broker::MessageBroker; use std::sync::atomic::Ordering; @@ -26,6 +27,8 @@ use crate::worker::sql::{ QUERY_SET_SALARY_PAYED, QUERY_UPDATE_SATISFACTION, QUERY_GET_DIRECTOR_USER, + QUERY_GET_DIRECTORS_FOR_RESIGNATION_CHECK, + QUERY_DELETE_DIRECTOR_BY_ID, QUERY_COUNT_VEHICLES_IN_BRANCH_REGION, QUERY_COUNT_VEHICLES_IN_REGION, QUERY_CHECK_ROUTE, @@ -36,6 +39,7 @@ use crate::worker::sql::{ QUERY_GET_USER_OFFICES, QUERY_CUMULATIVE_TAX_NO_EXEMPT, QUERY_CUMULATIVE_TAX_WITH_EXEMPT, + QUERY_INSERT_NOTIFICATION, }; #[derive(Debug, Clone)] @@ -47,6 +51,14 @@ struct Director { may_start_transport: bool, } +#[derive(Debug, Clone)] +struct DirectorResignationCandidate { + id: i32, + employer_user_id: i32, + director_character_id: i32, + satisfaction: i32, +} + #[derive(Debug, Clone)] struct ProductionPlan { falukant_user_id: i32, @@ -91,6 +103,7 @@ struct TransportVehicle { pub struct DirectorWorker { base: BaseWorker, last_run: Option, + last_resignation_check: Option, } // Maximale Anzahl paralleler Produktionen pro Branch @@ -114,6 +127,7 @@ impl DirectorWorker { Self { base: BaseWorker::new("DirectorWorker", pool, broker), last_run: None, + last_resignation_check: None, } } @@ -141,6 +155,84 @@ impl DirectorWorker { self.perform_task()?; self.pay_salary()?; self.calculate_satisfaction()?; + self.process_director_resignations()?; + Ok(()) + } + + fn process_director_resignations(&mut self) -> Result<(), DbError> { + let now = Instant::now(); + let should_run = match self.last_resignation_check { + None => true, + Some(last) => now.saturating_duration_since(last) >= Duration::from_secs(24 * 60 * 60), + }; + if !should_run { + return Ok(()); + } + + let mut conn = self + .base + .pool + .get() + .map_err(|e| DbError::new(format!("DB-Verbindung fehlgeschlagen: {e}")))?; + conn.prepare( + "get_directors_for_resignation_check", + QUERY_GET_DIRECTORS_FOR_RESIGNATION_CHECK, + )?; + conn.prepare("delete_director_by_id", QUERY_DELETE_DIRECTOR_BY_ID)?; + conn.prepare("insert_notification", QUERY_INSERT_NOTIFICATION)?; + + let rows = conn.execute("get_directors_for_resignation_check", &[])?; + let candidates: Vec = rows + .into_iter() + .filter_map(Self::map_row_to_resignation_candidate) + .collect(); + + let mut rng = rand::thread_rng(); + for candidate in candidates { + let sat = candidate.satisfaction.clamp(0, 100) as f64; + let resignation_probability = 1.0 - (sat / 100.0); + if resignation_probability > 0.5 { + let risk_percent = (resignation_probability * 100.0 * 100.0).round() / 100.0; + let payload = format!( + r#"{{"tr":"director.resignation_risk_high","event":"director_resignation_risk_high","director_id":{},"director_character_id":{},"risk_percent":{},"satisfaction":{},"threshold_percent":50}}"#, + candidate.id, + candidate.director_character_id, + risk_percent, + candidate.satisfaction.clamp(0, 100) + ); + let _ = conn.execute("insert_notification", &[&candidate.employer_user_id, &payload]); + } + let roll: f64 = rng.gen_range(0.0..1.0); + if roll >= resignation_probability { + continue; + } + + let deleted = conn.execute("delete_director_by_id", &[&candidate.id])?; + if deleted.is_empty() { + continue; + } + + eprintln!( + "[DirectorWorker] Director {} kündigt (character_id={}, user_id={}, satisfaction={}, prob={:.4}, roll={:.4})", + candidate.id, + candidate.director_character_id, + candidate.employer_user_id, + candidate.satisfaction, + resignation_probability, + roll + ); + + self.base.broker.publish(format!( + r#"{{"event":"directorchanged","user_id":{}}}"#, + candidate.employer_user_id + )); + self.base.broker.publish(format!( + r#"{{"event":"falukantUpdateStatus","user_id":{}}}"#, + candidate.employer_user_id + )); + } + + self.last_resignation_check = Some(now); Ok(()) } @@ -198,6 +290,15 @@ impl DirectorWorker { }) } + fn map_row_to_resignation_candidate(row: Row) -> Option { + Some(DirectorResignationCandidate { + id: row.get("id")?.parse().ok()?, + employer_user_id: row.get("employer_user_id")?.parse().ok()?, + director_character_id: row.get("director_character_id")?.parse().ok()?, + satisfaction: row.get("satisfaction")?.parse().ok()?, + }) + } + fn start_productions(&mut self, director: &Director) -> Result<(), DbError> { self.base .set_current_step("DirectorWorker: start_productions"); diff --git a/src/worker/sql.rs b/src/worker/sql.rs index 0758b5d..00e4dad 100644 --- a/src/worker/sql.rs +++ b/src/worker/sql.rs @@ -638,6 +638,21 @@ pub const QUERY_GET_DIRECTOR_USER: &str = r#" SELECT fu.id AS falukant_user_id FROM falukant_data.director d JOIN falukant_data.falukant_user fu ON fu.id = d.employer_user_id WHERE d.id = $1 LIMIT 1; "#; +pub const QUERY_GET_DIRECTORS_FOR_RESIGNATION_CHECK: &str = r#" +SELECT d.id, + d.employer_user_id, + d.director_character_id, + COALESCE(d.satisfaction, 0)::int AS satisfaction + FROM falukant_data.director d + WHERE d.employer_user_id IS NOT NULL; +"#; + +pub const QUERY_DELETE_DIRECTOR_BY_ID: &str = r#" +DELETE FROM falukant_data.director + WHERE id = $1::int + RETURNING employer_user_id, director_character_id; +"#; + pub const QUERY_COUNT_VEHICLES_IN_BRANCH_REGION: &str = r#" SELECT COUNT(v.id) AS cnt FROM falukant_data.vehicle v WHERE v.falukant_user_id = $1 AND v.region_id = $2; "#;