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:
@@ -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);
|
||||
|
||||
@@ -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' });
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
40
backend/models/falukant/data/weather.js
Normal file
40
backend/models/falukant/data/weather.js
Normal 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;
|
||||
|
||||
51
backend/models/falukant/type/product_weather_effect.js
Normal file
51
backend/models/falukant/type/product_weather_effect.js
Normal 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;
|
||||
|
||||
25
backend/models/falukant/type/weather.js
Normal file
25
backend/models/falukant/type/weather.js
Normal 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;
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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 } });
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -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 };
|
||||
}
|
||||
|
||||
// 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;
|
||||
const revenuePerUnit = minPrice + (maxPrice - minPrice) * (knowledgeFactor / 100);
|
||||
revenuePerUnit = minPrice + (maxPrice - minPrice) * (knowledgeFactor / 100);
|
||||
}
|
||||
|
||||
const perMinute = product.productionTime > 0
|
||||
? revenuePerUnit / product.productionTime
|
||||
: 0;
|
||||
|
||||
Reference in New Issue
Block a user