From 67ed5e542d0a086ae1e27046fa00a20baf44cda9 Mon Sep 17 00:00:00 2001 From: Benjamin Admin Date: Mon, 20 Apr 2026 10:23:59 +0200 Subject: [PATCH] feat(pitch-deck): live-computed sum rows in Finanzplan (like Excel formulas) Sum rows (is_sum_row=true) are now computed live in the frontend from their detail rows, not read from stale DB values. This means: - Category sums (Versicherungen, Marketing, Sonstige etc.) always match - "Summe sonstige" = all non-personal, non-AfA rows - "SUMME Betriebliche" = all rows including personal + AfA - No more manual recompute needed after DB changes Also: chart labels increased from 7-8px to 11px for readability. Co-Authored-By: Claude Opus 4.6 (1M context) --- .../components/slides/FinanzplanSlide.tsx | 46 ++++++++++++++++++- 1 file changed, 45 insertions(+), 1 deletion(-) diff --git a/pitch-deck/components/slides/FinanzplanSlide.tsx b/pitch-deck/components/slides/FinanzplanSlide.tsx index 0e1527e..4b64c53 100644 --- a/pitch-deck/components/slides/FinanzplanSlide.tsx +++ b/pitch-deck/components/slides/FinanzplanSlide.tsx @@ -39,6 +39,7 @@ interface SheetRow { position?: string start_date?: string purchase_amount?: number + [key: string]: unknown } const MONTH_LABELS = [ @@ -575,7 +576,50 @@ export default function FinanzplanSlide({ lang, investorId, preferredScenarioId, - {rows.map(row => { + {(() => { + // Live-compute sum rows from detail rows (like Excel formulas) + const computedRows = rows.map(row => { + const label = getLabel(row) + const cat = (row as Record).category as string || '' + if (!row.is_sum_row) return row + + // Find detail rows for this category + const detailRows = rows.filter(r => { + const rCat = (r as Record).category as string || '' + return rCat === cat && !r.is_sum_row + }) + + // For "Summe sonstige" or "SUMME Betriebliche" — sum all non-sum, non-personal, non-abschreibung + let sourceRows = detailRows + if (label.includes('Summe sonstige') || label.includes('SUMME Betriebliche')) { + sourceRows = rows.filter(r => { + const rCat = (r as Record).category as string || '' + const rLabel = getLabel(r) + if (r.is_sum_row) return false + if (label.includes('Summe sonstige')) { + return rCat !== 'personal' && rCat !== 'abschreibungen' && !rLabel.includes('Personalkosten') && !rLabel.includes('Abschreibungen') + } + // SUMME Betriebliche = everything including personal + abschreibungen + return true + }) + } + + if (sourceRows.length === 0) return row + + const computed: Record = {} + for (let m = 1; m <= 60; m++) { + const key = `m${m}` + computed[key] = sourceRows.reduce((sum, r) => { + const v = getValues(r) + return sum + (v[key] || 0) + }, 0) + } + + return { ...row, values: computed, values_total: computed } + }) + + return computedRows + })().map(row => { const values = getValues(row) const label = getLabel(row) const isSumRow = row.is_sum_row || label.includes('GESAMT') || label.includes('Summe') || label.includes('ÜBERSCHUSS') || label.includes('LIQUIDITÄT') || label.includes('UEBERSCHUSS') || label.includes('LIQUIDITAET')