Füge neue SQL-Abfragen für die Abfrage von Lagerbeständen nach Typ hinzu: Implementiere QUERY_GET_REGION_STOCKS_BY_TYPE und QUERY_GET_USER_STOCKS_BY_TYPE, um die Abfrage von Lagerbeständen zu optimieren. Aktualisiere die Logik in events.rs, um die neuen Abfragen zu verwenden und erweitere die Struktur StorageDamageInfo, um zusätzliche Informationen zu zerstörten Einheiten und betroffenen Regionen zu speichern. Ergänze die Dokumentation in AGENTS.md mit Projektleitlinien und Entwicklungsbefehlen.
All checks were successful
Deploy yourpart (blue-green) / deploy (push) Successful in 1m48s

This commit is contained in:
Torsten Schulz (local)
2026-05-27 17:51:39 +02:00
parent 86d79c90a5
commit f09033a99d
3 changed files with 214 additions and 28 deletions

29
AGENTS.md Normal file
View File

@@ -0,0 +1,29 @@
# Repository Guidelines
## Project Structure & Module Organization
- **Daemon-Worker Architecture**: A standalone Rust daemon for the Falukant game backend, managing game mechanics through background workers.
- **Workers (`./src/worker/`)**: Modular task handlers (e.g., `PoliticsWorker`, `FalukantFamilyWorker`, `EventsWorker`) that execute periodic game logic ("ticks"). Most workers extend the `BaseWorker` logic.
- **Database Layer**: Uses PostgreSQL for persistence. Core SQL queries are centralized in `./src/worker/sql.rs`, while connection management resides in `./src/db/`.
- **Real-time Communication**: `./src/websocket_server.rs` manages client connections, broadcasting game state updates via `./src/message_broker.rs`.
- **Migrations**: Database schema changes are managed manually via SQL scripts in `./migrations/`.
- **Documentation**: Detailed technical specs and handoff notes are located in `./docs/`.
## Build, Test, and Development Commands
- `cargo build`: Compile the project.
- `cargo clippy`: Run the linter to ensure code quality.
- `cargo run`: Start the daemon.
- `cargo fmt`: Format the codebase according to Rust standards.
- `cargo check`: Rapidly check for compilation errors.
## Coding Style & Naming Conventions
- **Rust Standards**: Follows idiomatic Rust conventions (snake_case for variables/functions, PascalCase for types).
- **Linter**: Enforced via `clippy`. Use `cargo clippy` before committing.
- **Database Logic**: Prefer centralizing complex SQL queries in `./src/worker/sql.rs` rather than inlining them in worker logic.
## Testing Guidelines
- **Manual Verification**: No automated test suite exists (`cargo test` returns no results). Verify changes by running the daemon and inspecting database state or WebSocket output.
- **Smoke Tests**: Follow instructions in `./docs/` for feature-specific manual testing (e.g., `./docs/FALUKANT_DEATH_SUCCESSION_SMOKE_TEST.md`).
## Commit & Pull Request Guidelines
- **Commit Messages**: Primarily written in German, though English is acceptable. Messages typically start with an imperative verb (e.g., "Füge", "Verbessere", "Behebe", "Refactor", "Enhance").
- **Context**: Reference specific features or workers in the commit subject to maintain clarity in the history.

View File

