Add product weather effects and regional pricing enhancements

- Introduced a new endpoint in FalukantController to retrieve product prices based on region and product ID.
- Implemented logic in FalukantService to calculate product prices considering user knowledge and regional factors.
- Added weather-related data models and associations to enhance product pricing accuracy based on weather conditions.
- Updated frontend components to cache and display regional product prices effectively, improving user experience.
This commit is contained in:
Torsten Schulz (local)
2025-12-02 09:55:08 +01:00
parent 39716b1f40
commit ba1a12402d
11 changed files with 755 additions and 10 deletions

View File

@@ -144,6 +144,14 @@ class FalukantController {
this.applyForElections = this._wrapWithUser((userId, req) => this.service.applyForElections(userId, req.body.electionIds));
this.getRegions = this._wrapWithUser((userId) => this.service.getRegions(userId));
this.getProductPriceInRegion = this._wrapWithUser((userId, req) => {
const productId = parseInt(req.query.productId, 10);
const regionId = parseInt(req.query.regionId, 10);
if (Number.isNaN(productId) || Number.isNaN(regionId)) {
throw new Error('productId and regionId are required');
}
return this.service.getProductPriceInRegion(userId, productId, regionId);
});
this.getProductPricesInCities = this._wrapWithUser((userId, req) => {
const productId = parseInt(req.query.productId, 10);
const currentPrice = parseFloat(req.query.currentPrice);

View File

@@ -99,6 +99,9 @@ import VehicleType from './falukant/type/vehicle.js';
import Vehicle from './falukant/data/vehicle.js';
import Transport from './falukant/data/transport.js';
import RegionDistance from './falukant/data/region_distance.js';
import WeatherType from './falukant/type/weather.js';
import Weather from './falukant/data/weather.js';
import ProductWeatherEffect from './falukant/type/product_weather_effect.js';
import Blog from './community/blog.js';
import BlogPost from './community/blog_post.js';
import Campaign from './match3/campaign.js';
@@ -288,6 +291,21 @@ export default function setupAssociations() {
RegionData.belongsTo(RegionType, { foreignKey: 'regionTypeId', as: 'regionType' });
RegionType.hasMany(RegionData, { foreignKey: 'regionTypeId', as: 'regions' });
Weather.belongsTo(RegionData, { foreignKey: 'regionId', as: 'region' });
RegionData.hasOne(Weather, { foreignKey: 'regionId', as: 'weather' });
Weather.belongsTo(WeatherType, { foreignKey: 'weatherTypeId', as: 'weatherType' });
WeatherType.hasMany(Weather, { foreignKey: 'weatherTypeId', as: 'weathers' });
ProductWeatherEffect.belongsTo(ProductType, { foreignKey: 'productId', as: 'product' });
ProductType.hasMany(ProductWeatherEffect, { foreignKey: 'productId', as: 'weatherEffects' });
ProductWeatherEffect.belongsTo(WeatherType, { foreignKey: 'weatherTypeId', as: 'weatherType' });
WeatherType.hasMany(ProductWeatherEffect, { foreignKey: 'weatherTypeId', as: 'productEffects' });
Production.belongsTo(WeatherType, { foreignKey: 'weatherTypeId', as: 'weatherType' });
WeatherType.hasMany(Production, { foreignKey: 'weatherTypeId', as: 'productions' });
FalukantUser.belongsTo(RegionData, { foreignKey: 'mainBranchRegionId', as: 'mainBranchRegion' });
RegionData.hasMany(FalukantUser, { foreignKey: 'mainBranchRegionId', as: 'users' });

View File

@@ -1,5 +1,6 @@
import { Model, DataTypes } from 'sequelize';
import { sequelize } from '../../../utils/sequelize.js';
import WeatherType from '../type/weather.js';
class Production extends Model { }
@@ -13,6 +14,16 @@ Production.init({
quantity: {
type: DataTypes.INTEGER,
allowNull: false},
weatherTypeId: {
type: DataTypes.INTEGER,
allowNull: true,
references: {
model: WeatherType,
key: 'id',
schema: 'falukant_type'
},
comment: 'Wetter zum Zeitpunkt der Produktionserstellung'
},
startTimestamp: {
type: DataTypes.DATE,
allowNull: false,

View File

@@ -0,0 +1,40 @@
import { Model, DataTypes } from 'sequelize';
import { sequelize } from '../../../utils/sequelize.js';
import RegionData from './region.js';
import WeatherType from '../type/weather.js';
class Weather extends Model {}
Weather.init(
{
regionId: {
type: DataTypes.INTEGER,
allowNull: false,
references: {
model: RegionData,
key: 'id',
schema: 'falukant_data'
},
unique: true // Jede Stadt hat nur ein Wetter
},
weatherTypeId: {
type: DataTypes.INTEGER,
allowNull: false,
references: {
model: WeatherType,
key: 'id',
schema: 'falukant_type'
}
}
},
{
sequelize,
modelName: 'Weather',
tableName: 'weather',
schema: 'falukant_data',
timestamps: false,
underscored: true}
);
export default Weather;

View File

@@ -0,0 +1,51 @@
import { Model, DataTypes } from 'sequelize';
import { sequelize } from '../../../utils/sequelize.js';
import ProductType from './product.js';
import WeatherType from './weather.js';
class ProductWeatherEffect extends Model {}
ProductWeatherEffect.init(
{
productId: {
type: DataTypes.INTEGER,
allowNull: false,
references: {
model: ProductType,
key: 'id',
schema: 'falukant_type'
}
},
weatherTypeId: {
type: DataTypes.INTEGER,
allowNull: false,
references: {
model: WeatherType,
key: 'id',
schema: 'falukant_type'
}
},
qualityEffect: {
type: DataTypes.INTEGER,
allowNull: false,
comment: 'Effekt auf Qualität: -2 (sehr negativ), -1 (negativ), 0 (neutral), 1 (positiv), 2 (sehr positiv)'
}
},
{
sequelize,
modelName: 'ProductWeatherEffect',
tableName: 'product_weather_effect',
schema: 'falukant_type',
timestamps: false,
underscored: true,
indexes: [
{
unique: true,
fields: ['product_id', 'weather_type_id']
}
]
}
);
export default ProductWeatherEffect;

View File

@@ -0,0 +1,25 @@
import { Model, DataTypes } from 'sequelize';
import { sequelize } from '../../../utils/sequelize.js';
class WeatherType extends Model {}
WeatherType.init(
{
id: {
type: DataTypes.INTEGER,
primaryKey: true,
autoIncrement: true},
tr: {
type: DataTypes.STRING,
allowNull: false}},
{
sequelize,
modelName: 'WeatherType',
tableName: 'weather',
schema: 'falukant_type',
timestamps: false,
underscored: true}
);
export default WeatherType;

View File

@@ -117,6 +117,9 @@ import VehicleType from './falukant/type/vehicle.js';
import Vehicle from './falukant/data/vehicle.js';
import Transport from './falukant/data/transport.js';
import RegionDistance from './falukant/data/region_distance.js';
import WeatherType from './falukant/type/weather.js';
import Weather from './falukant/data/weather.js';
import ProductWeatherEffect from './falukant/type/product_weather_effect.js';
import Room from './chat/room.js';
import ChatUser from './chat/user.js';
@@ -228,6 +231,9 @@ const models = {
ElectionHistory,
UndergroundType,
Underground,
WeatherType,
Weather,
ProductWeatherEffect,
Room,
ChatUser,
ChatRight,

View File

@@ -70,6 +70,7 @@ router.post('/politics/elections', falukantController.vote);
router.get('/politics/open', falukantController.getOpenPolitics);
router.post('/politics/open', falukantController.applyForElections);
router.get('/cities', falukantController.getRegions);
router.get('/products/price-in-region', falukantController.getProductPriceInRegion);
router.get('/products/prices-in-cities', falukantController.getProductPricesInCities);
router.get('/vehicles/types', falukantController.getVehicleTypes);
router.post('/vehicles', falukantController.buyVehicles);

View File

@@ -61,7 +61,10 @@ import VehicleType from '../models/falukant/type/vehicle.js';
import Vehicle from '../models/falukant/data/vehicle.js';
import Transport from '../models/falukant/data/transport.js';
import RegionDistance from '../models/falukant/data/region_distance.js';
import Weather from '../models/falukant/data/weather.js';
import TownProductWorth from '../models/falukant/data/town_product_worth.js';
import ProductWeatherEffect from '../models/falukant/type/product_weather_effect.js';
import WeatherType from '../models/falukant/type/weather.js';
function calcAge(birthdate) {
const b = new Date(birthdate); b.setHours(0, 0);
@@ -1036,7 +1039,19 @@ class FalukantService extends BaseService {
if (u.money < cost) throw new Error('notenoughmoney');
const r = await updateFalukantUserMoney(u.id, -cost, 'Production cost', u.id);
if (!r.success) throw new Error('Failed to update money');
const d = await Production.create({ branchId: b.id, productId, quantity });
// Hole aktuelles Wetter der Region
const currentWeather = await Weather.findOne({
where: { regionId: b.regionId }
});
const weatherTypeId = currentWeather?.weatherTypeId || null;
const d = await Production.create({
branchId: b.id,
productId,
quantity,
weatherTypeId
});
notifyUser(u.user.hashedId, 'falukantUpdateStatus', {});
notifyUser(u.user.hashedId, 'falukantBranchUpdate', { branchId: b.id });
return d;
@@ -1045,7 +1060,41 @@ class FalukantService extends BaseService {
async getProduction(hashedUserId, branchId) {
const u = await getFalukantUserOrFail(hashedUserId);
const b = await getBranchOrFail(u.id, branchId);
return Production.findOne({ where: { regionId: b.regionId } });
const production = await Production.findOne({
where: { branchId: b.id },
include: [
{ model: ProductType, as: 'productType' },
{ model: WeatherType, as: 'weatherType' }
]
});
if (!production) {
return null;
}
// Berechne Qualität basierend auf Wettereffekt
let quality = 50; // Basisqualität (50%)
if (production.weatherTypeId) {
const weatherEffect = await ProductWeatherEffect.findOne({
where: {
productId: production.productId,
weatherTypeId: production.weatherTypeId
}
});
if (weatherEffect) {
// Wettereffekt: -2 bis +2, wird auf Qualität angewendet
// Basisqualität 50%, Effekt wird als Prozentpunkte addiert
quality = Math.max(0, Math.min(100, 50 + (weatherEffect.qualityEffect * 10)));
}
}
// Konvertiere zu JSON und füge berechnete Qualität hinzu
const result = production.toJSON();
result.quality = Math.round(quality);
return result;
}
async getProducts(hashedUserId) {
@@ -1507,20 +1556,43 @@ class FalukantService extends BaseService {
],
where: { falukantUserId: user.id }
},
{ model: ProductType, as: 'productType', attributes: ['labelTr', 'productionTime'] }
{ model: ProductType, as: 'productType', attributes: ['labelTr', 'productionTime', 'id'] },
{ model: WeatherType, as: 'weatherType' }
],
attributes: ['startTimestamp', 'quantity'],
attributes: ['startTimestamp', 'quantity', 'productId', 'weatherTypeId'],
});
const formattedProductions = productions.map((production) => {
// Berechne Qualität für jede Produktion
const formattedProductions = await Promise.all(productions.map(async (production) => {
const startTimestamp = new Date(production.startTimestamp).getTime();
const endTimestamp = startTimestamp + production.productType.productionTime * 60 * 1000;
// Berechne Qualität basierend auf Wettereffekt
let quality = 50; // Basisqualität (50%)
if (production.weatherTypeId) {
const weatherEffect = await ProductWeatherEffect.findOne({
where: {
productId: production.productId,
weatherTypeId: production.weatherTypeId
}
});
if (weatherEffect) {
// Wettereffekt: -2 bis +2, wird auf Qualität angewendet
quality = Math.max(0, Math.min(100, 50 + (weatherEffect.qualityEffect * 10)));
}
}
return {
cityName: production.branch.region.name,
productName: production.productType.labelTr,
quantity: production.quantity,
quality: Math.round(quality),
endTimestamp: new Date(endTimestamp).toISOString(),
};
});
}));
formattedProductions.sort((a, b) => new Date(a.endTimestamp) - new Date(b.endTimestamp));
return formattedProductions;
}
@@ -3538,6 +3610,31 @@ class FalukantService extends BaseService {
return regions;
}
async getProductPriceInRegion(hashedUserId, productId, regionId) {
const user = await this.getFalukantUserByHashedId(hashedUserId);
const character = await FalukantCharacter.findOne({ where: { userId: user.id } });
if (!character) {
throw new Error(`No FalukantCharacter found for user with id ${user.id}`);
}
// Produkt abrufen
const product = await ProductType.findOne({ where: { id: productId } });
if (!product) {
throw new Error(`Product not found with id ${productId}`);
}
// Knowledge für dieses Produkt abrufen
const knowledge = await Knowledge.findOne({
where: { characterId: character.id, productId: productId }
});
const knowledgeFactor = knowledge?.knowledge || 0;
// Verwende die bereits existierende calcRegionalSellPrice Funktion
const price = await calcRegionalSellPrice(product, knowledgeFactor, regionId);
return { price };
}
async getProductPricesInCities(hashedUserId, productId, currentPrice) {
const user = await this.getFalukantUserByHashedId(hashedUserId);
const character = await FalukantCharacter.findOne({ where: { userId: user.id } });

View File

@@ -18,6 +18,10 @@ import PoliticalOfficeType from "../../models/falukant/type/political_office_typ
import PoliticalOfficeBenefitType from "../../models/falukant/type/political_office_benefit_type.js";
import PoliticalOfficePrerequisite from "../../models/falukant/predefine/political_office_prerequisite.js";
import UndergroundType from "../../models/falukant/type/underground.js";
import WeatherType from "../../models/falukant/type/weather.js";
import Weather from "../../models/falukant/data/weather.js";
import ProductWeatherEffect from "../../models/falukant/type/product_weather_effect.js";
import ProductType from "../../models/falukant/type/product.js";
// Debug-Flag: Nur wenn DEBUG_FALUKANT=1 gesetzt ist, werden ausführliche Logs ausgegeben.
const falukantDebug = process.env.DEBUG_FALUKANT === '1';
@@ -43,6 +47,9 @@ export const initializeFalukantTypes = async () => {
await initializePoliticalOfficePrerequisites();
await initializeUndergroundTypes();
await initializeVehicleTypes();
await initializeFalukantWeatherTypes();
await initializeFalukantWeathers();
await initializeFalukantProductWeatherEffects();
};
const regionTypes = [];
@@ -1010,3 +1017,445 @@ export const initializeFalukantTitlesOfNobility = async () => {
throw error;
}
};
const weatherTypes = [
{ tr: "sunny" },
{ tr: "cloudy" },
{ tr: "rainy" },
{ tr: "stormy" },
{ tr: "snowy" },
{ tr: "foggy" },
{ tr: "windy" },
{ tr: "clear" }
];
export const initializeFalukantWeatherTypes = async () => {
try {
for (const weatherType of weatherTypes) {
await WeatherType.findOrCreate({
where: { tr: weatherType.tr },
});
}
console.log(`[Falukant] Wettertypen initialisiert: ${weatherTypes.length} Typen`);
} catch (error) {
console.error('❌ Fehler beim Initialisieren der Falukant-Wettertypen:', error);
throw error;
}
};
export const initializeFalukantWeathers = async () => {
try {
// Hole alle Städte (Regions vom Typ "city")
const cityRegionType = await RegionType.findOne({ where: { labelTr: 'city' } });
if (!cityRegionType) {
console.warn('[Falukant] Kein RegionType "city" gefunden, überspringe Wetter-Initialisierung');
return;
}
const cities = await RegionData.findAll({
where: { regionTypeId: cityRegionType.id },
attributes: ['id', 'name']
});
// Hole alle Wettertypen
const allWeatherTypes = await WeatherType.findAll();
if (allWeatherTypes.length === 0) {
console.warn('[Falukant] Keine Wettertypen gefunden, überspringe Wetter-Initialisierung');
return;
}
// Weise jeder Stadt zufällig ein Wetter zu
for (const city of cities) {
const randomWeatherType = allWeatherTypes[Math.floor(Math.random() * allWeatherTypes.length)];
await Weather.findOrCreate({
where: { regionId: city.id },
defaults: {
weatherTypeId: randomWeatherType.id
}
});
}
console.log(`[Falukant] Wetter für ${cities.length} Städte initialisiert`);
} catch (error) {
console.error('❌ Fehler beim Initialisieren der Falukant-Wetter:', error);
throw error;
}
};
export const initializeFalukantProductWeatherEffects = async () => {
try {
// Hole alle Produkte und Wettertypen
const products = await ProductType.findAll();
const weatherTypes = await WeatherType.findAll();
if (products.length === 0 || weatherTypes.length === 0) {
console.warn('[Falukant] Keine Produkte oder Wettertypen gefunden, überspringe Produkt-Wetter-Effekte');
return;
}
// Erstelle Map für schnellen Zugriff
const productMap = new Map(products.map(p => [p.labelTr, p.id]));
const weatherMap = new Map(weatherTypes.map(w => [w.tr, w.id]));
// Definiere Effekte für jedes Produkt-Wetter-Paar
// Format: { productLabel: { weatherTr: effectValue } }
// effectValue: -2 (sehr negativ), -1 (negativ), 0 (neutral), 1 (positiv), 2 (sehr positiv)
const effects = {
// Landwirtschaftliche Produkte
wheat: {
sunny: 1, // Gutes Wachstum
cloudy: 0,
rainy: 2, // Wasser ist essentiell
stormy: -1, // Kann Ernte beschädigen
snowy: -2, // Kein Wachstum
foggy: 0,
windy: 0,
clear: 1
},
grain: {
sunny: 1,
cloudy: 0,
rainy: 2,
stormy: -1,
snowy: -2,
foggy: 0,
windy: 0,
clear: 1
},
carrot: {
sunny: 1,
cloudy: 0,
rainy: 2,
stormy: -1,
snowy: -2,
foggy: 0,
windy: 0,
clear: 1
},
fish: {
sunny: 0,
cloudy: 0,
rainy: 0,
stormy: -2, // Gefährlich zu fischen
snowy: -1, // Kaltes Wasser
foggy: -1, // Schlechte Sicht
windy: -1, // Schwierig zu fischen
clear: 1
},
meat: {
sunny: -1, // Kann verderben
cloudy: 0,
rainy: -1, // Feucht
stormy: -2,
snowy: 1, // Kühlt
foggy: 0,
windy: 0,
clear: 0
},
leather: {
sunny: -1, // Kann austrocknen
cloudy: 0,
rainy: -1, // Feucht
stormy: -2,
snowy: 1, // Kühlt
foggy: 0,
windy: 0,
clear: 0
},
wood: {
sunny: 1, // Trocknet gut
cloudy: 0,
rainy: -1, // Feucht
stormy: -2, // Kann beschädigt werden
snowy: 0,
foggy: -1, // Feucht
windy: 0,
clear: 1
},
stone: {
sunny: 0,
cloudy: 0,
rainy: 0,
stormy: 0,
snowy: 0,
foggy: 0,
windy: 0,
clear: 0
},
milk: {
sunny: -1, // Kann sauer werden
cloudy: 0,
rainy: 0,
stormy: -1,
snowy: 1, // Kühlt
foggy: 0,
windy: 0,
clear: 0
},
cheese: {
sunny: -1,
cloudy: 0,
rainy: -1, // Feucht
stormy: -1,
snowy: 1, // Kühlt
foggy: 0,
windy: 0,
clear: 0
},
bread: {
sunny: 0,
cloudy: 0,
rainy: -1, // Feucht
stormy: -1,
snowy: 0,
foggy: -1, // Feucht
windy: 0,
clear: 0
},
beer: {
sunny: 0,
cloudy: 0,
rainy: 0,
stormy: 0,
snowy: 1, // Kühlt
foggy: 0,
windy: 0,
clear: 0
},
iron: {
sunny: 0,
cloudy: 0,
rainy: -1, // Rost
stormy: -2, // Rost
snowy: 0,
foggy: -1, // Feucht
windy: 0,
clear: 0
},
copper: {
sunny: 0,
cloudy: 0,
rainy: -1, // Oxidation
stormy: -2,
snowy: 0,
foggy: -1,
windy: 0,
clear: 0
},
spices: {
sunny: 0,
cloudy: 0,
rainy: -1, // Feucht
stormy: -1,
snowy: 0,
foggy: -1, // Feucht
windy: 0,
clear: 0
},
salt: {
sunny: 1, // Trocknet gut
cloudy: 0,
rainy: -2, // Löst sich auf
stormy: -2,
snowy: 0,
foggy: -1, // Feucht
windy: 0,
clear: 1
},
sugar: {
sunny: 0,
cloudy: 0,
rainy: -2, // Löst sich auf
stormy: -2,
snowy: 0,
foggy: -1, // Feucht
windy: 0,
clear: 0
},
vinegar: {
sunny: 1, // Heißes Wetter fördert Gärung
cloudy: 0,
rainy: 0,
stormy: 0,
snowy: -1, // Kaltes Wetter hemmt Gärung
foggy: 0,
windy: 0,
clear: 1 // Heißes Wetter fördert Gärung
},
cotton: {
sunny: 1, // Trocknet gut
cloudy: 0,
rainy: -1, // Feucht
stormy: -2,
snowy: 0,
foggy: -1, // Feucht
windy: 0,
clear: 1
},
wine: {
sunny: 0,
cloudy: 0,
rainy: 0,
stormy: 0,
snowy: 1, // Kühlt
foggy: 0,
windy: 0,
clear: 0
},
gold: {
sunny: 0,
cloudy: 0,
rainy: 0,
stormy: 0,
snowy: 0,
foggy: 0,
windy: 0,
clear: 0
},
diamond: {
sunny: 0,
cloudy: 0,
rainy: 0,
stormy: 0,
snowy: 0,
foggy: 0,
windy: 0,
clear: 0
},
furniture: {
sunny: 0,
cloudy: 0,
rainy: -1, // Feucht
stormy: -2,
snowy: 0,
foggy: -1, // Feucht
windy: 0,
clear: 0
},
clothing: {
sunny: 0,
cloudy: 0,
rainy: -1, // Feucht
stormy: -2,
snowy: 0,
foggy: -1, // Feucht
windy: 0,
clear: 0
},
jewelry: {
sunny: 0,
cloudy: 0,
rainy: -2, // Kann beschädigt werden
stormy: -2,
snowy: 0,
foggy: -1, // Feucht
windy: 0,
clear: 0
},
painting: {
sunny: -1, // Kann verblassen
cloudy: 0,
rainy: -2, // Feucht
stormy: -2,
snowy: 0,
foggy: -1, // Feucht
windy: 0,
clear: -1
},
book: {
sunny: -1, // Kann verblassen
cloudy: 0,
rainy: -2, // Feucht
stormy: -2,
snowy: 0,
foggy: -1, // Feucht
windy: 0,
clear: -1
},
weapon: {
sunny: 0,
cloudy: 0,
rainy: -1, // Rost
stormy: -2, // Rost
snowy: 0,
foggy: -1, // Feucht
windy: 0,
clear: 0
},
armor: {
sunny: 0,
cloudy: 0,
rainy: -1, // Rost
stormy: -2, // Rost
snowy: 0,
foggy: -1, // Feucht
windy: 0,
clear: 0
},
shield: {
sunny: 0,
cloudy: 0,
rainy: -1, // Rost
stormy: -2, // Rost
snowy: 0,
foggy: -1, // Feucht
windy: 0,
clear: 0
},
horse: {
sunny: 1, // Gutes Wetter
cloudy: 0,
rainy: -1, // Nass
stormy: -2, // Angst
snowy: -1, // Kalt
foggy: 0,
windy: 0,
clear: 1
},
ox: {
sunny: 1, // Gutes Wetter
cloudy: 0,
rainy: -1, // Nass
stormy: -2, // Angst
snowy: -1, // Kalt
foggy: 0,
windy: 0,
clear: 1
}
};
// Erstelle alle Produkt-Wetter-Effekte
const effectEntries = [];
for (const [productLabel, weatherEffects] of Object.entries(effects)) {
const productId = productMap.get(productLabel);
if (!productId) {
console.warn(`[Falukant] Produkt "${productLabel}" nicht gefunden, überspringe`);
continue;
}
for (const [weatherTr, effectValue] of Object.entries(weatherEffects)) {
const weatherTypeId = weatherMap.get(weatherTr);
if (!weatherTypeId) {
console.warn(`[Falukant] Wettertyp "${weatherTr}" nicht gefunden, überspringe`);
continue;
}
effectEntries.push({
productId,
weatherTypeId,
qualityEffect: effectValue
});
}
}
// Bulk insert mit ignoreDuplicates
await ProductWeatherEffect.bulkCreate(effectEntries, {
ignoreDuplicates: true
});
console.log(`[Falukant] Produkt-Wetter-Effekte initialisiert: ${effectEntries.length} Einträge`);
} catch (error) {
console.error('❌ Fehler beim Initialisieren der Produkt-Wetter-Effekte:', error);
throw error;
}
};

View File

@@ -165,6 +165,7 @@ export default {
products: [],
vehicles: [],
activeTab: 'production',
productPricesCache: {}, // Cache für regionale Preise: { productId: price }
tabs: [
{ value: 'production', label: 'falukant.branch.tabs.production' },
{ value: 'inventory', label: 'falukant.branch.tabs.inventory' },
@@ -249,6 +250,7 @@ export default {
this.selectedBranch = newBranch;
await this.loadProducts();
await this.loadVehicles();
await this.loadProductPricesForCurrentBranch();
this.$nextTick(() => {
this.$refs.directorInfo?.refresh();
this.$refs.saleSection?.loadInventory();
@@ -264,6 +266,34 @@ export default {
this.activeTab = 'director';
}
},
async loadProductPricesForCurrentBranch() {
if (!this.selectedBranch || !this.selectedBranch.regionId) {
this.productPricesCache = {};
return;
}
// Lade Preise für alle Produkte in der aktuellen Region
const prices = {};
for (const product of this.products) {
try {
const { data } = await apiClient.get('/api/falukant/products/price-in-region', {
params: {
productId: product.id,
regionId: this.selectedBranch.regionId
}
});
prices[product.id] = data.price;
} catch (error) {
console.error(`Error loading price for product ${product.id}:`, error);
// Fallback auf Standard-Berechnung
const knowledgeFactor = product.knowledges?.[0]?.knowledge || 0;
const maxPrice = product.sellCost;
const minPrice = maxPrice * 0.6;
prices[product.id] = minPrice + (maxPrice - minPrice) * (knowledgeFactor / 100);
}
}
this.productPricesCache = prices;
},
async createBranch() {
await this.loadBranches();
@@ -315,10 +345,19 @@ export default {
if (!product.knowledges || product.knowledges.length === 0) {
return { absolute: 0, perMinute: 0 };
}
const knowledgeFactor = product.knowledges[0].knowledge || 0;
const maxPrice = product.sellCost;
const minPrice = maxPrice * 0.6;
const revenuePerUnit = minPrice + (maxPrice - minPrice) * (knowledgeFactor / 100);
// Verwende gecachten regionalen Preis, falls verfügbar
let revenuePerUnit;
if (this.productPricesCache[product.id] !== undefined) {
revenuePerUnit = this.productPricesCache[product.id];
} else {
// Fallback auf Standard-Berechnung
const knowledgeFactor = product.knowledges[0].knowledge || 0;
const maxPrice = product.sellCost;
const minPrice = maxPrice * 0.6;
revenuePerUnit = minPrice + (maxPrice - minPrice) * (knowledgeFactor / 100);
}
const perMinute = product.productionTime > 0
? revenuePerUnit / product.productionTime
: 0;