Implement synchronous price calculation for batch operations in FalukantService, optimizing performance by reducing database queries. Update inventory handling to batch delete items and enhance revenue calculations. Fix translation formatting in German locale for sellAllSuccess message.
This commit is contained in:
@@ -92,6 +92,14 @@ function calcSellPrice(product, knowledgeFactor = 0) {
|
||||
return min + (max - min) * (knowledgeFactor / 100);
|
||||
}
|
||||
|
||||
// Synchron version für Batch-Operationen (ohne DB-Query)
|
||||
function calcRegionalSellPriceSync(product, knowledgeFactor, worthPercent = 50) {
|
||||
const basePrice = product.sellCost * (worthPercent / 100);
|
||||
const min = basePrice * 0.6;
|
||||
const max = basePrice;
|
||||
return min + (max - min) * (knowledgeFactor / 100);
|
||||
}
|
||||
|
||||
async function calcRegionalSellPrice(product, knowledgeFactor, regionId, worthPercent = null) {
|
||||
// Wenn worthPercent nicht übergeben wurde, hole es aus der Datenbank
|
||||
if (worthPercent === null) {
|
||||
@@ -1698,33 +1706,68 @@ class FalukantService extends BaseService {
|
||||
]
|
||||
});
|
||||
if (!inventory.length) return { success: true, revenue: 0 };
|
||||
|
||||
// PERFORMANCE OPTIMIZATION: Batch-Load alle benötigten Daten
|
||||
const regionIds = [...new Set(inventory.map(item => item.stock.branch.regionId))];
|
||||
const productIds = [...new Set(inventory.map(item => item.productType.id))];
|
||||
|
||||
// 1. Batch-Load alle TownProductWorth Einträge
|
||||
const townWorths = await TownProductWorth.findAll({
|
||||
where: {
|
||||
productId: { [Op.in]: productIds },
|
||||
regionId: { [Op.in]: regionIds }
|
||||
}
|
||||
});
|
||||
const worthMap = new Map();
|
||||
townWorths.forEach(tw => {
|
||||
worthMap.set(`${tw.productId}-${tw.regionId}`, tw.worthPercent);
|
||||
});
|
||||
|
||||
// 2. Batch-Load alle RegionData (falls benötigt, aber eigentlich schon in inventory.stock.branch enthalten)
|
||||
// 3. Batch-Berechne Steuern für alle Regionen
|
||||
const taxMap = new Map();
|
||||
const uniqueRegionIds = [...new Set(regionIds)];
|
||||
for (const regionId of uniqueRegionIds) {
|
||||
const tax = await getCumulativeTaxPercentWithExemptions(falukantUser.id, regionId);
|
||||
taxMap.set(regionId, tax);
|
||||
}
|
||||
|
||||
// 4. Berechne Preise, Steuern und Einnahmen in EINER Schleife
|
||||
let total = 0;
|
||||
let totalTax = 0;
|
||||
const sellItems = [];
|
||||
|
||||
for (const item of inventory) {
|
||||
const knowledgeVal = item.productType.knowledges[0]?.knowledge || 0;
|
||||
const regionId = item.stock.branch.regionId;
|
||||
const pricePerUnit = await calcRegionalSellPrice(item.productType, knowledgeVal, regionId);
|
||||
const cumulativeTax = await getCumulativeTaxPercentWithExemptions(falukantUser.id, regionId);
|
||||
const inflationFactor = cumulativeTax >= 100 ? 1 : (1 / (1 - cumulativeTax / 100));
|
||||
const adjustedPricePerUnit = Math.round(pricePerUnit * inflationFactor * 100) / 100;
|
||||
total += item.quantity * adjustedPricePerUnit;
|
||||
await this.addSellItem(item.stock.branch.id, falukantUser.id, item.productType.id, item.quantity);
|
||||
}
|
||||
// compute tax per region (using cumulative tax per region) and aggregate
|
||||
let totalTax = 0;
|
||||
for (const item of inventory) {
|
||||
const regionId = item.stock.branch.regionId;
|
||||
const region = await RegionData.findOne({ where: { id: regionId } });
|
||||
const cumulativeTax = await getCumulativeTaxPercent(regionId);
|
||||
const pricePerUnit = await calcRegionalSellPrice(item.productType, item.productType.knowledges?.[0]?.knowledge || 0, regionId);
|
||||
const worthPercent = worthMap.get(`${item.productType.id}-${regionId}`) || 50;
|
||||
|
||||
// Berechne Preis (ohne DB-Query, da worthPercent bereits geladen)
|
||||
const pricePerUnit = calcRegionalSellPriceSync(item.productType, knowledgeVal, worthPercent);
|
||||
|
||||
// Hole Steuer aus Cache/Map
|
||||
const cumulativeTax = taxMap.get(regionId);
|
||||
const inflationFactor = cumulativeTax >= 100 ? 1 : (1 / (1 - cumulativeTax / 100));
|
||||
const adjustedPricePerUnit = Math.round(pricePerUnit * inflationFactor * 100) / 100;
|
||||
const itemRevenue = item.quantity * adjustedPricePerUnit;
|
||||
const itemTax = Math.round((itemRevenue * cumulativeTax / 100) * 100) / 100;
|
||||
|
||||
total += itemRevenue;
|
||||
totalTax += itemTax;
|
||||
|
||||
// Sammle SellItems für Batch-Operation
|
||||
sellItems.push({
|
||||
branchId: item.stock.branch.id,
|
||||
productId: item.productType.id,
|
||||
quantity: item.quantity
|
||||
});
|
||||
}
|
||||
|
||||
const totalNet = Math.round((total - totalTax) * 100) / 100;
|
||||
|
||||
// 5. Batch-Update DaySell Einträge
|
||||
await this.addSellItemsBatch(sellItems, falukantUser.id);
|
||||
|
||||
const moneyResult = await updateFalukantUserMoney(
|
||||
falukantUser.id,
|
||||
totalNet,
|
||||
@@ -1738,9 +1781,11 @@ class FalukantService extends BaseService {
|
||||
const taxResult = await updateFalukantUserMoney(parseInt(treasuryId, 10), Math.round(totalTax * 100) / 100, `Sales tax (aggregate)`, falukantUser.id);
|
||||
if (!taxResult.success) throw new Error('Failed to update money for treasury');
|
||||
}
|
||||
for (const item of inventory) {
|
||||
await Inventory.destroy({ where: { id: item.id } });
|
||||
}
|
||||
|
||||
// Batch-Delete Inventory Items
|
||||
const inventoryIds = inventory.map(item => item.id);
|
||||
await Inventory.destroy({ where: { id: { [Op.in]: inventoryIds } } });
|
||||
|
||||
console.log('[FalukantService.sellAllProducts] emitting events for user', falukantUser.user.hashedId, 'branch', branchId, 'revenue', total, 'items', inventory.length);
|
||||
notifyUser(falukantUser.user.hashedId, 'falukantUpdateStatus', {});
|
||||
notifyUser(falukantUser.user.hashedId, 'falukantBranchUpdate', { branchId });
|
||||
@@ -1772,6 +1817,57 @@ class FalukantService extends BaseService {
|
||||
}
|
||||
}
|
||||
|
||||
async addSellItemsBatch(sellItems, userId) {
|
||||
if (!sellItems || sellItems.length === 0) return;
|
||||
|
||||
// Lade alle benötigten Branches auf einmal
|
||||
const branchIds = [...new Set(sellItems.map(item => item.branchId))];
|
||||
const branches = await Branch.findAll({
|
||||
where: { id: { [Op.in]: branchIds } },
|
||||
attributes: ['id', 'regionId']
|
||||
});
|
||||
const branchMap = new Map(branches.map(b => [b.id, b]));
|
||||
|
||||
// Gruppiere nach (regionId, productId, sellerId)
|
||||
const grouped = new Map();
|
||||
for (const item of sellItems) {
|
||||
const branch = branchMap.get(item.branchId);
|
||||
if (!branch) continue;
|
||||
|
||||
const key = `${branch.regionId}-${item.productId}-${userId}`;
|
||||
if (!grouped.has(key)) {
|
||||
grouped.set(key, {
|
||||
regionId: branch.regionId,
|
||||
productId: item.productId,
|
||||
sellerId: userId,
|
||||
quantity: 0
|
||||
});
|
||||
}
|
||||
grouped.get(key).quantity += item.quantity;
|
||||
}
|
||||
|
||||
// Batch-Update oder Create für alle gruppierten Einträge
|
||||
const promises = [];
|
||||
for (const [key, data] of grouped) {
|
||||
promises.push(
|
||||
DaySell.findOrCreate({
|
||||
where: {
|
||||
regionId: data.regionId,
|
||||
productId: data.productId,
|
||||
sellerId: data.sellerId
|
||||
},
|
||||
defaults: { quantity: data.quantity }
|
||||
}).then(([daySell, created]) => {
|
||||
if (!created) {
|
||||
daySell.quantity += data.quantity;
|
||||
return daySell.save();
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
||||
await Promise.all(promises);
|
||||
}
|
||||
|
||||
// Return tax summary for a branch: total cumulative tax and breakdown per region (region -> parent chain)
|
||||
async getBranchTaxes(hashedUserId, branchId) {
|
||||
const user = await getFalukantUserOrFail(hashedUserId);
|
||||
|
||||
Reference in New Issue
Block a user