diff --git a/src/worker/director.rs b/src/worker/director.rs index 48550d6..741f68a 100644 --- a/src/worker/director.rs +++ b/src/worker/director.rs @@ -282,6 +282,7 @@ const QUERY_GET_USER_BRANCHES: &str = r#" "#; // Freie Transportmittel in einer Region (nicht in aktiven Transporten) +// Ein Transport ist aktiv, wenn er noch in der Tabelle existiert const QUERY_GET_FREE_VEHICLES_IN_REGION: &str = r#" SELECT v.id AS vehicle_id, @@ -295,7 +296,6 @@ const QUERY_GET_FREE_VEHICLES_IN_REGION: &str = r#" SELECT DISTINCT t.vehicle_id FROM falukant_data.transport t WHERE t.vehicle_id IS NOT NULL - AND t.arrived_at IS NULL ); "#; @@ -747,6 +747,7 @@ impl DirectorWorker { }; // Prüfe, ob Transportmittel im aktuellen Branch vorhanden sind + // Ein Transport ist aktiv, wenn er noch in der Tabelle existiert const QUERY_COUNT_VEHICLES_IN_BRANCH_REGION: &str = r#" SELECT COUNT(*) AS count FROM falukant_data.vehicle v @@ -758,7 +759,6 @@ impl DirectorWorker { SELECT DISTINCT t.vehicle_id FROM falukant_data.transport t WHERE t.vehicle_id IS NOT NULL - AND t.arrived_at IS NULL ); "#; @@ -800,6 +800,42 @@ impl DirectorWorker { return Ok(()); } + // Wenn keine Transportmittel im Branch vorhanden sind, aber Items vorhanden sind, + // versuche leere Transporte zu planen, um Fahrzeuge zurückzuholen + if vehicles_in_branch == 0 && !items.is_empty() { + eprintln!( + "[DirectorWorker] Keine Transportmittel im Branch {} vorhanden, aber {} Items vorhanden. Prüfe auf leere Transporte zum Zurückholen", + director.branch_id, items.len() + ); + if let Err(err) = self.plan_empty_transports_for_vehicle_retrieval( + &mut conn, + falukant_user_id, + director.branch_id, + ) { + eprintln!( + "[DirectorWorker] Fehler beim Planen leerer Transporte: {err}" + ); + } + // Nach dem Planen leerer Transporte erneut prüfen, ob jetzt Transportmittel vorhanden sind + let vehicle_count_rows_after = conn.execute( + "count_vehicles_in_branch", + &[&falukant_user_id, &director.branch_id], + )?; + let vehicles_in_branch_after = vehicle_count_rows_after + .into_iter() + .next() + .and_then(|row| row.get("count").and_then(|v| v.parse::().ok())) + .unwrap_or(0); + + if vehicles_in_branch_after == 0 { + eprintln!( + "[DirectorWorker] Nach Planen leerer Transporte immer noch keine Transportmittel im Branch {} vorhanden. Überspringe normale Transportplanung.", + director.branch_id + ); + return Ok(()); + } + } + // Lohnende Transporte planen. Dabei werden: // - ggf. Transport-Einträge erzeugt // - Inventar-Mengen reduziert @@ -841,9 +877,19 @@ impl DirectorWorker { // Nach normalen Transporten: Wenn keine Transportmittel mehr im Branch vorhanden sind, // aber bessere Verkaufspreise in anderen Branches existieren, plane leere Transporte - if vehicles_in_branch == 0 { + let vehicle_count_rows_final = conn.execute( + "count_vehicles_in_branch", + &[&falukant_user_id, &director.branch_id], + )?; + let vehicles_in_branch_final = vehicle_count_rows_final + .into_iter() + .next() + .and_then(|row| row.get("count").and_then(|v| v.parse::().ok())) + .unwrap_or(0); + + if vehicles_in_branch_final == 0 { eprintln!( - "[DirectorWorker] Keine Transportmittel mehr im Branch {} vorhanden, prüfe auf leere Transporte zum Zurückholen", + "[DirectorWorker] Nach Transporten keine Transportmittel mehr im Branch {} vorhanden, prüfe auf leere Transporte zum Zurückholen", director.branch_id ); if let Err(err) = self.plan_empty_transports_for_vehicle_retrieval( @@ -1392,29 +1438,14 @@ impl DirectorWorker { return Ok(()); } - // Für jeden anderen Branch prüfen, ob bessere Verkaufspreise existieren - // und freie Transportmittel verfügbar sind + // Für jeden anderen Branch prüfen, ob freie Transportmittel verfügbar sind + // und ob bessere Verkaufspreise existieren (zur Priorisierung) conn.prepare("get_free_vehicles_in_region", QUERY_GET_FREE_VEHICLES_IN_REGION)?; conn.prepare("get_region_worth_for_product", QUERY_GET_REGION_WORTH_FOR_PRODUCT)?; conn.prepare("insert_empty_transport", QUERY_INSERT_EMPTY_TRANSPORT)?; - // Prüfe für jeden Branch, ob es bessere Verkaufspreise gibt - // Dazu müssen wir alle Produkte durchgehen, die in town_product_worth existieren - const QUERY_GET_ALL_PRODUCTS: &str = r#" - SELECT DISTINCT product_id - FROM falukant_data.town_product_worth - WHERE region_id IN ( - SELECT region_id - FROM falukant_data.branch - WHERE falukant_user_id = $1 - ); - "#; - - conn.prepare("get_all_products", QUERY_GET_ALL_PRODUCTS)?; - let product_rows = conn.execute("get_all_products", &[&falukant_user_id])?; - - let mut best_target_branch: Option<(i32, i32)> = None; // (branch_id, region_id) - let mut best_price_delta: f64 = 0.0; + // Sammle alle Branches mit freien Transportmitteln und berechne Preisvorteil + let mut branches_with_vehicles: Vec<(i32, i32, i32, f64)> = Vec::new(); // (branch_id, region_id, vehicle_count, price_delta) for branch_row in &branch_rows { let target_region_id = branch_row @@ -1430,73 +1461,14 @@ impl DirectorWorker { continue; } - // Prüfe für jedes Produkt, ob der Preis in der Zielregion besser ist - for product_row in &product_rows { - let product_id = product_row - .get("product_id") - .and_then(|v| v.parse::().ok()) - .unwrap_or(-1); - - if product_id < 0 { - continue; - } - - // Worth-Percent für beide Regionen abrufen - let worth_rows = conn.execute( - "get_region_worth_for_product", - &[&falukant_user_id, &product_id], - )?; - - let mut current_worth: Option = None; - let mut target_worth: Option = None; - - for worth_row in worth_rows { - let region_id = worth_row - .get("region_id") - .and_then(|v| v.parse::().ok()) - .unwrap_or(-1); - let worth_percent = worth_row - .get("worth_percent") - .and_then(|v| v.parse::().ok()) - .unwrap_or(100.0); - - if region_id == current_region_id { - current_worth = Some(worth_percent); - } else if region_id == target_region_id { - target_worth = Some(worth_percent); - } - } - - // Wenn der Zielregion einen besseren Preis hat, merke diesen Branch - if let (Some(current), Some(target)) = (current_worth, target_worth) { - let price_delta = target - current; - if price_delta > best_price_delta { - best_price_delta = price_delta; - best_target_branch = Some((target_branch_id, target_region_id)); - } - } - } - } - - // Wenn ein Branch mit besseren Preisen gefunden wurde, prüfe auf freie Transportmittel - if let Some((target_branch_id, target_region_id)) = best_target_branch { - eprintln!( - "[DirectorWorker] Besserer Verkaufspreis in Branch {} (Region {}) gefunden, prüfe auf freie Transportmittel", - target_branch_id, target_region_id - ); - - // Freie Transportmittel in der Zielregion finden + // Prüfe auf freie Transportmittel in dieser Region let vehicle_rows = conn.execute( "get_free_vehicles_in_region", &[&falukant_user_id, &target_region_id], )?; if vehicle_rows.is_empty() { - eprintln!( - "[DirectorWorker] Keine freien Transportmittel in Region {} gefunden", - target_region_id - ); - return Ok(()); + continue; } // Prüfe, ob eine Route zurück zum aktuellen Branch existiert @@ -1508,33 +1480,86 @@ impl DirectorWorker { )?; if vehicles.is_empty() { - eprintln!( - "[DirectorWorker] Keine Route von Region {} zurück zu Region {} gefunden", - target_region_id, current_region_id - ); - return Ok(()); + continue; } - // Leere Transporte für alle verfügbaren Fahrzeuge anlegen - let mut transport_count = 0; - for vehicle in &vehicles { - conn.execute( - "insert_empty_transport", - &[&target_region_id, ¤t_region_id, &vehicle.id], - )?; - transport_count += 1; + // Berechne Preisvorteil (vereinfacht: verwende worth_percent-Differenz) + // Hole worth_percent für beide Regionen (für ein beliebiges Produkt) + let mut price_delta = 0.0; + const QUERY_GET_AVERAGE_WORTH: &str = r#" + SELECT + AVG(CASE WHEN region_id = $1 THEN worth_percent ELSE NULL END) AS current_worth, + AVG(CASE WHEN region_id = $2 THEN worth_percent ELSE NULL END) AS target_worth + FROM falukant_data.town_product_worth + WHERE region_id IN ($1, $2); + "#; + + conn.prepare("get_average_worth", QUERY_GET_AVERAGE_WORTH)?; + let worth_rows = conn.execute( + "get_average_worth", + &[¤t_region_id, &target_region_id], + )?; + + if let Some(worth_row) = worth_rows.into_iter().next() { + let current_worth = worth_row + .get("current_worth") + .and_then(|v| v.parse::().ok()) + .unwrap_or(100.0); + let target_worth = worth_row + .get("target_worth") + .and_then(|v| v.parse::().ok()) + .unwrap_or(100.0); + price_delta = target_worth - current_worth; } - eprintln!( - "[DirectorWorker] {} leere Transporte geplant: Region {} -> Region {}", - transport_count, target_region_id, current_region_id - ); - } else { - eprintln!( - "[DirectorWorker] Kein Branch mit besseren Verkaufspreisen gefunden" - ); + branches_with_vehicles.push(( + target_branch_id, + target_region_id, + vehicles.len() as i32, + price_delta, + )); } + if branches_with_vehicles.is_empty() { + eprintln!( + "[DirectorWorker] Keine Branches mit freien Transportmitteln gefunden" + ); + return Ok(()); + } + + // Wähle den Branch mit dem besten Preisvorteil (oder einfach den ersten, wenn alle gleich sind) + branches_with_vehicles.sort_by(|a, b| b.3.partial_cmp(&a.3).unwrap_or(std::cmp::Ordering::Equal)); + + let (target_branch_id, target_region_id, vehicle_count, price_delta) = branches_with_vehicles[0]; + + eprintln!( + "[DirectorWorker] Bester Branch für Fahrzeug-Rückholung: Branch {} (Region {}), {} Fahrzeuge, Preisvorteil: {:.2}%", + target_branch_id, target_region_id, vehicle_count, price_delta + ); + + // Hole die Fahrzeuge nochmal für diesen Branch + let vehicles = Self::get_transport_vehicles_for_route( + conn, + falukant_user_id, + target_region_id, + current_region_id, + )?; + + // Leere Transporte für alle verfügbaren Fahrzeuge anlegen + let mut transport_count = 0; + for vehicle in &vehicles { + conn.execute( + "insert_empty_transport", + &[&target_region_id, ¤t_region_id, &vehicle.id], + )?; + transport_count += 1; + } + + eprintln!( + "[DirectorWorker] {} leere Transporte geplant: Region {} -> Region {}", + transport_count, target_region_id, current_region_id + ); + Ok(()) }