diff --git a/src/worker/director.rs b/src/worker/director.rs index f10706a..48550d6 100644 --- a/src/worker/director.rs +++ b/src/worker/director.rs @@ -264,6 +264,41 @@ const QUERY_INSERT_TRANSPORT: &str = r#" VALUES ($1, $2, $3, $4, $5, NOW(), NOW()); "#; +// Leere Transporte (product_id = NULL, size = 0) zum Zurückholen von Fahrzeugen +const QUERY_INSERT_EMPTY_TRANSPORT: &str = r#" + INSERT INTO falukant_data.transport + (source_region_id, target_region_id, product_id, size, vehicle_id, created_at, updated_at) + VALUES ($1, $2, NULL, 0, $3, NOW(), NOW()); +"#; + +// Alle Branches des Users mit ihren Regionen +const QUERY_GET_USER_BRANCHES: &str = r#" + SELECT DISTINCT + b.region_id, + b.id AS branch_id + FROM falukant_data.branch b + WHERE b.falukant_user_id = $1 + AND b.region_id != $2; +"#; + +// Freie Transportmittel in einer Region (nicht in aktiven Transporten) +const QUERY_GET_FREE_VEHICLES_IN_REGION: &str = r#" + SELECT + v.id AS vehicle_id, + vt.capacity AS capacity + FROM falukant_data.vehicle v + JOIN falukant_type.vehicle vt + ON vt.id = v.vehicle_type_id + WHERE v.falukant_user_id = $1 + AND v.region_id = $2 + AND v.id NOT IN ( + SELECT DISTINCT t.vehicle_id + FROM falukant_data.transport t + WHERE t.vehicle_id IS NOT NULL + AND t.arrived_at IS NULL + ); +"#; + const QUERY_GET_SALARY_TO_PAY: &str = r#" SELECT d.id, d.employer_user_id, d.income FROM falukant_data.director d @@ -691,19 +726,80 @@ impl DirectorWorker { director.id, director.branch_id, items.len() ); - // Falls es nichts zu transportieren gibt, können wir sofort zurückkehren. + // Für alle Items dieses Directors sollten die user_id-Felder identisch + // sein (Arbeitgeber des Directors). + let falukant_user_id = if items.is_empty() { + // Wenn keine Items vorhanden sind, müssen wir die user_id anders ermitteln + const QUERY_GET_DIRECTOR_USER: &str = r#" + SELECT employer_user_id + FROM falukant_data.director + WHERE id = $1; + "#; + conn.prepare("get_director_user", QUERY_GET_DIRECTOR_USER)?; + let user_rows = conn.execute("get_director_user", &[&director.id])?; + user_rows + .into_iter() + .next() + .and_then(|row| row.get("employer_user_id").and_then(|v| v.parse::().ok())) + .ok_or_else(|| DbError::new("Konnte employer_user_id nicht ermitteln"))? + } else { + items[0].user_id + }; + + // Prüfe, ob Transportmittel im aktuellen Branch vorhanden sind + const QUERY_COUNT_VEHICLES_IN_BRANCH_REGION: &str = r#" + SELECT COUNT(*) AS count + FROM falukant_data.vehicle v + JOIN falukant_data.branch b + ON b.region_id = v.region_id + WHERE v.falukant_user_id = $1 + AND b.id = $2 + AND v.id NOT IN ( + SELECT DISTINCT t.vehicle_id + FROM falukant_data.transport t + WHERE t.vehicle_id IS NOT NULL + AND t.arrived_at IS NULL + ); + "#; + + conn.prepare("count_vehicles_in_branch", QUERY_COUNT_VEHICLES_IN_BRANCH_REGION)?; + let vehicle_count_rows = conn.execute( + "count_vehicles_in_branch", + &[&falukant_user_id, &director.branch_id], + )?; + + let vehicles_in_branch = vehicle_count_rows + .into_iter() + .next() + .and_then(|row| row.get("count").and_then(|v| v.parse::().ok())) + .unwrap_or(0); + + // Falls es nichts zu transportieren gibt, prüfe auf leere Transporte if items.is_empty() { eprintln!( "[DirectorWorker] Keine Inventar-Items für Transporte gefunden für Director {} (branch_id={})", director.id, director.branch_id ); + + // Wenn keine Transportmittel im Branch vorhanden sind, versuche leere Transporte zu planen + if vehicles_in_branch == 0 { + eprintln!( + "[DirectorWorker] Keine Transportmittel 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( + &mut conn, + falukant_user_id, + director.branch_id, + ) { + eprintln!( + "[DirectorWorker] Fehler beim Planen leerer Transporte: {err}" + ); + } + } return Ok(()); } - // Für alle Items dieses Directors sollten die user_id-Felder identisch - // sein (Arbeitgeber des Directors). - let falukant_user_id = items[0].user_id; - // Lohnende Transporte planen. Dabei werden: // - ggf. Transport-Einträge erzeugt // - Inventar-Mengen reduziert @@ -743,6 +839,24 @@ 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 { + eprintln!( + "[DirectorWorker] 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( + &mut conn, + falukant_user_id, + director.branch_id, + ) { + eprintln!( + "[DirectorWorker] Fehler beim Planen leerer Transporte: {err}" + ); + } + } + Ok(()) } @@ -1235,6 +1349,195 @@ impl DirectorWorker { Ok(result) } + /// Plant leere Transporte, um Fahrzeuge zurückzuholen, wenn: + /// - Keine Transportmittel im aktuellen Branch vorhanden sind + /// - Aber bessere Verkaufspreise in anderen Branches existieren + /// - Freie Transportmittel in anderen Regionen verfügbar sind + fn plan_empty_transports_for_vehicle_retrieval( + &mut self, + conn: &mut DbConnection, + falukant_user_id: i32, + current_branch_id: i32, + ) -> Result<(), DbError> { + // Aktuelle Branch-Region ermitteln + const QUERY_GET_BRANCH_REGION: &str = r#" + SELECT region_id + FROM falukant_data.branch + WHERE id = $1; + "#; + + conn.prepare("get_branch_region", QUERY_GET_BRANCH_REGION)?; + let branch_rows = conn.execute("get_branch_region", &[¤t_branch_id])?; + + let current_region_id = match branch_rows.into_iter().next() { + Some(row) => row + .get("region_id") + .and_then(|v| v.parse::().ok()) + .ok_or_else(|| DbError::new("Konnte region_id nicht ermitteln"))?, + None => return Ok(()), // Branch nicht gefunden, nichts zu tun + }; + + // Alle anderen Branches des Users finden + conn.prepare("get_user_branches", QUERY_GET_USER_BRANCHES)?; + let branch_rows = conn.execute( + "get_user_branches", + &[&falukant_user_id, ¤t_region_id], + )?; + + if branch_rows.is_empty() { + eprintln!( + "[DirectorWorker] Keine anderen Branches für User {} gefunden", + falukant_user_id + ); + return Ok(()); + } + + // Für jeden anderen Branch prüfen, ob bessere Verkaufspreise existieren + // und freie Transportmittel verfügbar sind + 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; + + for branch_row in &branch_rows { + let target_region_id = branch_row + .get("region_id") + .and_then(|v| v.parse::().ok()) + .unwrap_or(-1); + let target_branch_id = branch_row + .get("branch_id") + .and_then(|v| v.parse::().ok()) + .unwrap_or(-1); + + if target_region_id < 0 || target_branch_id < 0 { + 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 + 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(()); + } + + // Prüfe, ob eine Route zurück zum aktuellen Branch existiert + let vehicles = Self::get_transport_vehicles_for_route( + conn, + falukant_user_id, + target_region_id, + current_region_id, + )?; + + if vehicles.is_empty() { + eprintln!( + "[DirectorWorker] Keine Route von Region {} zurück zu Region {} gefunden", + target_region_id, current_region_id + ); + return Ok(()); + } + + // 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 + ); + } else { + eprintln!( + "[DirectorWorker] Kein Branch mit besseren Verkaufspreisen gefunden" + ); + } + + Ok(()) + } + fn update_inventory_quantity( conn: &mut DbConnection, inventory_id: i32,