Optimize getInventory method in FalukantService by replacing nested includes with a single SQL query for improved performance. Add error handling for invalid branchId input.

This commit is contained in:
Torsten Schulz (local)
2025-12-20 16:13:33 +01:00
parent fbe0d1bcd1
commit c5804f764c

View File

@@ -1547,46 +1547,62 @@ class FalukantService extends BaseService {
}
async getInventory(hashedUserId, branchId) {
// PERFORMANCE: Diese Route war langsam wegen doppelter/verschachtelter Includes (Branch->Stocks->Region + Inventory->Stock->Branch->Region).
// Wir holen stattdessen genau die benötigten Felder in EINER aggregierenden SQL-Query.
const u = await getFalukantUserOrFail(hashedUserId);
const f = branchId ? { id: branchId, falukantUserId: u.id } : { falukantUserId: u.id };
const br = await Branch.findAll({
where: f,
include: [
{ model: FalukantStock, as: 'stocks', include: [{ model: FalukantStockType, as: 'stockType' }] },
{ model: RegionData, as: 'region', include: [{ model: RegionType, as: 'regionType' }] }
]
});
const stockIds = br.flatMap(b => b.stocks.map(s => s.id));
const inv = await Inventory.findAll({
where: { stockId: stockIds },
include: [
{
model: FalukantStock,
as: 'stock',
include: [
{
model: Branch,
as: 'branch',
include: [{ model: RegionData, as: 'region', include: [{ model: RegionType, as: 'regionType' }] }]
},
{ model: FalukantStockType, as: 'stockType' }
]
},
{ model: ProductType, as: 'productType' }
]
});
const grouped = inv.reduce((acc, i) => {
const r = i.stock.branch.region;
const k = `${r.id}-${i.productType.id}-${i.quality}`;
acc[k] = acc[k] || { region: r, product: i.productType, quality: i.quality, totalQuantity: 0 };
acc[k].totalQuantity += i.quantity;
return acc;
}, {});
return Object.values(grouped).sort((a, b) => {
if (a.region.id !== b.region.id) return a.region.id - b.region.id;
if (a.product.id !== b.product.id) return a.product.id - b.product.id;
return a.quality - b.quality;
});
const branchIdInt = branchId == null ? null : parseInt(branchId, 10);
if (branchId != null && Number.isNaN(branchIdInt)) {
throw new Error('Invalid branchId');
}
const rows = await sequelize.query(
`
SELECT
r.id AS region_id,
r.name AS region_name,
rt.id AS region_type_id,
rt.label_tr AS region_type_label_tr,
p.id AS product_id,
p.label_tr AS product_label_tr,
p.sell_cost AS product_sell_cost,
i.quality AS quality,
SUM(i.quantity)::int AS total_quantity
FROM falukant_data.inventory i
JOIN falukant_data.stock s ON s.id = i.stock_id
JOIN falukant_data.branch b ON b.id = s.branch_id
JOIN falukant_data.region r ON r.id = b.region_id
LEFT JOIN falukant_type.region rt ON rt.id = r.region_type_id
JOIN falukant_type.product p ON p.id = i.product_id
WHERE b.falukant_user_id = :falukantUserId
AND (:branchId::int IS NULL OR b.id = :branchId::int)
GROUP BY
r.id, r.name, rt.id, rt.label_tr,
p.id, p.label_tr, p.sell_cost,
i.quality
ORDER BY r.id, p.id, i.quality
`,
{
replacements: { falukantUserId: u.id, branchId: branchIdInt },
type: sequelize.QueryTypes.SELECT
}
);
return (rows || []).map(r => ({
region: {
id: r.region_id,
name: r.region_name,
regionType: r.region_type_id
? { id: r.region_type_id, labelTr: r.region_type_label_tr }
: null
},
product: {
id: r.product_id,
labelTr: r.product_label_tr,
sellCost: r.product_sell_cost
},
quality: r.quality,
totalQuantity: r.total_quantity
}));
}
async sellProduct(hashedUserId, branchId, productId, quality, quantity) {