From ba1a12402d985719019be03b2899f11dbe8e67c1 Mon Sep 17 00:00:00 2001 From: "Torsten Schulz (local)" Date: Tue, 2 Dec 2025 09:55:08 +0100 Subject: [PATCH] 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. --- backend/controllers/falukantController.js | 8 + backend/models/associations.js | 18 + backend/models/falukant/data/production.js | 11 + backend/models/falukant/data/weather.js | 40 ++ .../falukant/type/product_weather_effect.js | 51 ++ backend/models/falukant/type/weather.js | 25 + backend/models/index.js | 6 + backend/routers/falukantRouter.js | 1 + backend/services/falukantService.js | 109 ++++- .../utils/falukant/initializeFalukantTypes.js | 449 ++++++++++++++++++ frontend/src/views/falukant/BranchView.vue | 47 +- 11 files changed, 755 insertions(+), 10 deletions(-) create mode 100644 backend/models/falukant/data/weather.js create mode 100644 backend/models/falukant/type/product_weather_effect.js create mode 100644 backend/models/falukant/type/weather.js diff --git a/backend/controllers/falukantController.js b/backend/controllers/falukantController.js index 8e5c2a3..3ef0be6 100644 --- a/backend/controllers/falukantController.js +++ b/backend/controllers/falukantController.js @@ -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); diff --git a/backend/models/associations.js b/backend/models/associations.js index a6b9703..78abb7e 100644 --- a/backend/models/associations.js +++ b/backend/models/associations.js @@ -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' }); diff --git a/backend/models/falukant/data/production.js b/backend/models/falukant/data/production.js index ce3f3f3..c443275 100644 --- a/backend/models/falukant/data/production.js +++ b/backend/models/falukant/data/production.js @@ -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, diff --git a/backend/models/falukant/data/weather.js b/backend/models/falukant/data/weather.js new file mode 100644 index 0000000..105f7d4 --- /dev/null +++ b/backend/models/falukant/data/weather.js @@ -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; + diff --git a/backend/models/falukant/type/product_weather_effect.js b/backend/models/falukant/type/product_weather_effect.js new file mode 100644 index 0000000..1b019f2 --- /dev/null +++ b/backend/models/falukant/type/product_weather_effect.js @@ -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; + diff --git a/backend/models/falukant/type/weather.js b/backend/models/falukant/type/weather.js new file mode 100644 index 0000000..ae54347 --- /dev/null +++ b/backend/models/falukant/type/weather.js @@ -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; + diff --git a/backend/models/index.js b/backend/models/index.js index 6b56507..74a46e9 100644 --- a/backend/models/index.js +++ b/backend/models/index.js @@ -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, diff --git a/backend/routers/falukantRouter.js b/backend/routers/falukantRouter.js index 53bc3ff..f880f0f 100644 --- a/backend/routers/falukantRouter.js +++ b/backend/routers/falukantRouter.js @@ -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); diff --git a/backend/services/falukantService.js b/backend/services/falukantService.js index 025ee8d..c2643dd 100644 --- a/backend/services/falukantService.js +++ b/backend/services/falukantService.js @@ -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 } }); diff --git a/backend/utils/falukant/initializeFalukantTypes.js b/backend/utils/falukant/initializeFalukantTypes.js index 5479649..64c2d9f 100644 --- a/backend/utils/falukant/initializeFalukantTypes.js +++ b/backend/utils/falukant/initializeFalukantTypes.js @@ -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; + } +}; diff --git a/frontend/src/views/falukant/BranchView.vue b/frontend/src/views/falukant/BranchView.vue index 60256ad..2a20373 100644 --- a/frontend/src/views/falukant/BranchView.vue +++ b/frontend/src/views/falukant/BranchView.vue @@ -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;