From 8550bd31d9087e9e01b105f02c22001d666e0f26 Mon Sep 17 00:00:00 2001 From: "Torsten Schulz (local)" Date: Wed, 28 Jan 2026 15:18:26 +0100 Subject: [PATCH] Add bulk pricing retrieval for products in region: Implement getAllProductPricesInRegion method in FalukantService, update FalukantController and router to support new endpoint, and modify BranchView to utilize bulk request for improved performance. --- backend/controllers/falukantController.js | 7 ++ backend/routers/falukantRouter.js | 1 + backend/services/falukantService.js | 85 +++++++++++++++++++++- frontend/src/views/falukant/BranchView.vue | 53 +++++++++----- 4 files changed, 126 insertions(+), 20 deletions(-) diff --git a/backend/controllers/falukantController.js b/backend/controllers/falukantController.js index a158980..2670649 100644 --- a/backend/controllers/falukantController.js +++ b/backend/controllers/falukantController.js @@ -182,6 +182,13 @@ class FalukantController { } return this.service.getProductPriceInRegion(userId, productId, regionId); }); + this.getAllProductPricesInRegion = this._wrapWithUser((userId, req) => { + const regionId = parseInt(req.query.regionId, 10); + if (Number.isNaN(regionId)) { + throw new Error('regionId is required'); + } + return this.service.getAllProductPricesInRegion(userId, regionId); + }); this.getProductPricesInCities = this._wrapWithUser((userId, req) => { const productId = parseInt(req.query.productId, 10); const currentPrice = parseFloat(req.query.currentPrice); diff --git a/backend/routers/falukantRouter.js b/backend/routers/falukantRouter.js index cf3f967..56c6001 100644 --- a/backend/routers/falukantRouter.js +++ b/backend/routers/falukantRouter.js @@ -73,6 +73,7 @@ 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-region', falukantController.getAllProductPricesInRegion); router.get('/products/prices-in-cities', falukantController.getProductPricesInCities); router.get('/branches/:branchId/taxes', falukantController.getBranchTaxes); router.get('/vehicles/types', falukantController.getVehicleTypes); diff --git a/backend/services/falukantService.js b/backend/services/falukantService.js index bf51176..4d24cb8 100644 --- a/backend/services/falukantService.js +++ b/backend/services/falukantService.js @@ -566,7 +566,13 @@ class FalukantService extends BaseService { } async getBranches(hashedUserId) { + const startTime = Date.now(); + console.log(`[getBranches] Start für userId: ${hashedUserId}`); + const u = await getFalukantUserOrFail(hashedUserId); + const userTime = Date.now(); + console.log(`[getBranches] User geladen in ${userTime - startTime}ms`); + const bs = await Branch.findAll({ where: { falukantUserId: u.id }, include: [ @@ -591,6 +597,8 @@ class FalukantService extends BaseService { attributes: ['id', 'regionId'], order: [['branchTypeId', 'ASC']] }); + const branchesTime = Date.now(); + console.log(`[getBranches] Branches geladen (${bs.length} Stück) in ${branchesTime - userTime}ms`); // Lade Wetter explizit für alle Regionen, um sicherzustellen, dass es korrekt geladen wird const regionIds = [...new Set(bs.map(b => b.regionId))]; @@ -601,8 +609,10 @@ class FalukantService extends BaseService { ] }); const weatherMap = new Map(weathers.map(w => [w.regionId, w.weatherType?.tr || null])); + const weatherTime = Date.now(); + console.log(`[getBranches] Weather geladen in ${weatherTime - branchesTime}ms`); - return bs.map(b => { + const result = bs.map(b => { const branchJson = b.toJSON(); // Verwende das explizit geladene Wetter, falls vorhanden, sonst das aus der Include-Beziehung const weather = weatherMap.get(b.regionId) || branchJson.region?.weather?.weatherType?.tr || null; @@ -612,6 +622,11 @@ class FalukantService extends BaseService { weather: weather }; }); + + const totalTime = Date.now() - startTime; + console.log(`[getBranches] Gesamtzeit: ${totalTime}ms für ${result.length} Branches`); + + return result; } async createBranch(hashedUserId, cityId, branchTypeId) { @@ -4217,6 +4232,74 @@ class FalukantService extends BaseService { } } + async getAllProductPricesInRegion(hashedUserId, regionId) { + const startTime = Date.now(); + console.log(`[getAllProductPricesInRegion] Start für userId: ${hashedUserId}, regionId: ${regionId}`); + + try { + const user = await this.getFalukantUserByHashedId(hashedUserId); + const userTime = Date.now(); + console.log(`[getAllProductPricesInRegion] User geladen in ${userTime - startTime}ms`); + + const character = await FalukantCharacter.findOne({ where: { userId: user.id } }); + if (!character) { + throw new Error(`No FalukantCharacter found for user with id ${user.id}`); + } + const characterTime = Date.now(); + console.log(`[getAllProductPricesInRegion] Character geladen in ${characterTime - userTime}ms`); + + // Lade alle Produkte auf einmal + const products = await ProductType.findAll({ + attributes: ['id', 'sellCost'] + }); + const productsTime = Date.now(); + console.log(`[getAllProductPricesInRegion] ${products.length} Produkte geladen in ${productsTime - characterTime}ms`); + + // Lade alle Knowledge-Werte für diesen Character auf einmal + const knowledges = await Knowledge.findAll({ + where: { characterId: character.id }, + attributes: ['productId', 'knowledge'] + }); + const knowledgeMap = new Map(knowledges.map(k => [k.productId, k.knowledge || 0])); + const knowledgeTime = Date.now(); + console.log(`[getAllProductPricesInRegion] ${knowledges.length} Knowledge-Werte geladen in ${knowledgeTime - productsTime}ms`); + + // Lade alle TownProductWorth-Werte für diese Region auf einmal + const townWorths = await TownProductWorth.findAll({ + where: { regionId: regionId }, + attributes: ['productId', 'worthPercent'] + }); + const worthMap = new Map(townWorths.map(tw => [tw.productId, tw.worthPercent || 50])); + const worthTime = Date.now(); + console.log(`[getAllProductPricesInRegion] ${townWorths.length} Worth-Werte geladen in ${worthTime - knowledgeTime}ms`); + + // Berechne Preise für alle Produkte + const prices = {}; + for (const product of products) { + if (product.sellCost === null || product.sellCost === undefined) { + continue; // Überspringe Produkte ohne sellCost + } + + const knowledgeFactor = knowledgeMap.get(product.id) || 0; + const worthPercent = worthMap.get(product.id) || 50; + + // Verwende calcRegionalSellPrice mit bereits geladenem worthPercent + const price = await calcRegionalSellPrice(product, knowledgeFactor, regionId, worthPercent); + prices[product.id] = price; + } + const calcTime = Date.now(); + console.log(`[getAllProductPricesInRegion] Preise berechnet in ${calcTime - worthTime}ms`); + + const totalTime = Date.now() - startTime; + console.log(`[getAllProductPricesInRegion] Gesamtzeit: ${totalTime}ms für ${Object.keys(prices).length} Produkte`); + + return { prices }; + } catch (error) { + console.error(`[getAllProductPricesInRegion] Error for regionId=${regionId}:`, error); + throw error; + } + } + async getProductPricesInCities(hashedUserId, productId, currentPrice, currentRegionId = null) { const user = await this.getFalukantUserByHashedId(hashedUserId); const character = await FalukantCharacter.findOne({ where: { userId: user.id } }); diff --git a/frontend/src/views/falukant/BranchView.vue b/frontend/src/views/falukant/BranchView.vue index 5ccae65..1a6f7a6 100644 --- a/frontend/src/views/falukant/BranchView.vue +++ b/frontend/src/views/falukant/BranchView.vue @@ -572,27 +572,42 @@ export default { 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); + // Lade alle Preise für die Region auf einmal (Bulk-Request) + try { + const startTime = performance.now(); + const { data } = await apiClient.get('/api/falukant/products/prices-in-region', { + params: { + regionId: this.selectedBranch.regionId + } + }); + const loadTime = performance.now() - startTime; + console.log(`[BranchView] Product prices loaded in ${loadTime.toFixed(2)}ms`); + this.productPricesCache = data.prices || {}; + } catch (error) { + console.error(`Error loading product prices for region ${this.selectedBranch.regionId}:`, error); + // Fallback: Lade Preise einzeln (alte Methode) + console.warn('[BranchView] Falling back to individual product price requests'); + 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 (err) { + console.error(`Error loading price for product ${product.id}:`, err); + // 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; } - this.productPricesCache = prices; }, formatPercent(value) {