Add empty transport planning in DirectorWorker: Introduced SQL queries and logic to handle empty transports for vehicle retrieval when no transport vehicles are available in the current branch. Enhanced the planning process to check for better selling prices in other branches and available free vehicles, improving transport management and operational efficiency.

This commit is contained in:
Torsten Schulz (local)
2025-12-04 16:31:39 +01:00
parent 940706bed3
commit 9f2f20cba2

View File

@@ -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::<i32>().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::<i32>().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", &[&current_branch_id])?;
let current_region_id = match branch_rows.into_iter().next() {
Some(row) => row
.get("region_id")
.and_then(|v| v.parse::<i32>().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, &current_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::<i32>().ok())
.unwrap_or(-1);
let target_branch_id = branch_row
.get("branch_id")
.and_then(|v| v.parse::<i32>().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::<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(
"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, &current_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,