@@ -20,6 +20,8 @@ use crate::worker::sql::{
QUERY_INSERT_MONEY_HISTORY, QUERY_INSERT_MONEY_HISTORY,
QUERY_GET_REGION_STOCKS, QUERY_GET_REGION_STOCKS,
QUERY_GET_USER_STOCKS, QUERY_GET_USER_STOCKS,
QUERY_GET_REGION_STOCKS_BY_TYPE,
QUERY_GET_USER_STOCKS_BY_TYPE,
QUERY_UPDATE_STOCK_CAPACITY, QUERY_UPDATE_STOCK_CAPACITY,
QUERY_UPDATE_STOCK_CAPACITY_REGIONAL, QUERY_UPDATE_STOCK_CAPACITY_REGIONAL,
QUERY_GET_REGION_HOUSES, QUERY_GET_REGION_HOUSES,
@@ -139,6 +141,8 @@ struct StorageDamageInfo {
storage_destruction_percent: f64, storage_destruction_percent: f64,
affected_stocks: i32, affected_stocks: i32,
destroyed_stocks: i32, destroyed_stocks: i32,
destroyed_inventory_units: i64,
affected_region_ids: Vec<i32>,
} }
/// Parameter für regionale Lager-Schäden /// Parameter für regionale Lager-Schäden
@@ -224,18 +228,18 @@ impl EventsWorker {
EventEffect::StorageDamage { EventEffect::StorageDamage {
probability: 1.0, probability: 1.0,
stock_type_label: "field".to_string(), stock_type_label: "field".to_string(),
inventory_damage_min_percent: 5.0, inventory_damage_min_percent: 35.0,
inventory_damage_max_percent: 75.0, inventory_damage_max_percent: 90.0,
storage_destruction_min_percent: 0.0, storage_destruction_min_percent: 0.0,
storage_destruction_max_percent: 50.0, storage_destruction_max_percent: 0.0,
}, },
EventEffect::StorageDamage { EventEffect::StorageDamage {
probability: 1.0, probability: 1.0,
stock_type_label: "wood".to_string(), stock_type_label: "wood".to_string(),
inventory_damage_min_percent: 0.0, inventory_damage_min_percent: 20.0,
inventory_damage_max_percent: 25.0, inventory_damage_max_percent: 55.0,
storage_destruction_min_percent: 0.0, storage_destruction_min_percent: 8.0,
storage_destruction_max_percent: 10.0, storage_destruction_max_percent: 25.0,
}, },
// Verbleibende Lager können durch den Sturm beschädigt werden und Kapazität verlieren // Verbleibende Lager können durch den Sturm beschädigt werden und Kapazität verlieren
EventEffect::StorageCapacityChange { EventEffect::StorageCapacityChange {
@@ -271,23 +275,32 @@ impl EventsWorker {
title: "Lagerbrand".to_string(), title: "Lagerbrand".to_string(),
description: "Ein Feuer hat Teile deines Lagers beschädigt.".to_string(), description: "Ein Feuer hat Teile deines Lagers beschädigt.".to_string(),
effects: vec![ effects: vec![
// Feldlager: Lagerbestand kann zerstört werden, Lager können zerstört werden // Feldlager: haeufig Inhaltsverlust, aber Lager selbst bleibt erhalten
EventEffect::StorageDamage { EventEffect::StorageDamage {
probability: 1.0, probability: 1.0,
stock_type_label: "field".to_string(), stock_type_label: "field".to_string(),
inventory_damage_min_percent: 0.0, inventory_damage_min_percent: 45.0,
inventory_damage_max_percent: 100.0, inventory_damage_max_percent: 95.0,
storage_destruction_min_percent: 0.0, storage_destruction_min_percent: 0.0,
storage_destruction_max_percent: 50.0, storage_destruction_max_percent: 0.0,
}, },
// Holzlager: Lagerbestand kann zerstört werden, Lager können zerstört werden // Holzlager: Brand richtet hohen Schaden an Inhalt und Lager an
EventEffect::StorageDamage { EventEffect::StorageDamage {
probability: 1.0, probability: 1.0,
stock_type_label: "wood".to_string(), stock_type_label: "wood".to_string(),
inventory_damage_min_percent: 0.0, inventory_damage_min_percent: 35.0,
inventory_damage_max_percent: 100.0, inventory_damage_max_percent: 85.0,
storage_destruction_min_percent: 12.0,
storage_destruction_max_percent: 35.0,
},
// Steinlager: nur durch Brand betroffen, selten und nur geringer Inhaltsverlust
EventEffect::StorageDamage {
probability: 0.25,
stock_type_label: "stone".to_string(),
inventory_damage_min_percent: 3.0,
inventory_damage_max_percent: 12.0,
storage_destruction_min_percent: 0.0, storage_destruction_min_percent: 0.0,
storage_destruction_max_percent: 50.0, storage_destruction_max_percent: 0.0,
}, },
// Verbleibende Lager können durch das Feuer beschädigt werden und Kapazität verlieren // Verbleibende Lager können durch das Feuer beschädigt werden und Kapazität verlieren
EventEffect::StorageCapacityChange { EventEffect::StorageCapacityChange {
@@ -297,6 +310,33 @@ impl EventsWorker {
}, },
], ],
}, },
RandomEvent {
id: "warehouse_raid".to_string(),
probability_per_minute: 0.003, // 0.3% pro Minute
event_type: EventType::Personal,
title: "Lagerraub".to_string(),
description: "Raeuber haben Teile deiner Lager gepluendert.".to_string(),
effects: vec![
// Holzlager: bei Raub oft stark betroffen, inkl. Lagerschaden
EventEffect::StorageDamage {
probability: 1.0,
stock_type_label: "wood".to_string(),
inventory_damage_min_percent: 25.0,
inventory_damage_max_percent: 70.0,
storage_destruction_min_percent: 6.0,
storage_destruction_max_percent: 20.0,
},
// Feldlager: haeufig Inhaltsverlust, aber kein Lagerschaden
EventEffect::StorageDamage {
probability: 1.0,
stock_type_label: "field".to_string(),
inventory_damage_min_percent: 30.0,
inventory_damage_max_percent: 80.0,
storage_destruction_min_percent: 0.0,
storage_destruction_max_percent: 0.0,
},
],
},
RandomEvent { RandomEvent {
id: "character_illness".to_string(), id: "character_illness".to_string(),
probability_per_minute: 0.004, // 0.4% pro Minute (reduziert) probability_per_minute: 0.004, // 0.4% pro Minute (reduziert)
@@ -1130,6 +1170,16 @@ impl EventsWorker {
.and_then(|v| v.parse::<i32>().ok()) .and_then(|v| v.parse::<i32>().ok())
.unwrap_or(0); .unwrap_or(0);
let stock_type_label = row
.get("stock_type_label")
.map(|v| v.as_str())
.unwrap_or("");
// Eisenlager sind unzerstoerbar (kein struktureller Event-Schaden)
if stock_type_label == "iron" {
continue;
}
if current_capacity > 0 { if current_capacity > 0 {
conn.execute("update_stock_capacity", &[&percent_change, &stock_id])?; conn.execute("update_stock_capacity", &[&percent_change, &stock_id])?;
affected_stocks += 1; affected_stocks += 1;
@@ -1185,6 +1235,16 @@ impl EventsWorker {
.and_then(|v| v.parse::<i32>().ok()) .and_then(|v| v.parse::<i32>().ok())
.unwrap_or(0); .unwrap_or(0);
let stock_type_label = row
.get("stock_type_label")
.map(|v| v.as_str())
.unwrap_or("");
// Eisenlager sind unzerstoerbar (kein struktureller Event-Schaden)
if stock_type_label == "iron" {
continue;
}
if current_capacity > 0 { if current_capacity > 0 {
conn.execute("update_stock_capacity_regional", &[&percent_change, &stock_id])?; conn.execute("update_stock_capacity_regional", &[&percent_change, &stock_id])?;
affected_stocks += 1; affected_stocks += 1;
@@ -1532,6 +1592,8 @@ impl EventsWorker {
"storage_destruction_percent": damage_info.storage_destruction_percent, "storage_destruction_percent": damage_info.storage_destruction_percent,
"affected_stocks": damage_info.affected_stocks, "affected_stocks": damage_info.affected_stocks,
"destroyed_stocks": damage_info.destroyed_stocks, "destroyed_stocks": damage_info.destroyed_stocks,
"destroyed_inventory_units": damage_info.destroyed_inventory_units,
"affected_region_ids": damage_info.affected_region_ids,
}))); })));
} }
@@ -1571,10 +1633,13 @@ impl EventsWorker {
return Ok(Some(json!({ return Ok(Some(json!({
"type": "storage_damage", "type": "storage_damage",
"stock_type": stock_type_label, "stock_type": stock_type_label,
"region_id": region_id,
"inventory_damage_percent": damage_info.inventory_damage_percent, "inventory_damage_percent": damage_info.inventory_damage_percent,
"storage_destruction_percent": damage_info.storage_destruction_percent, "storage_destruction_percent": damage_info.storage_destruction_percent,
"affected_stocks": damage_info.affected_stocks, "affected_stocks": damage_info.affected_stocks,
"destroyed_stocks": damage_info.destroyed_stocks, "destroyed_stocks": damage_info.destroyed_stocks,
"destroyed_inventory_units": damage_info.destroyed_inventory_units,
"affected_region_ids": damage_info.affected_region_ids,
}))); })));
} }
@@ -2138,8 +2203,8 @@ impl EventsWorker {
}; };
// 2. Hole alle Stocks dieses Typs in der Region mit ihren Branches // 2. Hole alle Stocks dieses Typs in der Region mit ihren Branches
conn.prepare("get_region_stocks", QUERY_GET_REGION_STOCKS)?; conn.prepare("get_region_stocks_by_type", QUERY_GET_REGION_STOCKS_BY_TYPE)?;
let stock_rows = conn.execute("get_region_stocks", &[&params.region_id, &stock_type_id])?; let stock_rows = conn.execute("get_region_stocks_by_type", &[&params.region_id, &stock_type_id])?;
if stock_rows.is_empty() { if stock_rows.is_empty() {
eprintln!( eprintln!(
@@ -2151,6 +2216,8 @@ impl EventsWorker {
storage_destruction_percent: 0.0, storage_destruction_percent: 0.0,
affected_stocks: 0, affected_stocks: 0,
destroyed_stocks: 0, destroyed_stocks: 0,
destroyed_inventory_units: 0,
affected_region_ids: vec![params.region_id],
}); });
} }
@@ -2161,7 +2228,7 @@ impl EventsWorker {
rng.gen_range(params.storage_destruction_min_percent..=params.storage_destruction_max_percent); rng.gen_range(params.storage_destruction_min_percent..=params.storage_destruction_max_percent);
let total_stocks = stock_rows.len(); let total_stocks = stock_rows.len();
let stocks_to_destroy = ((total_stocks as f64 * storage_destruction_percent / 100.0) let stocks_to_destroy_requested = ((total_stocks as f64 * storage_destruction_percent / 100.0)
.round() as usize) .round() as usize)
.min(total_stocks); .min(total_stocks);
@@ -2172,6 +2239,7 @@ impl EventsWorker {
let mut affected_stocks = 0; let mut affected_stocks = 0;
let mut processed_stocks = std::collections::HashSet::new(); let mut processed_stocks = std::collections::HashSet::new();
let mut destroyed_inventory_units: i64 = 0;
for row in &inventory_rows { for row in &inventory_rows {
let inventory_id: Option<i32> = row let inventory_id: Option<i32> = row
@@ -2200,6 +2268,7 @@ impl EventsWorker {
if inventory_quantity > 0 { if inventory_quantity > 0 {
let damage = (inventory_quantity as f64 * inventory_damage_percent / 100.0).round() as i32; let damage = (inventory_quantity as f64 * inventory_damage_percent / 100.0).round() as i32;
let new_quantity = (inventory_quantity - damage).max(0); let new_quantity = (inventory_quantity - damage).max(0);
destroyed_inventory_units += (inventory_quantity - new_quantity).max(0) as i64;
// Reduziere Lagerbestand pro Inventar-Eintrag // Reduziere Lagerbestand pro Inventar-Eintrag
conn.prepare("reduce_inventory", QUERY_REDUCE_INVENTORY)?; conn.prepare("reduce_inventory", QUERY_REDUCE_INVENTORY)?;
@@ -2214,18 +2283,39 @@ impl EventsWorker {
// 5. Zerstöre zufällig ausgewählte Stocks // 5. Zerstöre zufällig ausgewählte Stocks
let mut destroyed_stocks = 0; let mut destroyed_stocks = 0;
if stocks_to_destroy > 0 { // Regelwerk:
// - Nur Holzlager koennen als Lager zerstoert werden.
// - Niemals mehr Lager zerstoeren als Lager mit betroffenem Inhalt.
if stocks_to_destroy_requested > 0 && params.stock_type_label == "wood" {
// Wähle zufällige Stocks zum Zerstören // Wähle zufällige Stocks zum Zerstören
let mut stock_ids_to_destroy: Vec<i32> = stock_rows let mut stock_ids_to_destroy: Vec<i32> = stock_rows
.iter() .iter()
.filter_map(|row| row.get("stock_id").and_then(|v| v.parse::<i32>().ok())) .filter_map(|row| row.get("stock_id").and_then(|v| v.parse::<i32>().ok()))
.collect(); .collect();
stock_ids_to_destroy.retain(|sid| processed_stocks.contains(sid));
let stocks_to_destroy = stocks_to_destroy_requested
.min(stock_ids_to_destroy.len())
.min(affected_stocks as usize);
// Mische die Liste zufällig // Mische die Liste zufällig
stock_ids_to_destroy.shuffle(rng); stock_ids_to_destroy.shuffle(rng);
stock_ids_to_destroy.truncate(stocks_to_destroy); stock_ids_to_destroy.truncate(stocks_to_destroy);
for stock_id in &stock_ids_to_destroy { for stock_id in &stock_ids_to_destroy {
// Restbestand im zu zerstoerenden Lager zaehlen (zusaetzlicher Verlust)
conn.prepare("get_stock_inventory", QUERY_GET_STOCK_INVENTORY)?;
let doomed_items = conn.execute("get_stock_inventory", &[stock_id])?;
for item_row in doomed_items {
let qty = item_row
.get("quantity")
.and_then(|v| v.parse::<i32>().ok())
.unwrap_or(0);
if qty > 0 {
destroyed_inventory_units += qty as i64;
}
}
// Lösche zuerst den Lagerbestand // Lösche zuerst den Lagerbestand
conn.prepare("delete_inventory", QUERY_DELETE_INVENTORY)?; conn.prepare("delete_inventory", QUERY_DELETE_INVENTORY)?;
conn.execute("delete_inventory", &[stock_id])?; conn.execute("delete_inventory", &[stock_id])?;
@@ -2240,7 +2330,7 @@ impl EventsWorker {
// 6. Sicherstelle, dass Lagerbestand <= Lageranzahl für alle verbleibenden Stocks // 6. Sicherstelle, dass Lagerbestand <= Lageranzahl für alle verbleibenden Stocks
// Hole alle verbleibenden Stocks mit ihrem Lagerbestand // Hole alle verbleibenden Stocks mit ihrem Lagerbestand
let remaining_stock_rows = conn.execute("get_region_stocks", &[&params.region_id, &stock_type_id])?; let remaining_stock_rows = conn.execute("get_region_stocks_by_type", &[&params.region_id, &stock_type_id])?;
for row in remaining_stock_rows { for row in remaining_stock_rows {
let stock_id: Option<i32> = row let stock_id: Option<i32> = row
@@ -2299,6 +2389,8 @@ impl EventsWorker {
storage_destruction_percent, storage_destruction_percent,
affected_stocks, affected_stocks,
destroyed_stocks, destroyed_stocks,
destroyed_inventory_units,
affected_region_ids: vec![params.region_id],
}) })
} }
@@ -2331,8 +2423,8 @@ impl EventsWorker {
}; };
// 2. Hole alle Stocks dieses Typs für alle Branches des Spielers // 2. Hole alle Stocks dieses Typs für alle Branches des Spielers
conn.prepare("get_user_stocks", QUERY_GET_USER_STOCKS)?; conn.prepare("get_user_stocks_by_type", QUERY_GET_USER_STOCKS_BY_TYPE)?;
let stock_rows = conn.execute("get_user_stocks", &[&params.user_id, &stock_type_id])?; let stock_rows = conn.execute("get_user_stocks_by_type", &[&params.user_id, &stock_type_id])?;
if stock_rows.is_empty() { if stock_rows.is_empty() {
eprintln!( eprintln!(
@@ -2344,6 +2436,8 @@ impl EventsWorker {
storage_destruction_percent: 0.0, storage_destruction_percent: 0.0,
affected_stocks: 0, affected_stocks: 0,
destroyed_stocks: 0, destroyed_stocks: 0,
destroyed_inventory_units: 0,
affected_region_ids: Vec::new(),
}); });
} }
@@ -2354,7 +2448,7 @@ impl EventsWorker {
rng.gen_range(params.storage_destruction_min_percent..=params.storage_destruction_max_percent); rng.gen_range(params.storage_destruction_min_percent..=params.storage_destruction_max_percent);
let total_stocks = stock_rows.len(); let total_stocks = stock_rows.len();
let stocks_to_destroy = ((total_stocks as f64 * storage_destruction_percent / 100.0) let stocks_to_destroy_requested = ((total_stocks as f64 * storage_destruction_percent / 100.0)
.round() as usize) .round() as usize)
.min(total_stocks); .min(total_stocks);
@@ -2365,6 +2459,7 @@ impl EventsWorker {
let mut affected_stocks = 0; let mut affected_stocks = 0;
let mut processed_stocks = std::collections::HashSet::new(); let mut processed_stocks = std::collections::HashSet::new();
let mut destroyed_inventory_units: i64 = 0;
for row in &inventory_rows { for row in &inventory_rows {
let inventory_id: Option<i32> = row let inventory_id: Option<i32> = row
@@ -2393,6 +2488,7 @@ impl EventsWorker {
if inventory_quantity > 0 { if inventory_quantity > 0 {
let damage = (inventory_quantity as f64 * inventory_damage_percent / 100.0).round() as i32; let damage = (inventory_quantity as f64 * inventory_damage_percent / 100.0).round() as i32;
let new_quantity = (inventory_quantity - damage).max(0); let new_quantity = (inventory_quantity - damage).max(0);
destroyed_inventory_units += (inventory_quantity - new_quantity).max(0) as i64;
// Reduziere Lagerbestand pro Inventar-Eintrag // Reduziere Lagerbestand pro Inventar-Eintrag
conn.prepare("reduce_inventory_personal", QUERY_REDUCE_INVENTORY_PERSONAL)?; conn.prepare("reduce_inventory_personal", QUERY_REDUCE_INVENTORY_PERSONAL)?;
@@ -2405,20 +2501,38 @@ impl EventsWorker {
} }
} }
// 5. Zerstöre zufällig ausgewählte Stocks (nur für field und wood) // 5. Zerstöre zufällig ausgewählte Stocks (nur Holzlager)
let mut destroyed_stocks = 0; let mut destroyed_stocks = 0;
if stocks_to_destroy > 0 && (params.stock_type_label == "field" || params.stock_type_label == "wood") { if stocks_to_destroy_requested > 0 && params.stock_type_label == "wood" {
// Wähle zufällige Stocks zum Zerstören // Wähle zufällige Stocks zum Zerstören
let mut stock_ids_to_destroy: Vec<i32> = stock_rows let mut stock_ids_to_destroy: Vec<i32> = stock_rows
.iter() .iter()
.filter_map(|row| row.get("stock_id").and_then(|v| v.parse::<i32>().ok())) .filter_map(|row| row.get("stock_id").and_then(|v| v.parse::<i32>().ok()))
.collect(); .collect();
stock_ids_to_destroy.retain(|sid| processed_stocks.contains(sid));
let stocks_to_destroy = stocks_to_destroy_requested
.min(stock_ids_to_destroy.len())
.min(affected_stocks as usize);
// Mische die Liste zufällig // Mische die Liste zufällig
stock_ids_to_destroy.shuffle(rng); stock_ids_to_destroy.shuffle(rng);
stock_ids_to_destroy.truncate(stocks_to_destroy); stock_ids_to_destroy.truncate(stocks_to_destroy);
for stock_id in &stock_ids_to_destroy { for stock_id in &stock_ids_to_destroy {
// Restbestand im zu zerstoerenden Lager zaehlen (zusaetzlicher Verlust)
conn.prepare("get_stock_inventory_personal", QUERY_GET_STOCK_INVENTORY_PERSONAL)?;
let doomed_items = conn.execute("get_stock_inventory_personal", &[stock_id])?;
for item_row in doomed_items {
let qty = item_row
.get("quantity")
.and_then(|v| v.parse::<i32>().ok())
.unwrap_or(0);
if qty > 0 {
destroyed_inventory_units += qty as i64;
}
}
// Lösche zuerst den Lagerbestand // Lösche zuerst den Lagerbestand
conn.prepare("delete_inventory_personal", QUERY_DELETE_INVENTORY_PERSONAL)?; conn.prepare("delete_inventory_personal", QUERY_DELETE_INVENTORY_PERSONAL)?;
conn.execute("delete_inventory_personal", &[stock_id])?; conn.execute("delete_inventory_personal", &[stock_id])?;
@@ -2432,7 +2546,7 @@ impl EventsWorker {
} }
// 6. Sicherstelle, dass Lagerbestand <= Lageranzahl für alle verbleibenden Stocks // 6. Sicherstelle, dass Lagerbestand <= Lageranzahl für alle verbleibenden Stocks
let remaining_stock_rows = conn.execute("get_user_stocks", &[&params.user_id, &stock_type_id])?; let remaining_stock_rows = conn.execute("get_user_stocks_by_type", &[&params.user_id, &stock_type_id])?;
for row in remaining_stock_rows { for row in remaining_stock_rows {
let stock_id: Option<i32> = row let stock_id: Option<i32> = row
@@ -2486,11 +2600,23 @@ impl EventsWorker {
} }
} }
let mut affected_region_ids = std::collections::BTreeSet::new();
for row in &stock_rows {
if let Some(rid) = row
.get("region_id")
.and_then(|v| v.parse::<i32>().ok())
{
affected_region_ids.insert(rid);
}
}
Ok(StorageDamageInfo { Ok(StorageDamageInfo {
inventory_damage_percent, inventory_damage_percent,
storage_destruction_percent, storage_destruction_percent,
affected_stocks, affected_stocks,
destroyed_stocks, destroyed_stocks,
destroyed_inventory_units,
affected_region_ids: affected_region_ids.into_iter().collect(),
}) })
} }
} }

View File

@@ -753,9 +753,12 @@ UPDATE falukant_data.inventory SET quantity = $1 WHERE id = $2;
"#; "#;
pub const QUERY_GET_USER_STOCKS: &str = r#" pub const QUERY_GET_USER_STOCKS: &str = r#"
SELECT s.id AS stock_id, s.quantity AS current_capacity SELECT s.id AS stock_id,
s.quantity AS current_capacity,
st.label_tr AS stock_type_label
FROM falukant_data.stock s FROM falukant_data.stock s
JOIN falukant_data.branch b ON s.branch_id = b.id JOIN falukant_data.branch b ON s.branch_id = b.id
LEFT JOIN falukant_type.stock st ON st.id = s.stock_type_id
WHERE b.falukant_user_id = $1; WHERE b.falukant_user_id = $1;
"#; "#;
@@ -766,9 +769,12 @@ UPDATE falukant_data.stock
"#; "#;
pub const QUERY_GET_REGION_STOCKS: &str = r#" pub const QUERY_GET_REGION_STOCKS: &str = r#"
SELECT s.id AS stock_id, s.quantity AS current_capacity SELECT s.id AS stock_id,
s.quantity AS current_capacity,
st.label_tr AS stock_type_label
FROM falukant_data.stock s FROM falukant_data.stock s
JOIN falukant_data.branch b ON s.branch_id = b.id JOIN falukant_data.branch b ON s.branch_id = b.id
LEFT JOIN falukant_type.stock st ON st.id = s.stock_type_id
WHERE b.region_id = $1; WHERE b.region_id = $1;
"#; "#;
@@ -2699,6 +2705,18 @@ pub const QUERY_GET_INVENTORY_ITEMS: &str = r#"
SELECT i.id AS inventory_id, i.quantity AS inventory_quantity, i.stock_id FROM falukant_data.inventory i JOIN falukant_data.stock s ON i.stock_id = s.id JOIN falukant_data.branch b ON s.branch_id = b.id WHERE b.region_id = $1 AND s.stock_type_id = $2; SELECT i.id AS inventory_id, i.quantity AS inventory_quantity, i.stock_id FROM falukant_data.inventory i JOIN falukant_data.stock s ON i.stock_id = s.id JOIN falukant_data.branch b ON s.branch_id = b.id WHERE b.region_id = $1 AND s.stock_type_id = $2;
"#; "#;
pub const QUERY_GET_REGION_STOCKS_BY_TYPE: &str = r#"
SELECT s.id AS stock_id,
s.quantity AS stock_capacity,
COALESCE(SUM(i.quantity), 0)::int AS inventory_quantity
FROM falukant_data.stock s
JOIN falukant_data.branch b ON s.branch_id = b.id
LEFT JOIN falukant_data.inventory i ON i.stock_id = s.id
WHERE b.region_id = $1
AND s.stock_type_id = $2
GROUP BY s.id, s.quantity;
"#;
pub const QUERY_REDUCE_INVENTORY: &str = r#" pub const QUERY_REDUCE_INVENTORY: &str = r#"
UPDATE falukant_data.inventory SET quantity = $1 WHERE id = $2; UPDATE falukant_data.inventory SET quantity = $1 WHERE id = $2;
"#; "#;
@@ -2726,6 +2744,19 @@ JOIN falukant_data.stock s ON i.stock_id = s.id
JOIN falukant_data.branch b ON s.branch_id = b.id JOIN falukant_data.branch b ON s.branch_id = b.id
WHERE b.falukant_user_id = $1 AND s.stock_type_id = $2; WHERE b.falukant_user_id = $1 AND s.stock_type_id = $2;
"#; "#;
pub const QUERY_GET_USER_STOCKS_BY_TYPE: &str = r#"
SELECT s.id AS stock_id,
b.region_id AS region_id,
s.quantity AS stock_capacity,
COALESCE(SUM(i.quantity), 0)::int AS inventory_quantity
FROM falukant_data.stock s
JOIN falukant_data.branch b ON s.branch_id = b.id
LEFT JOIN falukant_data.inventory i ON i.stock_id = s.id
WHERE b.falukant_user_id = $1
AND s.stock_type_id = $2
GROUP BY s.id, b.region_id, s.quantity;
"#;
// Produce worker queries // Produce worker queries
pub const QUERY_GET_FINISHED_PRODUCTIONS: &str = r#" pub const QUERY_GET_FINISHED_PRODUCTIONS: &str = r#"
SELECT DISTINCT ON (p.id) SELECT DISTINCT ON (p.id)