From f32c4b14560cb6204ad4cbf7972408b3b42cc116 Mon Sep 17 00:00:00 2001 From: "Torsten Schulz (local)" Date: Wed, 14 Jan 2026 15:26:15 +0100 Subject: [PATCH] Add sleep management for productions in ProduceWorker: Implement logic to set productions to sleep when storage capacity is insufficient. Introduce methods for processing and retrieving sleep productions, enhancing inventory management and production flow control. --- src/worker/produce.rs | 126 +++++++++++++++++++++++++++++++++++++++++- src/worker/sql.rs | 59 ++++++++++++++++++++ 2 files changed, 182 insertions(+), 3 deletions(-) diff --git a/src/worker/produce.rs b/src/worker/produce.rs index 69b8507..7094edb 100644 --- a/src/worker/produce.rs +++ b/src/worker/produce.rs @@ -14,6 +14,8 @@ use crate::worker::sql::{ QUERY_DELETE_PRODUCTION, QUERY_INSERT_INVENTORY, QUERY_INSERT_UPDATE_PRODUCTION_LOG, + QUERY_SET_PRODUCTION_SLEEP, + QUERY_GET_SLEEP_PRODUCTIONS, }; use crate::worker::insert_notification_conn; @@ -44,6 +46,7 @@ pub struct ProduceWorker { base: BaseWorker, last_iteration: Option, last_start_fix: Option, + last_sleep_check: Option, } impl ProduceWorker { @@ -52,6 +55,7 @@ impl ProduceWorker { base: BaseWorker::new("ProduceWorker", pool, broker), last_iteration: None, last_start_fix: None, + last_sleep_check: None, } } @@ -72,6 +76,8 @@ impl ProduceWorker { self.base.set_current_step("Process Productions"); self.process_productions(); + self.base.set_current_step("Process Sleep Productions"); + self.process_sleep_productions(); self.base.set_current_step("Signal Activity"); // TODO: Später Analogie zu signalActivity() aus der C++-Basisklasse herstellen. self.base.set_current_step("Loop Done"); @@ -215,12 +221,37 @@ impl ProduceWorker { production_id, } = *production; - if self.add_to_inventory(branch_id, product_id, quantity, quality, user_id) { - self.delete_production(production_id); - self.add_production_to_log(region_id, user_id, product_id, quantity); + // Prüfe VOR dem Abschluss, ob genug Lagerplatz vorhanden ist + if self.has_enough_storage_capacity(branch_id, quantity) { + // Genug Platz: Produktion abschließen + if self.add_to_inventory(branch_id, product_id, quantity, quality, user_id) { + self.delete_production(production_id); + self.add_production_to_log(region_id, user_id, product_id, quantity); + } + } else { + // Nicht genug Platz: Produktion auf sleep setzen + if let Err(err) = self.set_production_sleep(production_id) { + eprintln!("[ProduceWorker] Fehler beim Setzen von sleep für Produktion {}: {}", production_id, err); + } } } + fn has_enough_storage_capacity(&self, branch_id: i32, required_quantity: i32) -> bool { + let stocks = match self.get_available_stocks(branch_id) { + Ok(rows) => rows, + Err(err) => { + eprintln!("[ProduceWorker] Fehler in getAvailableStocks: {err}"); + return false; + } + }; + + let total_free_capacity: i32 = stocks.iter() + .map(|stock| (stock.total_capacity - stock.filled).max(0)) + .sum(); + + total_free_capacity >= required_quantity + } + fn add_to_inventory( &mut self, branch_id: i32, @@ -458,6 +489,95 @@ impl ProduceWorker { Ok(()) } + fn set_production_sleep(&self, production_id: i32) -> Result<(), crate::db::DbError> { + let mut conn = self + .base + .pool + .get() + .map_err(|e| crate::db::DbError::new(format!("DB-Verbindung fehlgeschlagen: {e}")))?; + + conn.prepare("set_production_sleep", QUERY_SET_PRODUCTION_SLEEP)?; + conn.execute("set_production_sleep", &[&production_id])?; + Ok(()) + } + + fn process_sleep_productions(&mut self) { + const SLEEP_CHECK_INTERVAL_SECS: u64 = 300; // 5 Minuten + + let now = Instant::now(); + let should_check = match self.last_sleep_check { + None => { + self.last_sleep_check = Some(now); + true + } + Some(last) => { + if now.saturating_duration_since(last).as_secs() >= SLEEP_CHECK_INTERVAL_SECS { + self.last_sleep_check = Some(now); + true + } else { + false + } + } + }; + + if !should_check { + return; + } + + let sleep_productions = match self.get_sleep_productions() { + Ok(rows) => rows, + Err(err) => { + eprintln!("[ProduceWorker] Fehler beim Laden von sleep-Produktionen: {err}"); + Vec::new() + } + }; + + for production in sleep_productions { + self.handle_sleep_production(&production); + } + } + + fn get_sleep_productions(&self) -> Result, crate::db::DbError> { + let rows = self.load_sleep_productions()?; + Ok(rows + .into_iter() + .filter_map(Self::map_row_to_finished_production) + .collect()) + } + + fn load_sleep_productions(&self) -> Result { + let mut conn = self + .base + .pool + .get() + .map_err(|e| crate::db::DbError::new(format!("DB-Verbindung fehlgeschlagen: {e}")))?; + + conn.prepare("get_sleep_productions", QUERY_GET_SLEEP_PRODUCTIONS)?; + conn.execute("get_sleep_productions", &[]) + } + + fn handle_sleep_production(&mut self, production: &FinishedProduction) { + let FinishedProduction { + branch_id, + product_id, + quantity, + quality, + user_id, + region_id, + production_id, + } = *production; + + // Prüfe erneut, ob jetzt genug Lagerplatz vorhanden ist + if self.has_enough_storage_capacity(branch_id, quantity) { + // Jetzt genug Platz: Produktion abschließen + if self.add_to_inventory(branch_id, product_id, quantity, quality, user_id) { + self.delete_production(production_id); + self.add_production_to_log(region_id, user_id, product_id, quantity); + } + } + // Wenn immer noch nicht genug Platz: Produktion bleibt auf sleep + } + fn insert_or_update_production_log( &self, region_id: i32, diff --git a/src/worker/sql.rs b/src/worker/sql.rs index d510ab9..651b263 100644 --- a/src/worker/sql.rs +++ b/src/worker/sql.rs @@ -1655,6 +1655,7 @@ pub const QUERY_GET_FINISHED_PRODUCTIONS: &str = r#" AND pwe.weather_type_id = w.weather_type_id -- Wetter-Effekte derzeit aus der Qualitätsberechnung entfernt WHERE p.start_timestamp + INTERVAL '1 minute' * pr.production_time <= NOW() + AND COALESCE(p.sleep, FALSE) = FALSE ORDER BY p.start_timestamp; "#; @@ -1663,6 +1664,64 @@ pub const QUERY_DELETE_PRODUCTION: &str = r#" WHERE id = $1; "#; +pub const QUERY_SET_PRODUCTION_SLEEP: &str = r#" + UPDATE falukant_data.production + SET sleep = TRUE + WHERE id = $1; +"#; + +pub const QUERY_GET_SLEEP_PRODUCTIONS: &str = r#" + SELECT + p.id AS production_id, + p.branch_id, + p.product_id, + p.quantity, + p.start_timestamp, + pr.production_time, + br.region_id, + br.falukant_user_id AS user_id, + ROUND( + GREATEST( + 0, + LEAST( + 100, + ( + (COALESCE(k.knowledge, 0) * 0.75 + + COALESCE(k2.knowledge, 0) * 0.25) + * COALESCE(pwe.quality_effect, 100) / 100.0 + ) + ) + ) + )::int AS quality + FROM falukant_data.production p + JOIN falukant_type.product pr + ON p.product_id = pr.id + JOIN falukant_data.branch br + ON p.branch_id = br.id + LEFT JOIN LATERAL ( + SELECT c.id, c.user_id + FROM falukant_data.character c + WHERE c.user_id = br.falukant_user_id + ORDER BY c.updated_at DESC NULLS LAST, c.id DESC + LIMIT 1 + ) c ON TRUE + LEFT JOIN falukant_data.knowledge k + ON p.product_id = k.product_id + AND k.character_id = c.id + LEFT JOIN falukant_data.director d + ON d.employer_user_id = c.user_id + LEFT JOIN falukant_data.knowledge k2 + ON k2.character_id = d.director_character_id + AND k2.product_id = p.product_id + LEFT JOIN falukant_data.weather w + ON w.region_id = br.region_id + LEFT JOIN falukant_type.product_weather_effect pwe + ON pwe.product_id = p.product_id + AND pwe.weather_type_id = w.weather_type_id + WHERE p.sleep = TRUE + ORDER BY p.start_timestamp; +"#; + pub const QUERY_INSERT_UPDATE_PRODUCTION_LOG: &str = r#" INSERT INTO falukant_log.production ( region_id,