Update dependencies and enhance WebSocket server logging: Add 'chrono' and 'android_system_properties' to Cargo.lock, improve error handling and logging in websocket_server.rs, and streamline character creation notifications in worker modules for better clarity and maintainability.
This commit is contained in:
@@ -3,7 +3,6 @@ use std::collections::HashMap;
|
||||
use crate::message_broker::MessageBroker;
|
||||
use std::sync::atomic::Ordering;
|
||||
use std::sync::Arc;
|
||||
use std::sync::Mutex;
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
use crate::db::ConnectionPool;
|
||||
@@ -25,6 +24,7 @@ use crate::worker::sql::{
|
||||
QUERY_GET_SALARY_TO_PAY,
|
||||
QUERY_SET_SALARY_PAYED,
|
||||
QUERY_UPDATE_SATISFACTION,
|
||||
QUERY_GET_DIRECTOR_USER,
|
||||
QUERY_COUNT_VEHICLES_IN_BRANCH_REGION,
|
||||
QUERY_COUNT_VEHICLES_IN_REGION,
|
||||
QUERY_CHECK_ROUTE,
|
||||
@@ -35,20 +35,15 @@ use crate::worker::sql::{
|
||||
QUERY_GET_USER_OFFICES,
|
||||
QUERY_CUMULATIVE_TAX_NO_EXEMPT,
|
||||
QUERY_CUMULATIVE_TAX_WITH_EXEMPT,
|
||||
QUERY_GET_VEHICLES_TO_REPAIR_IN_REGION,
|
||||
QUERY_REPAIR_VEHICLE,
|
||||
};
|
||||
use crate::worker::publish_update_status;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
struct Director {
|
||||
id: i32,
|
||||
branch_id: i32,
|
||||
falukant_user_id: i32,
|
||||
may_produce: bool,
|
||||
may_sell: bool,
|
||||
may_start_transport: bool,
|
||||
may_repair_vehicles: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
@@ -162,14 +157,6 @@ impl DirectorWorker {
|
||||
}
|
||||
|
||||
for director in directors {
|
||||
if director.may_repair_vehicles {
|
||||
if let Err(err) = self.repair_vehicles(&director) {
|
||||
eprintln!(
|
||||
"[DirectorWorker] Fehler bei repair_vehicles für Director {}: {err}",
|
||||
director.id
|
||||
);
|
||||
}
|
||||
}
|
||||
if director.may_produce {
|
||||
eprintln!(
|
||||
"[DirectorWorker] Starte Produktionsprüfung für Director {} (branch_id={})",
|
||||
@@ -205,106 +192,15 @@ impl DirectorWorker {
|
||||
Some(Director {
|
||||
id: row.get("id")?.parse().ok()?,
|
||||
branch_id: row.get("branch_id")?.parse().ok()?,
|
||||
falukant_user_id: row.get("falukant_user_id")?.parse().ok()?,
|
||||
may_produce: row.get("may_produce").map(|v| v == "t" || v == "true").unwrap_or(false),
|
||||
may_sell: row.get("may_sell").map(|v| v == "t" || v == "true").unwrap_or(false),
|
||||
may_start_transport: row
|
||||
.get("may_start_transport")
|
||||
.map(|v| v == "t" || v == "true")
|
||||
.unwrap_or(false),
|
||||
may_repair_vehicles: row
|
||||
.get("may_repair_vehicles")
|
||||
.map(|v| v == "t" || v == "true")
|
||||
.unwrap_or(false),
|
||||
})
|
||||
}
|
||||
|
||||
fn repair_vehicles(&mut self, director: &Director) -> Result<(), DbError> {
|
||||
self.base
|
||||
.set_current_step("DirectorWorker: repair_vehicles");
|
||||
|
||||
let mut conn = self
|
||||
.base
|
||||
.pool
|
||||
.get()
|
||||
.map_err(|e| DbError::new(format!("DB-Verbindung fehlgeschlagen: {e}")))?;
|
||||
|
||||
// Region des Directors/Branches bestimmen
|
||||
conn.prepare("get_branch_region_for_repair", QUERY_GET_BRANCH_REGION)?;
|
||||
let rows = conn.execute("get_branch_region_for_repair", &[&director.branch_id])?;
|
||||
let region_id: i32 = rows
|
||||
.first()
|
||||
.and_then(|r| r.get("region_id"))
|
||||
.and_then(|v| v.parse::<i32>().ok())
|
||||
.unwrap_or(-1);
|
||||
if region_id < 0 {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
conn.prepare(
|
||||
"get_vehicles_to_repair",
|
||||
QUERY_GET_VEHICLES_TO_REPAIR_IN_REGION,
|
||||
)?;
|
||||
conn.prepare("repair_vehicle", QUERY_REPAIR_VEHICLE)?;
|
||||
|
||||
let candidates =
|
||||
conn.execute("get_vehicles_to_repair", &[&director.falukant_user_id, ®ion_id])?;
|
||||
if candidates.is_empty() {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let mut repaired = 0usize;
|
||||
for row in candidates {
|
||||
let vehicle_id = row
|
||||
.get("vehicle_id")
|
||||
.and_then(|v| v.parse::<i32>().ok())
|
||||
.unwrap_or(-1);
|
||||
let condition = row
|
||||
.get("condition")
|
||||
.and_then(|v| v.parse::<f64>().ok())
|
||||
.unwrap_or(100.0);
|
||||
let type_cost = row
|
||||
.get("cost")
|
||||
.and_then(|v| v.parse::<f64>().ok())
|
||||
.unwrap_or(0.0);
|
||||
|
||||
if vehicle_id < 0 || type_cost <= 0.0 {
|
||||
continue;
|
||||
}
|
||||
|
||||
// repairCost = round(type.cost * 0.8 * (100 - condition) / 100)
|
||||
let repair_cost = (type_cost * 0.8 * (100.0 - condition) / 100.0).round();
|
||||
if repair_cost <= 0.0 {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Preconditions (wie Backend):
|
||||
// - sufficientFunds: wir versuchen abzubuchen und überspringen bei Fehler
|
||||
if let Err(_err) = self.base.change_falukant_user_money(
|
||||
director.falukant_user_id,
|
||||
-repair_cost,
|
||||
&format!("repair vehicle {}", vehicle_id),
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Fahrzeug auf 100 setzen + available_from in Zukunft (build_time_minutes)
|
||||
let _ = conn.execute("repair_vehicle", &[&vehicle_id])?;
|
||||
repaired += 1;
|
||||
}
|
||||
|
||||
if repaired > 0 {
|
||||
eprintln!(
|
||||
"[DirectorWorker] {} Fahrzeug(e) automatisch zur Reparatur gestartet (director_id={}, region_id={})",
|
||||
repaired, director.id, region_id
|
||||
);
|
||||
// Frontend: Branches/Status neu laden
|
||||
publish_update_status(&self.base.broker, director.falukant_user_id);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn start_productions(&mut self, director: &Director) -> Result<(), DbError> {
|
||||
self.base
|
||||
.set_current_step("DirectorWorker: start_productions");
|
||||
@@ -319,36 +215,10 @@ impl DirectorWorker {
|
||||
conn.prepare("get_to_produce", QUERY_GET_BEST_PRODUCTION)?;
|
||||
let rows = conn.execute("get_to_produce", &[&director.id, &director.branch_id])?;
|
||||
if rows.is_empty() {
|
||||
// Debug: SQL-Vorschau nur gedrosselt loggen, damit wir die Query testen können
|
||||
// ohne Log-Flut.
|
||||
static LAST_EMPTY_PROD_LOG: Mutex<Option<Instant>> = Mutex::new(None);
|
||||
let mut last = LAST_EMPTY_PROD_LOG.lock().unwrap();
|
||||
let should_log = last
|
||||
.map(|t| t.elapsed().as_secs() >= 60)
|
||||
.unwrap_or(true);
|
||||
if should_log {
|
||||
// SQL ggf. kürzen, um Log-Flut zu vermeiden
|
||||
let mut sql_preview = QUERY_GET_BEST_PRODUCTION.to_string();
|
||||
const MAX_SQL_PREVIEW: usize = 1200;
|
||||
if sql_preview.len() > MAX_SQL_PREVIEW {
|
||||
sql_preview.truncate(MAX_SQL_PREVIEW);
|
||||
sql_preview.push_str(" …");
|
||||
}
|
||||
eprintln!(
|
||||
"[DirectorWorker] Keine Produktionskandidaten für Director {} (branch_id={}). Query (get_to_produce): {} | params: director_id={}, branch_id={}",
|
||||
director.id,
|
||||
director.branch_id,
|
||||
sql_preview,
|
||||
director.id,
|
||||
director.branch_id
|
||||
);
|
||||
*last = Some(Instant::now());
|
||||
} else {
|
||||
eprintln!(
|
||||
"[DirectorWorker] Keine Produktionskandidaten für Director {} gefunden.",
|
||||
director.id
|
||||
);
|
||||
}
|
||||
eprintln!(
|
||||
"[DirectorWorker] Keine Produktionskandidaten für Director {} gefunden.",
|
||||
director.id
|
||||
);
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
@@ -432,32 +302,12 @@ impl DirectorWorker {
|
||||
base_plan.running_productions_quantity = running_productions_quantity;
|
||||
|
||||
// Eine neue Produktion starten (max. 100 Stück)
|
||||
let produced_quantity = match self.create_single_production(&mut conn, &base_plan) {
|
||||
Ok(qty) => qty,
|
||||
Err(err) => {
|
||||
eprintln!(
|
||||
"[DirectorWorker] Fehler beim Starten einer Produktion: {err}"
|
||||
);
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
// WICHTIG: Wenn wir die gesamte verfügbare Kapazität verwendet haben, breche ab.
|
||||
// Sonst könnte die nächste Iteration fälschlicherweise noch Platz sehen, wenn
|
||||
// die gerade gestartete Produktion noch nicht in running_productions_quantity enthalten ist.
|
||||
if produced_quantity >= free_capacity {
|
||||
if let Err(err) = self.create_single_production(&mut conn, &base_plan) {
|
||||
eprintln!(
|
||||
"[DirectorWorker] Produktion mit {} Stück gestartet, was die gesamte freie Kapazität ({}) ausnutzt. Breche ab.",
|
||||
produced_quantity, free_capacity
|
||||
"[DirectorWorker] Fehler beim Starten einer Produktion: {err}"
|
||||
);
|
||||
break;
|
||||
}
|
||||
|
||||
// Wenn wir weniger als 10% der freien Kapazität produziert haben, könnte es sein,
|
||||
// dass wir noch mehr Platz haben. Aber sicherheitshalber brechen wir nach einer
|
||||
// Produktion ab, um Race Conditions zu vermeiden.
|
||||
// Die nächste Iteration (beim nächsten Director-Check) wird dann wieder prüfen.
|
||||
break;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
@@ -519,15 +369,12 @@ impl DirectorWorker {
|
||||
&mut self,
|
||||
conn: &mut DbConnection,
|
||||
plan: &ProductionPlan,
|
||||
) -> Result<i32, DbError> {
|
||||
// WICHTIG: Kapazität direkt aus dem Plan berechnen (wurde gerade in der Schleife aktualisiert)
|
||||
) -> Result<(), DbError> {
|
||||
let free_capacity = Self::calc_free_capacity(plan);
|
||||
let one_piece_cost = Self::calc_one_piece_cost(plan);
|
||||
let max_money_production = Self::calc_max_money_production(plan, one_piece_cost);
|
||||
|
||||
// to_produce darf NIE größer sein als free_capacity, sonst passt es nicht ins Lager
|
||||
// Zusätzlich: max. 100 Stück pro Produktion, und max. was das Geld erlaubt
|
||||
let to_produce = free_capacity.min(max_money_production).min(100).max(0);
|
||||
let to_produce = (free_capacity.min(max_money_production)).clamp(0, 100);
|
||||
|
||||
eprintln!(
|
||||
"[DirectorWorker] Produktionsberechnung: free_capacity={}, one_piece_cost={}, max_money_production={}, to_produce={}, running_productions={}",
|
||||
@@ -546,16 +393,7 @@ impl DirectorWorker {
|
||||
plan.running_productions,
|
||||
plan.running_productions_quantity
|
||||
);
|
||||
return Ok(0);
|
||||
}
|
||||
|
||||
// Sicherheitsprüfung: to_produce darf niemals größer sein als free_capacity
|
||||
if to_produce > free_capacity {
|
||||
eprintln!(
|
||||
"[DirectorWorker] FEHLER: to_produce ({}) > free_capacity ({})! Das sollte nicht passieren. Breche Produktion ab.",
|
||||
to_produce, free_capacity
|
||||
);
|
||||
return Ok(0);
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let production_cost = to_produce as f64 * one_piece_cost;
|
||||
@@ -596,8 +434,7 @@ impl DirectorWorker {
|
||||
);
|
||||
self.base.broker.publish(message);
|
||||
|
||||
// Rückgabe der produzierten Menge, damit die Schleife entscheiden kann, ob sie weiterläuft
|
||||
Ok(to_produce)
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn calc_free_capacity(plan: &ProductionPlan) -> i32 {
|
||||
@@ -648,8 +485,14 @@ impl DirectorWorker {
|
||||
// Für alle Items dieses Directors sollten die user_id-Felder identisch
|
||||
// sein (Arbeitgeber des Directors).
|
||||
let falukant_user_id = if items.is_empty() {
|
||||
// User-ID ist bereits über QUERY_GET_DIRECTORS geladen.
|
||||
director.falukant_user_id
|
||||
// Wenn keine Items vorhanden sind, müssen wir die user_id anders ermitteln
|
||||
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
|
||||
};
|
||||
@@ -665,7 +508,7 @@ impl DirectorWorker {
|
||||
let vehicles_in_branch = vehicle_count_rows
|
||||
.into_iter()
|
||||
.next()
|
||||
.and_then(|row| row.get("cnt").and_then(|v| v.parse::<i32>().ok()))
|
||||
.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
|
||||
@@ -718,7 +561,7 @@ impl DirectorWorker {
|
||||
let vehicles_in_branch_after = vehicle_count_rows_after
|
||||
.into_iter()
|
||||
.next()
|
||||
.and_then(|row| row.get("cnt").and_then(|v| v.parse::<i32>().ok()))
|
||||
.and_then(|row| row.get("count").and_then(|v| v.parse::<i32>().ok()))
|
||||
.unwrap_or(0);
|
||||
|
||||
if vehicles_in_branch_after == 0 {
|
||||
@@ -768,7 +611,7 @@ impl DirectorWorker {
|
||||
let vehicles_in_branch_final = vehicle_count_rows_final
|
||||
.into_iter()
|
||||
.next()
|
||||
.and_then(|row| row.get("cnt").and_then(|v| v.parse::<i32>().ok()))
|
||||
.and_then(|row| row.get("count").and_then(|v| v.parse::<i32>().ok()))
|
||||
.unwrap_or(0);
|
||||
|
||||
if vehicles_in_branch_final == 0 {
|
||||
@@ -963,38 +806,24 @@ impl DirectorWorker {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
// compute piece price and full sell price
|
||||
let piece_price = Self::compute_piece_sell_price(item);
|
||||
let revenue = piece_price * item.quantity as f64;
|
||||
// compute piece price and full sell price
|
||||
let piece_price = Self::compute_piece_sell_price(item);
|
||||
let sell_price = piece_price * item.quantity as f64;
|
||||
|
||||
let one_piece_cost = Self::resolve_one_piece_cost(conn, item.product_id, item.sell_cost)?;
|
||||
let cumulative_tax_percent =
|
||||
Self::get_cumulative_tax_percent(conn, item.branch_id, item.user_id)?;
|
||||
let cumulative_tax_percent = Self::get_cumulative_tax_percent(conn, item.branch_id, item.user_id)?;
|
||||
|
||||
let revenue_cents = (revenue * 100.0).round() as i64;
|
||||
let cost = one_piece_cost * item.quantity as f64;
|
||||
let cost_cents = (cost * 100.0).round() as i64;
|
||||
let revenue_cents = (sell_price * 100.0).round() as i64;
|
||||
let cost_cents = (one_piece_cost * item.quantity as f64 * 100.0).round() as i64;
|
||||
let profit_cents = (revenue_cents - cost_cents).max(0);
|
||||
|
||||
// Steuer wird vom Gewinn abgezogen (nicht „zugerechnet“)
|
||||
let tax_cents = ((profit_cents as f64) * cumulative_tax_percent / 100.0).round() as i64;
|
||||
let payout_cents = revenue_cents - tax_cents;
|
||||
|
||||
eprintln!("[DirectorWorker] sell: revenue={:.2}, cost={:.2}, profit_cents={}, tax%={:.2}, tax_cents={}, payout_cents={}", revenue, cost, profit_cents, cumulative_tax_percent, tax_cents, payout_cents);
|
||||
|
||||
// Treasury-User-ID optional per ENV, fallback auf DEFAULT_TREASURY_USER_ID
|
||||
let treasury_user_id: i32 = std::env::var("TREASURY_FALUKANT_USER_ID")
|
||||
.ok()
|
||||
.and_then(|v| v.parse::<i32>().ok())
|
||||
.unwrap_or(DEFAULT_TREASURY_USER_ID);
|
||||
eprintln!("[DirectorWorker] sell: revenue={:.2}, cost={:.2}, profit_cents={}, tax%={:.2}, tax_cents={}, payout_cents={}", sell_price, one_piece_cost * item.quantity as f64, profit_cents, cumulative_tax_percent, tax_cents, payout_cents);
|
||||
|
||||
if tax_cents > 0 {
|
||||
let tax_amount = (tax_cents as f64) / 100.0;
|
||||
if let Err(err) = self.base.change_falukant_user_money(
|
||||
treasury_user_id,
|
||||
tax_amount,
|
||||
&format!("tax from sale product {}", item.product_id),
|
||||
) {
|
||||
if let Err(err) = self.base.change_falukant_user_money(DEFAULT_TREASURY_USER_ID, tax_amount, &format!("tax from sale product {}", item.product_id)) {
|
||||
eprintln!("[DirectorWorker] Fehler bei change_falukant_user_money (tax): {err}");
|
||||
}
|
||||
}
|
||||
@@ -1008,7 +837,7 @@ impl DirectorWorker {
|
||||
eprintln!(
|
||||
"[DirectorWorker] sell: user_id={}, revenue={:.2}, tax={:.2}, payout={:.2}, product_id={}",
|
||||
item.user_id,
|
||||
revenue,
|
||||
sell_price,
|
||||
(tax_cents as f64) / 100.0,
|
||||
payout_amount,
|
||||
item.product_id
|
||||
@@ -1221,7 +1050,7 @@ impl DirectorWorker {
|
||||
let vehicle_count = vehicle_count_rows
|
||||
.into_iter()
|
||||
.next()
|
||||
.and_then(|row| row.get("cnt").and_then(|v| v.parse::<i32>().ok()))
|
||||
.and_then(|row| row.get("count").and_then(|v| v.parse::<i32>().ok()))
|
||||
.unwrap_or(0);
|
||||
|
||||
eprintln!(
|
||||
@@ -1239,7 +1068,7 @@ impl DirectorWorker {
|
||||
let route_exists = route_rows
|
||||
.into_iter()
|
||||
.next()
|
||||
.and_then(|row| row.get("1").and_then(|v| v.parse::<i32>().ok()))
|
||||
.and_then(|row| row.get("count").and_then(|v| v.parse::<i32>().ok()))
|
||||
.unwrap_or(0) > 0;
|
||||
|
||||
eprintln!(
|
||||
|
||||
Reference in New Issue
Block a user