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.applyForElections = this._wrapWithUser((userId, req) => this.service.applyForElections(userId, req.body.electionIds));
|
||||||
|
|
||||||
this.getRegions = this._wrapWithUser((userId) => this.service.getRegions(userId));
|
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) => {
|
this.getProductPricesInCities = this._wrapWithUser((userId, req) => {
|
||||||
const productId = parseInt(req.query.productId, 10);
|
const productId = parseInt(req.query.productId, 10);
|
||||||
const currentPrice = parseFloat(req.query.currentPrice);
|
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 Vehicle from './falukant/data/vehicle.js';
|
||||||
import Transport from './falukant/data/transport.js';
|
import Transport from './falukant/data/transport.js';
|
||||||
import RegionDistance from './falukant/data/region_distance.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 Blog from './community/blog.js';
|
||||||
import BlogPost from './community/blog_post.js';
|
import BlogPost from './community/blog_post.js';
|
||||||
import Campaign from './match3/campaign.js';
|
import Campaign from './match3/campaign.js';
|
||||||
@@ -288,6 +291,21 @@ export default function setupAssociations() {
|
|||||||
RegionData.belongsTo(RegionType, { foreignKey: 'regionTypeId', as: 'regionType' });
|
RegionData.belongsTo(RegionType, { foreignKey: 'regionTypeId', as: 'regionType' });
|
||||||
RegionType.hasMany(RegionData, { foreignKey: 'regionTypeId', as: 'regions' });
|
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' });
|
FalukantUser.belongsTo(RegionData, { foreignKey: 'mainBranchRegionId', as: 'mainBranchRegion' });
|
||||||
RegionData.hasMany(FalukantUser, { foreignKey: 'mainBranchRegionId', as: 'users' });
|
RegionData.hasMany(FalukantUser, { foreignKey: 'mainBranchRegionId', as: 'users' });
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { Model, DataTypes } from 'sequelize';
|
import { Model, DataTypes } from 'sequelize';
|
||||||
import { sequelize } from '../../../utils/sequelize.js';
|
import { sequelize } from '../../../utils/sequelize.js';
|
||||||
|
import WeatherType from '../type/weather.js';
|
||||||
|
|
||||||
class Production extends Model { }
|
class Production extends Model { }
|
||||||
|
|
||||||
@@ -13,6 +14,16 @@ Production.init({
|
|||||||
quantity: {
|
quantity: {
|
||||||
type: DataTypes.INTEGER,
|
type: DataTypes.INTEGER,
|
||||||
allowNull: false},
|
allowNull: false},
|
||||||
|
weatherTypeId: {
|
||||||
|
type: DataTypes.INTEGER,
|
||||||
|
allowNull: true,
|
||||||
|
references: {
|
||||||
|
model: WeatherType,
|
||||||
|
key: 'id',
|
||||||
|
schema: 'falukant_type'
|
||||||
|
},
|
||||||
|
comment: 'Wetter zum Zeitpunkt der Produktionserstellung'
|
||||||
|
},
|
||||||
startTimestamp: {
|
startTimestamp: {
|
||||||
type: DataTypes.DATE,
|
type: DataTypes.DATE,
|
||||||
allowNull: false,
|
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 Vehicle from './falukant/data/vehicle.js';
|
||||||
import Transport from './falukant/data/transport.js';
|
import Transport from './falukant/data/transport.js';
|
||||||
import RegionDistance from './falukant/data/region_distance.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 Room from './chat/room.js';
|
||||||
import ChatUser from './chat/user.js';
|
import ChatUser from './chat/user.js';
|
||||||
@@ -228,6 +231,9 @@ const models = {
|
|||||||
ElectionHistory,
|
ElectionHistory,
|
||||||
UndergroundType,
|
UndergroundType,
|
||||||
Underground,
|
Underground,
|
||||||
|
WeatherType,
|
||||||
|
Weather,
|
||||||
|
ProductWeatherEffect,
|
||||||
Room,
|
Room,
|
||||||
ChatUser,
|
ChatUser,
|
||||||
ChatRight,
|
ChatRight,
|
||||||
|
|||||||
@@ -70,6 +70,7 @@ router.post('/politics/elections', falukantController.vote);
|
|||||||
router.get('/politics/open', falukantController.getOpenPolitics);
|
router.get('/politics/open', falukantController.getOpenPolitics);
|
||||||
router.post('/politics/open', falukantController.applyForElections);
|
router.post('/politics/open', falukantController.applyForElections);
|
||||||
router.get('/cities', falukantController.getRegions);
|
router.get('/cities', falukantController.getRegions);
|
||||||
|
router.get('/products/price-in-region', falukantController.getProductPriceInRegion);
|
||||||
router.get('/products/prices-in-cities', falukantController.getProductPricesInCities);
|
router.get('/products/prices-in-cities', falukantController.getProductPricesInCities);
|
||||||
router.get('/vehicles/types', falukantController.getVehicleTypes);
|
router.get('/vehicles/types', falukantController.getVehicleTypes);
|
||||||
router.post('/vehicles', falukantController.buyVehicles);
|
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 Vehicle from '../models/falukant/data/vehicle.js';
|
||||||
import Transport from '../models/falukant/data/transport.js';
|
import Transport from '../models/falukant/data/transport.js';
|
||||||
import RegionDistance from '../models/falukant/data/region_distance.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 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) {
|
function calcAge(birthdate) {
|
||||||
const b = new Date(birthdate); b.setHours(0, 0);
|
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');
|
if (u.money < cost) throw new Error('notenoughmoney');
|
||||||
const r = await updateFalukantUserMoney(u.id, -cost, 'Production cost', u.id);
|
const r = await updateFalukantUserMoney(u.id, -cost, 'Production cost', u.id);
|
||||||
if (!r.success) throw new Error('Failed to update money');
|
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, 'falukantUpdateStatus', {});
|
||||||
notifyUser(u.user.hashedId, 'falukantBranchUpdate', { branchId: b.id });
|
notifyUser(u.user.hashedId, 'falukantBranchUpdate', { branchId: b.id });
|
||||||
return d;
|
return d;
|
||||||
@@ -1045,7 +1060,41 @@ class FalukantService extends BaseService {
|
|||||||
async getProduction(hashedUserId, branchId) {
|
async getProduction(hashedUserId, branchId) {
|
||||||
const u = await getFalukantUserOrFail(hashedUserId);
|
const u = await getFalukantUserOrFail(hashedUserId);
|
||||||
const b = await getBranchOrFail(u.id, branchId);
|
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) {
|
async getProducts(hashedUserId) {
|
||||||
@@ -1507,20 +1556,43 @@ class FalukantService extends BaseService {
|
|||||||
],
|
],
|
||||||
where: { falukantUserId: user.id }
|
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 startTimestamp = new Date(production.startTimestamp).getTime();
|
||||||
const endTimestamp = startTimestamp + production.productType.productionTime * 60 * 1000;
|
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 {
|
return {
|
||||||
cityName: production.branch.region.name,
|
cityName: production.branch.region.name,
|
||||||
productName: production.productType.labelTr,
|
productName: production.productType.labelTr,
|
||||||
quantity: production.quantity,
|
quantity: production.quantity,
|
||||||
|
quality: Math.round(quality),
|
||||||
endTimestamp: new Date(endTimestamp).toISOString(),
|
endTimestamp: new Date(endTimestamp).toISOString(),
|
||||||
};
|
};
|
||||||
});
|
}));
|
||||||
|
|
||||||
formattedProductions.sort((a, b) => new Date(a.endTimestamp) - new Date(b.endTimestamp));
|
formattedProductions.sort((a, b) => new Date(a.endTimestamp) - new Date(b.endTimestamp));
|
||||||
return formattedProductions;
|
return formattedProductions;
|
||||||
}
|
}
|
||||||
@@ -3538,6 +3610,31 @@ class FalukantService extends BaseService {
|
|||||||
return regions;
|
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) {
|
async getProductPricesInCities(hashedUserId, productId, currentPrice) {
|
||||||
const user = await this.getFalukantUserByHashedId(hashedUserId);
|
const user = await this.getFalukantUserByHashedId(hashedUserId);
|
||||||
const character = await FalukantCharacter.findOne({ where: { userId: user.id } });
|
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 PoliticalOfficeBenefitType from "../../models/falukant/type/political_office_benefit_type.js";
|
||||||
import PoliticalOfficePrerequisite from "../../models/falukant/predefine/political_office_prerequisite.js";
|
import PoliticalOfficePrerequisite from "../../models/falukant/predefine/political_office_prerequisite.js";
|
||||||
import UndergroundType from "../../models/falukant/type/underground.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.
|
// Debug-Flag: Nur wenn DEBUG_FALUKANT=1 gesetzt ist, werden ausführliche Logs ausgegeben.
|
||||||
const falukantDebug = process.env.DEBUG_FALUKANT === '1';
|
const falukantDebug = process.env.DEBUG_FALUKANT === '1';
|
||||||
@@ -43,6 +47,9 @@ export const initializeFalukantTypes = async () => {
|
|||||||
await initializePoliticalOfficePrerequisites();
|
await initializePoliticalOfficePrerequisites();
|
||||||
await initializeUndergroundTypes();
|
await initializeUndergroundTypes();
|
||||||
await initializeVehicleTypes();
|
await initializeVehicleTypes();
|
||||||
|
await initializeFalukantWeatherTypes();
|
||||||
|
await initializeFalukantWeathers();
|
||||||
|
await initializeFalukantProductWeatherEffects();
|
||||||
};
|
};
|
||||||
|
|
||||||
const regionTypes = [];
|
const regionTypes = [];
|
||||||
@@ -1010,3 +1017,445 @@ export const initializeFalukantTitlesOfNobility = async () => {
|
|||||||
throw error;
|
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: [],
|
products: [],
|
||||||
vehicles: [],
|
vehicles: [],
|
||||||
activeTab: 'production',
|
activeTab: 'production',
|
||||||
|
productPricesCache: {}, // Cache für regionale Preise: { productId: price }
|
||||||
tabs: [
|
tabs: [
|
||||||
{ value: 'production', label: 'falukant.branch.tabs.production' },
|
{ value: 'production', label: 'falukant.branch.tabs.production' },
|
||||||
{ value: 'inventory', label: 'falukant.branch.tabs.inventory' },
|
{ value: 'inventory', label: 'falukant.branch.tabs.inventory' },
|
||||||
@@ -249,6 +250,7 @@ export default {
|
|||||||
this.selectedBranch = newBranch;
|
this.selectedBranch = newBranch;
|
||||||
await this.loadProducts();
|
await this.loadProducts();
|
||||||
await this.loadVehicles();
|
await this.loadVehicles();
|
||||||
|
await this.loadProductPricesForCurrentBranch();
|
||||||
this.$nextTick(() => {
|
this.$nextTick(() => {
|
||||||
this.$refs.directorInfo?.refresh();
|
this.$refs.directorInfo?.refresh();
|
||||||
this.$refs.saleSection?.loadInventory();
|
this.$refs.saleSection?.loadInventory();
|
||||||
@@ -264,6 +266,34 @@ export default {
|
|||||||
this.activeTab = 'director';
|
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() {
|
async createBranch() {
|
||||||
await this.loadBranches();
|
await this.loadBranches();
|
||||||
@@ -315,10 +345,19 @@ export default {
|
|||||||
if (!product.knowledges || product.knowledges.length === 0) {
|
if (!product.knowledges || product.knowledges.length === 0) {
|
||||||
return { absolute: 0, perMinute: 0 };
|
return { absolute: 0, perMinute: 0 };
|
||||||
}
|
}
|
||||||
const knowledgeFactor = product.knowledges[0].knowledge || 0;
|
|
||||||
const maxPrice = product.sellCost;
|
// Verwende gecachten regionalen Preis, falls verfügbar
|
||||||
const minPrice = maxPrice * 0.6;
|
let revenuePerUnit;
|
||||||
const revenuePerUnit = minPrice + (maxPrice - minPrice) * (knowledgeFactor / 100);
|
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
|
const perMinute = product.productionTime > 0
|
||||||
? revenuePerUnit / product.productionTime
|
? revenuePerUnit / product.productionTime
|
||||||
: 0;
|
: 0;
|
||||||
|
|||||||
Reference in New Issue
Block a user