feat(falukant): add scandalExtraDailyPct field and update related components
All checks were successful
Deploy to production / deploy (push) Successful in 2m59s

- Introduced a new field `scandalExtraDailyPct` in the relationship state model to track additional scandal risk per day, with validation constraints.
- Updated the FalukantService to include the new field in relevant calculations and data handling.
- Enhanced the frontend components, including RevenueSection and FamilyView, to display the scandal risk information and updated price calculations.
- Added localization entries for the new field in multiple languages to ensure clarity for users.
This commit is contained in:
Torsten Schulz (local)
2026-04-13 15:31:47 +02:00
parent b0624422b8
commit 86e14a875d
12 changed files with 176 additions and 26 deletions

View File

@@ -41,10 +41,10 @@
>, </span>
<span
:class="['city-price', getCityPriceClass(city.branchType)]"
:title="`${city.regionName}: ${formatPrice(city.price)}`"
:title="`${city.regionName}: ${formatCityPrice(city, product)}`"
>
<span class="city-name">{{ city.regionName }}</span>
<span class="city-price-value">({{ formatPrice(city.price) }})</span>
<span class="city-price-value">({{ formatCityPrice(city, product) }})</span>
</span>
</template>
</div>
@@ -169,6 +169,14 @@
maximumFractionDigits: 2,
}).format(price);
},
formatCityPrice(city, product) {
const absolute = Number(city?.price || 0);
const productionTime = Number(product?.productionTime || 0);
const perMinute = Number.isFinite(Number(city?.grossPerMinute))
? Number(city.grossPerMinute)
: (productionTime > 0 ? (absolute / productionTime) : 0);
return `${this.formatPrice(absolute)} / ${this.formatPrice(perMinute)}`;
},
},
};
</script>

View File

@@ -796,6 +796,7 @@
"visibility": "Klaro ba",
"discretion": "Diskreto",
"maintenance": "Suporta bulanan",
"scandalExtraDailyPct": "Dugang risgo sa iskandalo/adlaw",
"monthlyCost": "Gasto kada bulan",
"politicalFreeSlotsHint": "Ang mga politikal nga opisina naghatag og {count} ka affair slot nga walay bulan nga suporta (ang barato nga relasyon una).",
"politicalFreeMaintenance": "Opisina (libre)",

View File

@@ -761,6 +761,7 @@
"visibility": "Sichtbarkeit",
"discretion": "Diskretion",
"maintenance": "Unterhalt",
"scandalExtraDailyPct": "Zusatz-Skandalrisiko/Tag",
"monthlyCost": "Monatskosten",
"politicalFreeSlotsHint": "Politische Ämter gewähren dir {count} Liebschaftsplatz/-plätze ohne monatlichen Unterhalt (die günstigsten Beziehungen zählen zuerst).",
"politicalFreeMaintenance": "Amt (frei)",

View File

@@ -907,6 +907,7 @@
"visibility": "Visibility",
"discretion": "Discretion",
"maintenance": "Maintenance",
"scandalExtraDailyPct": "Extra scandal risk/day",
"monthlyCost": "Monthly Cost",
"politicalFreeSlotsHint": "Political offices grant you {count} affair slot(s) with no monthly upkeep (cheapest relationships count first).",
"politicalFreeMaintenance": "Office (free)",

View File

@@ -761,6 +761,7 @@
"visibility": "Visibilidad",
"discretion": "Discreción",
"maintenance": "Mantenimiento",
"scandalExtraDailyPct": "Riesgo extra de escándalo/día",
"monthlyCost": "Coste mensual",
"politicalFreeSlotsHint": "Los cargos políticos te conceden {count} plaza(s) de relación sin mantenimiento mensual (primero cuentan las relaciones más baratas).",
"politicalFreeMaintenance": "Cargo (gratis)",

View File

@@ -759,6 +759,7 @@
"visibility": "visibilité",
"discretion": "discrétion",
"maintenance": "Entretien",
"scandalExtraDailyPct": "Risque de scandale supplémentaire/jour",
"monthlyCost": "Coûts mensuels",
"politicalFreeSlotsHint": "Les bureaux politiques vous accordent {count} intérêts amoureux sans entretien mensuel (les relations les moins chères comptent en premier).",
"politicalFreeMaintenance": "Bureau (vacant)",

View File

@@ -436,6 +436,7 @@ export default {
vehicles: [],
activeTab: 'production',
productPricesCache: {}, // Cache für regionale Preise: { productId: price }
productNetMetricsCache: {}, // Netto-Metriken vom Backend: { productId: { netPerPiece, netPerMinute, ... } }
/** Cache-Schlüssel: Region + ob MAX(worth) über alle Filialregionen (bei Fahrzeug) */
productPricesCacheKey: null,
tabs: [
@@ -739,6 +740,7 @@ export default {
async loadProductPricesForCurrentBranch() {
if (!this.selectedBranch || !this.selectedBranch.regionId) {
this.productPricesCache = {};
this.productNetMetricsCache = {};
this.productPricesCacheKey = null;
return;
}
@@ -755,6 +757,7 @@ export default {
}
const { data } = await apiClient.get('/api/falukant/products/prices-in-region', { params });
this.productPricesCache = data.prices || {};
this.productNetMetricsCache = data.netMetrics || {};
this.productPricesCacheKey = cacheKey;
} catch (error) {
console.error(`Error loading product prices for region ${this.selectedBranch.regionId}:`, error);
@@ -780,6 +783,7 @@ export default {
}
}
this.productPricesCache = prices;
this.productNetMetricsCache = {};
this.productPricesCacheKey = cacheKey;
}
},
@@ -873,6 +877,13 @@ export default {
},
calculateProductProfit(product) {
const netMetrics = this.productNetMetricsCache?.[product.id];
if (netMetrics && Number.isFinite(Number(netMetrics.netPerPiece)) && Number.isFinite(Number(netMetrics.netPerMinute))) {
return {
absolute: Number(netMetrics.netPerPiece).toFixed(2),
perMinute: Number(netMetrics.netPerMinute).toFixed(2),
};
}
const { absolute: revenueAbsoluteStr, perMinute: revenuePerMinuteStr }
= this.calculateProductRevenue(product);
const revenueAbsolute = parseFloat(revenueAbsoluteStr);

View File

@@ -397,6 +397,10 @@
<dt>{{ $t('falukant.family.lovers.maintenance') }}</dt>
<dd>{{ lover.maintenanceLevel }}</dd>
</div>
<div>
<dt>{{ $t('falukant.family.lovers.scandalExtraDailyPct') }}</dt>
<dd>{{ Number(lover.scandalExtraDailyPct || 0).toFixed(1) }}%</dd>
</div>
<div>
<dt>{{ $t('falukant.family.lovers.monthlyCost') }}</dt>
<dd>