Финансы: деревья категорий как в Data-Insight2 — группировка авто/ручные, свёрнуто по умолчанию, полосы сумм, нераспределённые

This commit is contained in:
2026-09-06 09:53:45 +03:00
parent 2d137332da
commit a7a14d947a
6 changed files with 228 additions and 31 deletions

View File

@@ -34,3 +34,58 @@ export function applyCategoryRules(row: Record<string, any>, categories: any[]):
}
return null;
}
// Сумма статистики категории со всеми потомками (статистика хранится по id глубочайшей совпавшей категории)
function subtreeStats(
cat: any,
treeTotals: Map<string, { total: number; count: number }>
): { total: number; count: number } {
const own = treeTotals.get(cat.id) || { total: 0, count: 0 };
let total = own.total;
let count = own.count;
for (const child of cat.children || []) {
const s = subtreeStats(child, treeTotals);
total += s.total;
count += s.count;
}
return { total, count };
}
// Группировка корневых категорий по деревьям автоопределения (как в Data-Insight2):
// корни с treeId из конфигурации дерева — в это дерево, остальные — в «Ручные категории».
export function buildGroupedTrees(
categories: any[],
autoConfig: { trees: Array<{ id: string; name: string; isPrimary?: boolean }> },
treeTotals: Map<string, { total: number; count: number }>,
buildTreeNodes: (roots: any[]) => any[]
): Array<{ id: string; name: string; isPrimary: boolean; total: number; count: number; nodes: any[] }> {
const configIds = new Set(autoConfig.trees.map((t) => t.id));
const manualRoots = categories.filter((c) => !c.treeId || !configIds.has(c.treeId));
const trees: Array<{ id: string; name: string; isPrimary: boolean; total: number; count: number; nodes: any[] }> = [];
if (manualRoots.length > 0) {
trees.push({
id: "__manual__",
name: "Ручные категории",
isPrimary: true,
total: manualRoots.reduce((s, c) => s + subtreeStats(c, treeTotals).total, 0),
count: manualRoots.reduce((s, c) => s + subtreeStats(c, treeTotals).count, 0),
nodes: buildTreeNodes(manualRoots),
});
}
for (const treeCfg of autoConfig.trees) {
const roots = categories.filter((c) => c.treeId === treeCfg.id);
trees.push({
id: treeCfg.id,
name: treeCfg.name,
isPrimary: !!treeCfg.isPrimary,
total: roots.reduce((s, c) => s + subtreeStats(c, treeTotals).total, 0),
count: roots.reduce((s, c) => s + subtreeStats(c, treeTotals).count, 0),
nodes: buildTreeNodes(roots),
});
}
return trees;
}