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({
|
||||
|
||||
Reference in New Issue
Block a user