Add batch processing for product price retrieval: Implement getProductPricesInCitiesBatch method in FalukantService for handling multiple product price requests in a single API call. Update FalukantController and router to support new endpoint, and refactor RevenueSection and SaleSection components to utilize batch processing for improved performance and reduced API calls.
This commit is contained in:
@@ -214,6 +214,16 @@ class FalukantController {
|
||||
}
|
||||
return this.service.getProductPricesInCities(userId, productId, currentPrice, currentRegionId);
|
||||
});
|
||||
this.getProductPricesInCitiesBatch = this._wrapWithUser((userId, req) => {
|
||||
const body = req.body || {};
|
||||
const items = Array.isArray(body.items) ? body.items : [];
|
||||
const currentRegionId = body.currentRegionId != null ? parseInt(body.currentRegionId, 10) : null;
|
||||
const valid = items.map(i => ({
|
||||
productId: parseInt(i.productId, 10),
|
||||
currentPrice: parseFloat(i.currentPrice)
|
||||
})).filter(i => !Number.isNaN(i.productId) && !Number.isNaN(i.currentPrice));
|
||||
return this.service.getProductPricesInCitiesBatch(userId, valid, Number.isNaN(currentRegionId) ? null : currentRegionId);
|
||||
});
|
||||
this.renovate = this._wrapWithUser((userId, req) => this.service.renovate(userId, req.body.element));
|
||||
this.renovateAll = this._wrapWithUser((userId) => this.service.renovateAll(userId));
|
||||
|
||||
|
||||
@@ -82,6 +82,7 @@ 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.post('/products/prices-in-cities-batch', falukantController.getProductPricesInCitiesBatch);
|
||||
router.get('/branches/:branchId/taxes', falukantController.getBranchTaxes);
|
||||
router.get('/vehicles/types', falukantController.getVehicleTypes);
|
||||
router.post('/vehicles', falukantController.buyVehicles);
|
||||
|
||||
@@ -4528,6 +4528,103 @@ class FalukantService extends BaseService {
|
||||
return results;
|
||||
}
|
||||
|
||||
/**
|
||||
* Batch-Variante: Preise für mehrere Produkte in einem Request.
|
||||
* @param {string} hashedUserId
|
||||
* @param {Array<{ productId: number, currentPrice: number }>} items
|
||||
* @param {number|null} currentRegionId
|
||||
* @returns {Promise<Record<number, Array<{ regionId, regionName, price, branchType }>>>}
|
||||
*/
|
||||
async getProductPricesInCitiesBatch(hashedUserId, items, currentRegionId = null) {
|
||||
if (!items || items.length === 0) return {};
|
||||
const productIds = [...new Set(items.map(i => i.productId))];
|
||||
const priceByProduct = new Map(items.map(i => [i.productId, i.currentPrice]));
|
||||
|
||||
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}`);
|
||||
}
|
||||
|
||||
const [products, knowledges, cities, townWorths] = await Promise.all([
|
||||
ProductType.findAll({ where: { id: { [Op.in]: productIds } }, attributes: ['id', 'sellCost'] }),
|
||||
Knowledge.findAll({
|
||||
where: { characterId: character.id, productId: { [Op.in]: productIds } },
|
||||
attributes: ['productId', 'knowledge']
|
||||
}),
|
||||
RegionData.findAll({
|
||||
attributes: ['id', 'name'],
|
||||
include: [
|
||||
{
|
||||
model: RegionType,
|
||||
as: 'regionType',
|
||||
where: { labelTr: 'city' },
|
||||
attributes: ['labelTr']
|
||||
},
|
||||
{
|
||||
model: Branch,
|
||||
as: 'branches',
|
||||
where: { falukantUserId: user.id },
|
||||
include: [{ model: BranchType, as: 'branchType', attributes: ['labelTr'] }],
|
||||
attributes: ['branchTypeId'],
|
||||
required: false
|
||||
}
|
||||
]
|
||||
}),
|
||||
TownProductWorth.findAll({
|
||||
where: { productId: { [Op.in]: productIds } },
|
||||
attributes: ['productId', 'regionId', 'worthPercent']
|
||||
})
|
||||
]);
|
||||
|
||||
const knowledgeByProduct = new Map(knowledges.map(k => [k.productId, k.knowledge || 0]));
|
||||
const worthByProductRegion = new Map();
|
||||
for (const tw of townWorths) {
|
||||
const key = `${tw.productId}-${tw.regionId}`;
|
||||
worthByProductRegion.set(key, tw.worthPercent);
|
||||
}
|
||||
const productById = new Map(products.map(p => [p.id, p]));
|
||||
|
||||
const PRICE_TOLERANCE = 0.01;
|
||||
const out = {};
|
||||
|
||||
for (const product of products) {
|
||||
const knowledgeFactor = knowledgeByProduct.get(product.id) || 0;
|
||||
const currentPrice = priceByProduct.get(product.id) ?? product.sellCost ?? 0;
|
||||
let currentRegionalPrice = currentPrice;
|
||||
if (currentRegionId) {
|
||||
const wp = worthByProductRegion.get(`${product.id}-${currentRegionId}`) ?? 50;
|
||||
currentRegionalPrice = calcRegionalSellPriceSync(product, knowledgeFactor, wp) ?? currentPrice;
|
||||
}
|
||||
|
||||
const results = [];
|
||||
for (const city of cities) {
|
||||
if (currentRegionId && city.id === currentRegionId) continue;
|
||||
const worthPercent = worthByProductRegion.get(`${product.id}-${city.id}`) ?? 50;
|
||||
const priceInCity = calcRegionalSellPriceSync(product, knowledgeFactor, worthPercent);
|
||||
if (priceInCity == null) continue;
|
||||
if (priceInCity <= currentRegionalPrice - PRICE_TOLERANCE) continue;
|
||||
|
||||
let branchType = null;
|
||||
if (city.branches && city.branches.length > 0) {
|
||||
const branchTypes = city.branches.map(b => b.branchType?.labelTr).filter(Boolean);
|
||||
if (branchTypes.includes('store') || branchTypes.includes('fullstack')) branchType = 'store';
|
||||
else if (branchTypes.includes('production')) branchType = 'production';
|
||||
}
|
||||
results.push({
|
||||
regionId: city.id,
|
||||
regionName: city.name,
|
||||
price: priceInCity,
|
||||
branchType
|
||||
});
|
||||
}
|
||||
results.sort((a, b) => b.price - a.price);
|
||||
out[product.id] = results;
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
async renovate(hashedUserId, element) {
|
||||
const user = await getFalukantUserOrFail(hashedUserId);
|
||||
const house = await UserHouse.findOne({
|
||||
|
||||
@@ -114,36 +114,28 @@
|
||||
}
|
||||
},
|
||||
async loadPricesForAllProducts() {
|
||||
if (this.currentRegionId === null || this.currentRegionId === undefined) {
|
||||
if (this.currentRegionId === null || this.currentRegionId === undefined || !this.products.length) {
|
||||
return;
|
||||
}
|
||||
const requests = this.products.map(async (product) => {
|
||||
if (this.loadingPrices.has(product.id)) return;
|
||||
this.loadingPrices.add(product.id);
|
||||
try {
|
||||
const currentPrice = parseFloat(this.calculateProductRevenue(product).absolute);
|
||||
const { data } = await apiClient.get('/api/falukant/products/prices-in-cities', {
|
||||
params: {
|
||||
productId: product.id,
|
||||
currentPrice: currentPrice,
|
||||
currentRegionId: this.currentRegionId
|
||||
}
|
||||
});
|
||||
this.betterPricesMap = {
|
||||
...this.betterPricesMap,
|
||||
[product.id]: data || []
|
||||
};
|
||||
} catch (error) {
|
||||
console.error(`Error loading prices for product ${product.id}:`, error);
|
||||
this.betterPricesMap = {
|
||||
...this.betterPricesMap,
|
||||
[product.id]: []
|
||||
};
|
||||
} finally {
|
||||
this.loadingPrices.delete(product.id);
|
||||
const items = this.products.map((product) => ({
|
||||
productId: product.id,
|
||||
currentPrice: parseFloat(this.calculateProductRevenue(product).absolute)
|
||||
}));
|
||||
try {
|
||||
const { data } = await apiClient.post('/api/falukant/products/prices-in-cities-batch', {
|
||||
currentRegionId: this.currentRegionId,
|
||||
items
|
||||
});
|
||||
this.betterPricesMap = { ...this.betterPricesMap };
|
||||
for (const product of this.products) {
|
||||
this.betterPricesMap[product.id] = (data && data[product.id]) ? data[product.id] : [];
|
||||
}
|
||||
});
|
||||
await Promise.all(requests);
|
||||
} catch (error) {
|
||||
console.error('Error loading prices for products:', error);
|
||||
for (const product of this.products) {
|
||||
this.betterPricesMap = { ...this.betterPricesMap, [product.id]: [] };
|
||||
}
|
||||
}
|
||||
},
|
||||
getBetterPrices(productId) {
|
||||
return this.betterPricesMap[productId] || [];
|
||||
|
||||
@@ -293,27 +293,26 @@
|
||||
}
|
||||
},
|
||||
async loadPricesForInventory() {
|
||||
const requests = this.inventory.map(async (item) => {
|
||||
const itemKey = `${item.region.id}-${item.product.id}-${item.quality}`;
|
||||
if (this.loadingPrices.has(itemKey)) return;
|
||||
this.loadingPrices.add(itemKey);
|
||||
try {
|
||||
const currentPrice = item.product.sellCost || 0;
|
||||
const { data } = await apiClient.get('/api/falukant/products/prices-in-cities', {
|
||||
params: {
|
||||
productId: item.product.id,
|
||||
currentPrice: currentPrice
|
||||
}
|
||||
});
|
||||
item.betterPrices = data || [];
|
||||
} catch (error) {
|
||||
console.error(`Error loading prices for item ${itemKey}:`, error);
|
||||
item.betterPrices = [];
|
||||
} finally {
|
||||
this.loadingPrices.delete(itemKey);
|
||||
if (this.inventory.length === 0) return;
|
||||
const currentRegionId = this.inventory[0]?.region?.id ?? null;
|
||||
const items = this.inventory.map(item => ({
|
||||
productId: item.product.id,
|
||||
currentPrice: item.product.sellCost || 0
|
||||
}));
|
||||
try {
|
||||
const { data } = await apiClient.post('/api/falukant/products/prices-in-cities-batch', {
|
||||
currentRegionId,
|
||||
items
|
||||
});
|
||||
for (const item of this.inventory) {
|
||||
item.betterPrices = data && data[item.product.id] ? data[item.product.id] : [];
|
||||
}
|
||||
});
|
||||
await Promise.all(requests);
|
||||
} catch (error) {
|
||||
console.error('Error loading prices for inventory:', error);
|
||||
for (const item of this.inventory) {
|
||||
item.betterPrices = [];
|
||||
}
|
||||
}
|
||||
},
|
||||
getCityPriceClass(branchType) {
|
||||
if (branchType === 'store') return 'city-price-green';
|
||||
|
||||
Reference in New Issue
Block a user