From 5e26422e9c5d4d5d663f1c1e3661396096b7f27d Mon Sep 17 00:00:00 2001 From: "Torsten Schulz (local)" Date: Mon, 12 Jan 2026 08:58:28 +0100 Subject: [PATCH] Add batch price retrieval for products in region - Implemented a new endpoint to fetch prices for multiple products in a specified region in a single request, improving efficiency. - Added validation for input parameters to ensure proper data handling. - Updated the FalukantService to calculate prices based on knowledge factors and worth percentages for each product. - Modified the frontend to utilize the new batch endpoint, optimizing the loading of product prices. --- backend/controllers/falukantController.js | 12 +++++ backend/routers/falukantRouter.js | 1 + backend/services/falukantService.js | 51 ++++++++++++++++++ frontend/src/views/falukant/BranchView.vue | 62 +++++++++++++++------- 4 files changed, 106 insertions(+), 20 deletions(-) diff --git a/backend/controllers/falukantController.js b/backend/controllers/falukantController.js index 7e97749..c9ff440 100644 --- a/backend/controllers/falukantController.js +++ b/backend/controllers/falukantController.js @@ -162,6 +162,18 @@ class FalukantController { } return this.service.getProductPriceInRegion(userId, productId, regionId); }); + this.getProductPricesInRegionBatch = this._wrapWithUser((userId, req) => { + const productIds = req.query.productIds; + const regionId = parseInt(req.query.regionId, 10); + if (!productIds || Number.isNaN(regionId)) { + throw new Error('productIds (comma-separated) and regionId are required'); + } + const productIdArray = productIds.split(',').map(id => parseInt(id.trim(), 10)).filter(id => !Number.isNaN(id)); + if (productIdArray.length === 0) { + throw new Error('At least one valid productId is required'); + } + return this.service.getProductPricesInRegionBatch(userId, productIdArray, 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 531e64b..d484674 100644 --- a/backend/routers/falukantRouter.js +++ b/backend/routers/falukantRouter.js @@ -76,6 +76,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-batch', falukantController.getProductPricesInRegionBatch); 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 bad5a8e..6576141 100644 --- a/backend/services/falukantService.js +++ b/backend/services/falukantService.js @@ -5133,6 +5133,57 @@ class FalukantService extends BaseService { return regions; } + async getProductPricesInRegionBatch(hashedUserId, productIds, 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}`); + } + + if (!Array.isArray(productIds) || productIds.length === 0) { + return {}; + } + + // Hole alle Produkte auf einmal + const products = await ProductType.findAll({ + where: { id: { [Op.in]: productIds } } + }); + + // Hole alle Knowledge-Werte auf einmal + const knowledges = await Knowledge.findAll({ + where: { + characterId: character.id, + productId: { [Op.in]: productIds } + } + }); + const knowledgeMap = new Map(knowledges.map(k => [k.productId, k.knowledge])); + + // Hole alle TownProductWorth-Werte auf einmal + const townWorths = await TownProductWorth.findAll({ + where: { + productId: { [Op.in]: productIds }, + regionId: regionId + } + }); + const worthMap = new Map(townWorths.map(tw => [tw.productId, tw.worthPercent])); + + // Berechne Preise für alle Produkte + const prices = {}; + for (const product of products) { + const knowledgeFactor = knowledgeMap.get(product.id) || 0; + const worthPercent = worthMap.get(product.id) || 50; + + const basePrice = product.sellCost * (worthPercent / 100); + const min = basePrice * 0.6; + const max = basePrice; + const price = min + (max - min) * (knowledgeFactor / 100); + + prices[product.id] = Math.round(price * 100) / 100; // Auf 2 Dezimalstellen runden + } + + return prices; + } + async getProductPriceInRegion(hashedUserId, productId, regionId) { 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 303e670..c017bd3 100644 --- a/frontend/src/views/falukant/BranchView.vue +++ b/frontend/src/views/falukant/BranchView.vue @@ -572,27 +572,49 @@ 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); - } + if (!this.products || this.products.length === 0) { + this.productPricesCache = {}; + return; + } + + // OPTIMIERUNG: Lade alle Preise in einem Batch-Request + try { + const productIds = this.products.map(p => p.id).join(','); + const { data } = await apiClient.get('/api/falukant/products/prices-in-region-batch', { + params: { + productIds: productIds, + regionId: this.selectedBranch.regionId + } + }); + this.productPricesCache = data || {}; + } catch (error) { + console.error('Error loading prices in batch:', error); + // Fallback: Lade Preise einzeln (aber parallel) + const pricePromises = this.products.map(async (product) => { + try { + const { data } = await apiClient.get('/api/falukant/products/price-in-region', { + params: { + productId: product.id, + regionId: this.selectedBranch.regionId + } + }); + return { productId: product.id, price: 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; + return { productId: product.id, price: minPrice + (maxPrice - minPrice) * (knowledgeFactor / 100) }; + } + }); + + const results = await Promise.all(pricePromises); + this.productPricesCache = {}; + results.forEach(({ productId, price }) => { + this.productPricesCache[productId] = price; + }); } - this.productPricesCache = prices; }, formatPercent(value) {