Refactor transport planning logic in DirectorWorker: Improved handling of empty transports for vehicle retrieval when no vehicles are available in the current branch. Enhanced checks for free vehicles and better selling prices in other branches, along with detailed logging for improved traceability during transport operations.

This commit is contained in:
Torsten Schulz (local)
2025-12-05 12:56:59 +01:00
parent 9f2f20cba2
commit b59837feb3

View File

@@ -282,6 +282,7 @@ const QUERY_GET_USER_BRANCHES: &str = r#"
"#; "#;
// Freie Transportmittel in einer Region (nicht in aktiven Transporten) // 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#" const QUERY_GET_FREE_VEHICLES_IN_REGION: &str = r#"
SELECT SELECT
v.id AS vehicle_id, v.id AS vehicle_id,
@@ -295,7 +296,6 @@ const QUERY_GET_FREE_VEHICLES_IN_REGION: &str = r#"
SELECT DISTINCT t.vehicle_id SELECT DISTINCT t.vehicle_id
FROM falukant_data.transport t FROM falukant_data.transport t
WHERE t.vehicle_id IS NOT NULL 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 // 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#" const QUERY_COUNT_VEHICLES_IN_BRANCH_REGION: &str = r#"
SELECT COUNT(*) AS count SELECT COUNT(*) AS count
FROM falukant_data.vehicle v FROM falukant_data.vehicle v
@@ -758,7 +759,6 @@ impl DirectorWorker {
SELECT DISTINCT t.vehicle_id SELECT DISTINCT t.vehicle_id
FROM falukant_data.transport t FROM falukant_data.transport t
WHERE t.vehicle_id IS NOT NULL WHERE t.vehicle_id IS NOT NULL
AND t.arrived_at IS NULL
); );
"#; "#;
@@ -800,6 +800,42 @@ impl DirectorWorker {
return Ok(()); 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::<i32>().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: // Lohnende Transporte planen. Dabei werden:
// - ggf. Transport-Einträge erzeugt // - ggf. Transport-Einträge erzeugt
// - Inventar-Mengen reduziert // - Inventar-Mengen reduziert
@@ -841,9 +877,19 @@ impl DirectorWorker {
// Nach normalen Transporten: Wenn keine Transportmittel mehr im Branch vorhanden sind, // Nach normalen Transporten: Wenn keine Transportmittel mehr im Branch vorhanden sind,
// aber bessere Verkaufspreise in anderen Branches existieren, plane leere Transporte // 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::<i32>().ok()))
.unwrap_or(0);
if vehicles_in_branch_final == 0 {
eprintln!( 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 director.branch_id
); );
if let Err(err) = self.plan_empty_transports_for_vehicle_retrieval( if let Err(err) = self.plan_empty_transports_for_vehicle_retrieval(
@@ -1392,29 +1438,14 @@ impl DirectorWorker {
return Ok(()); return Ok(());
} }
// Für jeden anderen Branch prüfen, ob bessere Verkaufspreise existieren // Für jeden anderen Branch prüfen, ob freie Transportmittel verfügbar sind
// und 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_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("get_region_worth_for_product", QUERY_GET_REGION_WORTH_FOR_PRODUCT)?;
conn.prepare("insert_empty_transport", QUERY_INSERT_EMPTY_TRANSPORT)?; conn.prepare("insert_empty_transport", QUERY_INSERT_EMPTY_TRANSPORT)?;
// Prüfe für jeden Branch, ob es bessere Verkaufspreise gibt // Sammle alle Branches mit freien Transportmitteln und berechne Preisvorteil
// Dazu müssen wir alle Produkte durchgehen, die in town_product_worth existieren let mut branches_with_vehicles: Vec<(i32, i32, i32, f64)> = Vec::new(); // (branch_id, region_id, vehicle_count, price_delta)
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 { for branch_row in &branch_rows {
let target_region_id = branch_row let target_region_id = branch_row
@@ -1430,73 +1461,14 @@ impl DirectorWorker {
continue; continue;
} }
// Prüfe für jedes Produkt, ob der Preis in der Zielregion besser ist // Prüfe auf freie Transportmittel in dieser Region
for product_row in &product_rows {
let product_id = product_row
.get("product_id")
.and_then(|v| v.parse::<i32>().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<f64> = None;
let mut target_worth: Option<f64> = None;
for worth_row in worth_rows {
let region_id = worth_row
.get("region_id")
.and_then(|v| v.parse::<i32>().ok())
.unwrap_or(-1);
let worth_percent = worth_row
.get("worth_percent")
.and_then(|v| v.parse::<f64>().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( let vehicle_rows = conn.execute(
"get_free_vehicles_in_region", "get_free_vehicles_in_region",
&[&falukant_user_id, &target_region_id], &[&falukant_user_id, &target_region_id],
)?; )?;
if vehicle_rows.is_empty() { if vehicle_rows.is_empty() {
eprintln!( continue;
"[DirectorWorker] Keine freien Transportmittel in Region {} gefunden",
target_region_id
);
return Ok(());
} }
// Prüfe, ob eine Route zurück zum aktuellen Branch existiert // Prüfe, ob eine Route zurück zum aktuellen Branch existiert
@@ -1508,33 +1480,86 @@ impl DirectorWorker {
)?; )?;
if vehicles.is_empty() { if vehicles.is_empty() {
eprintln!( continue;
"[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 // Berechne Preisvorteil (vereinfacht: verwende worth_percent-Differenz)
let mut transport_count = 0; // Hole worth_percent für beide Regionen (für ein beliebiges Produkt)
for vehicle in &vehicles { let mut price_delta = 0.0;
conn.execute( const QUERY_GET_AVERAGE_WORTH: &str = r#"
"insert_empty_transport", SELECT
&[&target_region_id, &current_region_id, &vehicle.id], 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
transport_count += 1; 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",
&[&current_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::<f64>().ok())
.unwrap_or(100.0);
let target_worth = worth_row
.get("target_worth")
.and_then(|v| v.parse::<f64>().ok())
.unwrap_or(100.0);
price_delta = target_worth - current_worth;
} }
eprintln!( branches_with_vehicles.push((
"[DirectorWorker] {} leere Transporte geplant: Region {} -> Region {}", target_branch_id,
transport_count, target_region_id, current_region_id target_region_id,
); vehicles.len() as i32,
} else { price_delta,
eprintln!( ));
"[DirectorWorker] Kein Branch mit besseren Verkaufspreisen gefunden"
);
} }
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, &current_region_id, &vehicle.id],
)?;
transport_count += 1;
}
eprintln!(
"[DirectorWorker] {} leere Transporte geplant: Region {} -> Region {}",
transport_count, target_region_id, current_region_id
);
Ok(()) Ok(())
} }