Refactor production cost calculation in DirectorWorker: Updated the cost formula to depend on product category rather than certificate level, introducing a base cost and headroom discount mechanism. Modified SQL queries to retrieve product category and user certificate for accurate cost assessment. Enhanced documentation for clarity on the new cost structure and its implications for production management.
This commit is contained in:
@@ -31,7 +31,7 @@ use crate::worker::sql::{
|
||||
QUERY_GET_BRANCH_REGION,
|
||||
QUERY_GET_AVERAGE_WORTH,
|
||||
QUERY_UPDATE_INVENTORY_QTY,
|
||||
QUERY_GET_PRODUCT_COST,
|
||||
QUERY_GET_PRODUCT_CATEGORY_AND_USER_CERTIFICATE,
|
||||
QUERY_GET_USER_OFFICES,
|
||||
QUERY_CUMULATIVE_TAX_NO_EXEMPT,
|
||||
QUERY_CUMULATIVE_TAX_WITH_EXEMPT,
|
||||
@@ -51,6 +51,7 @@ struct ProductionPlan {
|
||||
falukant_user_id: i32,
|
||||
money: f64,
|
||||
certificate: i32,
|
||||
product_category: i32,
|
||||
branch_id: i32,
|
||||
product_id: i32,
|
||||
region_id: i32,
|
||||
@@ -94,6 +95,14 @@ pub struct DirectorWorker {
|
||||
// Maximale Anzahl paralleler Produktionen pro Branch
|
||||
const MAX_PARALLEL_PRODUCTIONS: i32 = 2;
|
||||
|
||||
// Stückkosten: abhängig von der **Produktklasse**, nicht vom Zertifikat als Linearfaktor.
|
||||
// Höheres Zertifikat erweitert nur die Produktpalette (`ftp.category <= certificate`); optional
|
||||
// Headroom-Rabatt, wenn Zertifikat über der Klasse des gewählten Produkts liegt.
|
||||
const PRODUCTION_COST_BASE: f64 = 6.0;
|
||||
const PRODUCTION_COST_PER_PRODUCT_CATEGORY: f64 = 1.0;
|
||||
const PRODUCTION_HEADROOM_DISCOUNT_PER_STEP: f64 = 0.035;
|
||||
const PRODUCTION_HEADROOM_DISCOUNT_CAP: f64 = 0.14;
|
||||
|
||||
// ...existing code...
|
||||
|
||||
// Verfügbare Transportmittel für eine Route (source_region -> target_region)
|
||||
@@ -284,6 +293,10 @@ impl DirectorWorker {
|
||||
// Pflichtfelder: ohne diese können wir keinen sinnvollen Plan erstellen.
|
||||
let falukant_user_id: i32 = row.get("falukant_user_id")?.parse().ok()?;
|
||||
let certificate: i32 = row.get("certificate")?.parse().ok()?;
|
||||
let product_category: i32 = row
|
||||
.get("product_category")
|
||||
.and_then(|v| v.parse::<i32>().ok())
|
||||
.unwrap_or(1);
|
||||
let branch_id: i32 = row.get("branch_id")?.parse().ok()?;
|
||||
let product_id: i32 = row.get("product_id")?.parse().ok()?;
|
||||
|
||||
@@ -318,6 +331,7 @@ impl DirectorWorker {
|
||||
falukant_user_id,
|
||||
money,
|
||||
certificate,
|
||||
product_category,
|
||||
branch_id,
|
||||
product_id,
|
||||
region_id,
|
||||
@@ -396,8 +410,19 @@ impl DirectorWorker {
|
||||
}
|
||||
|
||||
|
||||
/// Stück-Einstand: Basis + Material je Produktklasse; Zertifikat **erhöht** die Kosten nicht.
|
||||
/// Headroom (`certificate > product_category`) senkt leicht (Erfahrung/Reserve).
|
||||
fn calc_one_piece_cost(plan: &ProductionPlan) -> f64 {
|
||||
(plan.certificate * 6) as f64
|
||||
Self::piece_production_cost(plan.product_category, plan.certificate)
|
||||
}
|
||||
|
||||
fn piece_production_cost(product_category: i32, certificate: i32) -> f64 {
|
||||
let cat = product_category.max(1);
|
||||
let cert = certificate.max(1);
|
||||
let raw = PRODUCTION_COST_BASE + (cat as f64) * PRODUCTION_COST_PER_PRODUCT_CATEGORY;
|
||||
let headroom = (cert - cat).max(0);
|
||||
let discount = ((headroom as f64) * PRODUCTION_HEADROOM_DISCOUNT_PER_STEP).min(PRODUCTION_HEADROOM_DISCOUNT_CAP);
|
||||
raw * (1.0 - discount)
|
||||
}
|
||||
|
||||
fn calc_max_money_production(plan: &ProductionPlan, one_piece_cost: f64) -> i32 {
|
||||
@@ -641,15 +666,25 @@ impl DirectorWorker {
|
||||
min_price + (max_price - min_price) * (knowledge_factor / 100.0)
|
||||
}
|
||||
|
||||
// Helper: get one_piece_cost from DB row fallback logic
|
||||
fn resolve_one_piece_cost(conn: &mut DbConnection, product_id: i32, fallback: f64) -> Result<f64, DbError> {
|
||||
conn.prepare("get_product_cost", QUERY_GET_PRODUCT_COST)?;
|
||||
let rows = conn.execute("get_product_cost", &[&product_id])?;
|
||||
if let Some(row) = rows.first()
|
||||
&& let Some(sc) = row.get("sell_cost")
|
||||
&& let Ok(v) = sc.parse::<f64>()
|
||||
{
|
||||
return Ok(v);
|
||||
/// Stückkosten für Steuer-/Marge beim Verkauf — gleiche Logik wie bei Produktionsstart.
|
||||
fn resolve_production_piece_cost(
|
||||
conn: &mut DbConnection,
|
||||
product_id: i32,
|
||||
falukant_user_id: i32,
|
||||
fallback: f64,
|
||||
) -> Result<f64, DbError> {
|
||||
conn.prepare("get_cat_cert", QUERY_GET_PRODUCT_CATEGORY_AND_USER_CERTIFICATE)?;
|
||||
let rows = conn.execute("get_cat_cert", &[&product_id, &falukant_user_id])?;
|
||||
if let Some(row) = rows.first() {
|
||||
let cat = row
|
||||
.get("category")
|
||||
.and_then(|v| v.parse::<i32>().ok())
|
||||
.unwrap_or(1);
|
||||
let cert = row
|
||||
.get("certificate")
|
||||
.and_then(|v| v.parse::<i32>().ok())
|
||||
.unwrap_or(1);
|
||||
return Ok(Self::piece_production_cost(cat, cert));
|
||||
}
|
||||
Ok(fallback)
|
||||
}
|
||||
@@ -733,7 +768,12 @@ impl DirectorWorker {
|
||||
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 one_piece_cost = Self::resolve_production_piece_cost(
|
||||
conn,
|
||||
item.product_id,
|
||||
item.user_id,
|
||||
item.sell_cost,
|
||||
)?;
|
||||
let cumulative_tax_percent = Self::get_cumulative_tax_percent(conn, item.branch_id, item.user_id)?;
|
||||
|
||||
let revenue_cents = (sell_price * 100.0).round() as i64;
|
||||
|
||||
@@ -48,11 +48,22 @@ INSERT INTO falukant_log.notification (user_id, tr, shown, created_at, updated_a
|
||||
VALUES ($1, $2, FALSE, NOW(), NOW());
|
||||
"#;
|
||||
|
||||
// Product pricing
|
||||
// Product pricing (nur sell_cost; für Produktions-Stückkosten siehe QUERY_GET_PRODUCT_CATEGORY_AND_USER_CERTIFICATE)
|
||||
#[allow(dead_code)]
|
||||
pub const QUERY_GET_PRODUCT_COST: &str = r#"
|
||||
SELECT sell_cost FROM falukant_type.product WHERE id = $1;
|
||||
"#;
|
||||
|
||||
/// Produktklasse + Spieler-Zertifikat für Stückkosten (kein „teurer wegen höherem Zertifikat“).
|
||||
pub const QUERY_GET_PRODUCT_CATEGORY_AND_USER_CERTIFICATE: &str = r#"
|
||||
SELECT COALESCE(p.category, 1)::int AS category,
|
||||
COALESCE(u.certificate, 1)::int AS certificate
|
||||
FROM falukant_type.product p
|
||||
CROSS JOIN falukant_data.falukant_user u
|
||||
WHERE p.id = $1::int
|
||||
AND u.id = $2::int;
|
||||
"#;
|
||||
|
||||
pub const QUERY_GET_DIRECTORS: &str = r#"
|
||||
SELECT d.may_produce, d.may_sell, d.may_start_transport, b.id AS branch_id, fu.id AS falukantUserId, d.id
|
||||
FROM falukant_data.director d
|
||||
@@ -63,7 +74,8 @@ WHERE current_time BETWEEN '08:00:00' AND '17:00:00';
|
||||
"#;
|
||||
|
||||
pub const QUERY_GET_BEST_PRODUCTION: &str = r#"
|
||||
SELECT fdu.id falukant_user_id, CAST(fdu.money AS text) AS money, fdu.certificate, ftp.id product_id, ftp.label_tr, fdb.region_id,
|
||||
SELECT fdu.id falukant_user_id, CAST(fdu.money AS text) AS money, fdu.certificate, ftp.id product_id, ftp.label_tr,
|
||||
COALESCE(ftp.category, 1)::int AS product_category, fdb.region_id,
|
||||
(SELECT SUM(quantity) FROM falukant_data.stock fds WHERE fds.branch_id = fdb.id) AS stock_size,
|
||||
COALESCE((SELECT SUM(COALESCE(fdi.quantity, 0)) FROM falukant_data.stock fds JOIN falukant_data.inventory fdi ON fdi.stock_id = fds.id WHERE fds.branch_id = fdb.id), 0) AS used_in_stock,
|
||||
(ftp.sell_cost * (fdtpw.worth_percent + (fdk_character.knowledge * 2 + fdk_director.knowledge) / 3) / 100 - 6 * ftp.category) / (300.0 * ftp.production_time) AS worth,
|
||||
|
||||
Reference in New Issue
Block a user