97 lines
3.8 KiB
JavaScript
97 lines
3.8 KiB
JavaScript
/**
|
||
* Zentrale Produktions- und Preisformeln (muss mit der Spielwirtschaft konsistent bleiben).
|
||
* Wird von falukantService und der Produkt-Initialisierung genutzt.
|
||
*
|
||
* Mindest-Erlös (Ertrags-Tabelle, Branch): bei 100 % Produktwissen ist der Verkaufspreis
|
||
* das obere Ende der Spanne = basePrice = sellCost * (effectiveWorth/100), mit
|
||
* effectiveWorth >= 75 (siehe effectiveWorthPercent in falukantService).
|
||
* Engster Fall für Gewinn/Stück: Zertifikat = Produktkategorie (kein Headroom-Rabatt auf
|
||
* Stückkosten) und regionale Nachfrage am Boden (75 %).
|
||
*/
|
||
|
||
export const PRODUCTION_COST_BASE = 6.0;
|
||
export const PRODUCTION_COST_PER_PRODUCT_CATEGORY = 1.0;
|
||
export const PRODUCTION_HEADROOM_DISCOUNT_PER_STEP = 0.035;
|
||
export const PRODUCTION_HEADROOM_DISCOUNT_CAP = 0.14;
|
||
|
||
export function productionPieceCost(certificate, category) {
|
||
const c = Math.max(1, Number(category) || 1);
|
||
const cert = Math.max(1, Number(certificate) || 1);
|
||
const raw = PRODUCTION_COST_BASE + (c * PRODUCTION_COST_PER_PRODUCT_CATEGORY);
|
||
const headroom = Math.max(0, cert - c);
|
||
const discount = Math.min(
|
||
headroom * PRODUCTION_HEADROOM_DISCOUNT_PER_STEP,
|
||
PRODUCTION_HEADROOM_DISCOUNT_CAP
|
||
);
|
||
return raw * (1 - discount);
|
||
}
|
||
|
||
export function productionCostTotal(quantity, category, certificate) {
|
||
const q = Math.min(100, Math.max(1, Number(quantity) || 1));
|
||
return q * productionPieceCost(certificate, category);
|
||
}
|
||
|
||
export function effectiveWorthPercent(worthPercent) {
|
||
const w = Number(worthPercent);
|
||
if (Number.isNaN(w)) return 75;
|
||
return Math.min(100, Math.max(75, w));
|
||
}
|
||
|
||
/** Untergrenze für den Wissens-Multiplikator auf den regionalen Basispreis. */
|
||
export const KNOWLEDGE_PRICE_FLOOR = 0.7;
|
||
|
||
export function calcSellPrice(product, knowledgeFactor = 0) {
|
||
const max = product.sellCost;
|
||
const min = max * KNOWLEDGE_PRICE_FLOOR;
|
||
return min + (max - min) * (knowledgeFactor / 100);
|
||
}
|
||
|
||
export function calcRegionalSellPriceSync(product, knowledgeFactor, worthPercent) {
|
||
if (product.sellCost === null || product.sellCost === undefined) return null;
|
||
const w = effectiveWorthPercent(worthPercent);
|
||
const basePrice = product.sellCost * (w / 100);
|
||
const min = basePrice * KNOWLEDGE_PRICE_FLOOR;
|
||
const max = basePrice;
|
||
return min + (max - min) * (knowledgeFactor / 100);
|
||
}
|
||
|
||
/** Untergrenze für worthPercent nach effectiveWorthPercent (75–100). */
|
||
export const EFFECTIVE_WORTH_PERCENT_MIN = 75;
|
||
|
||
/**
|
||
* Minimaler ganzzahliger Basis-sell_cost (vor Steuer-/Regions-Faktoren in der DB),
|
||
* sodass bei Zertifikat = Produktkategorie, 100 % Wissen und 75 % Nachfrage
|
||
* der Erlös pro Stück >= Stückkosten (kein struktureller Verlust in der Ertrags-Tabelle).
|
||
*/
|
||
export function minBaseSellCostForTightProduction(category) {
|
||
const c = Math.max(1, Number(category) || 1);
|
||
const cost = productionPieceCost(c, c);
|
||
return Math.ceil((cost * 100) / EFFECTIVE_WORTH_PERCENT_MIN);
|
||
}
|
||
|
||
/**
|
||
* Prüft Vordefinierungen; meldet Abweichungen nur per warn (kein Throw), damit Deployments
|
||
* mit alter DB nicht brechen — Balance-Anpassung erfolgt bewusst im Code/SQL.
|
||
*/
|
||
export function validateProductBaseSellCosts(products, { warn = console.warn } = {}) {
|
||
const issues = [];
|
||
for (const p of products) {
|
||
const min = minBaseSellCostForTightProduction(p.category);
|
||
if (Number(p.sellCost) < min) {
|
||
issues.push({
|
||
labelTr: p.labelTr,
|
||
category: p.category,
|
||
sellCost: p.sellCost,
|
||
minRequired: min,
|
||
});
|
||
}
|
||
}
|
||
if (issues.length && typeof warn === 'function') {
|
||
warn(
|
||
'[falukantProductEconomy] sell_cost unter Mindestbedarf (Zertifikat=Kategorie, 100% Wissen, 75% Nachfrage):',
|
||
issues
|
||
);
|
||
}
|
||
return issues;
|
||
}
|