From dc36e59d17c7f19b16fb642d550e1b8c94f3ddbf Mon Sep 17 00:00:00 2001 From: Benjamin Admin Date: Sat, 18 Apr 2026 14:15:16 +0200 Subject: [PATCH] feat(pitch-deck): formula engine + tooltips for betriebliche Aufwendungen MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Engine formulas added: - Berufsgenossenschaft (F): 2.77% of total brutto payroll (VBG IT rate) - Internet/Mobilfunk (F): Headcount × 50 EUR/Mon - Allgemeine Marketingkosten (F): 10% of monthly revenue UI: Hover tooltips on all (F) and computed rows showing the formula. SUMME matcher updated for renamed "SUMME Betriebliche Aufwendungen". Co-Authored-By: Claude Opus 4.6 (1M context) --- .../components/slides/FinanzplanSlide.tsx | 32 +++++++++++++++++-- pitch-deck/lib/finanzplan/engine.ts | 23 +++++++++++++ 2 files changed, 53 insertions(+), 2 deletions(-) diff --git a/pitch-deck/components/slides/FinanzplanSlide.tsx b/pitch-deck/components/slides/FinanzplanSlide.tsx index 90188a0..69d8268 100644 --- a/pitch-deck/components/slides/FinanzplanSlide.tsx +++ b/pitch-deck/components/slides/FinanzplanSlide.tsx @@ -51,6 +51,34 @@ function getLabel(row: SheetRow): string { return row.row_label || row.person_name || row.item_name || '—' } +const FORMULA_TOOLTIPS: Record = { + 'Fort-/Weiterbildungskosten (F)': 'Mitarbeiter (ohne Gründer) × 500 EUR/Mon', + 'Fahrzeugkosten (F)': 'Mitarbeiter (ohne Gründer) × 400 EUR/Mon', + 'KFZ-Steuern (F)': 'Mitarbeiter (ohne Gründer) × 50 EUR/Mon', + 'KFZ-Versicherung (F)': 'Mitarbeiter (ohne Gründer) × 500 EUR/Mon', + 'Reisekosten (F)': 'Headcount gesamt × 100 EUR/Mon', + 'Bewirtungskosten (F)': 'Enterprise-Kunden × 200 EUR/Mon', + 'Internet/Mobilfunk (F)': 'Headcount gesamt × 50 EUR/Mon', + 'Serverkosten Cloud (F)': 'Bestandskunden × 100 EUR + 500 EUR Basis', + 'Berufsgenossenschaft (F)': '2,77% der Brutto-Lohnsumme (VBG IT)', + 'Allgemeine Marketingkosten (F)': '10% vom Monatsumsatz', + 'Personalkosten': 'Summe aus Tab Personalkosten', + 'Abschreibungen': 'Summe AfA aus Tab Investitionen', +} + +function LabelWithTooltip({ label }: { label: string }) { + const tooltip = FORMULA_TOOLTIPS[label] + if (!tooltip) return {label} + return ( + + {label} + + {tooltip} + + + ) +} + function getValues(row: SheetRow): Record { return row.values || row.values_total || row.values_brutto || {} } @@ -394,7 +422,7 @@ export default function FinanzplanSlide({ lang, investorId, preferredScenarioId return ( - {label} + {[2026, 2027, 2028, 2029, 2030].map(y => { const v = values[`y${y}`] || 0 @@ -453,7 +481,7 @@ export default function FinanzplanSlide({ lang, investorId, preferredScenarioId
{isEditable && } - {label} + {row.position && ({row.position})} {row.section && [{row.section}]}
diff --git a/pitch-deck/lib/finanzplan/engine.ts b/pitch-deck/lib/finanzplan/engine.ts index 0360d39..4fd6925 100644 --- a/pitch-deck/lib/finanzplan/engine.ts +++ b/pitch-deck/lib/finanzplan/engine.ts @@ -237,6 +237,7 @@ export async function computeFinanzplan(pool: Pool, scenarioId: string): Promise { label: 'KFZ-Versicherung (F)', perUnit: 500, source: hcWithoutFounders }, { label: 'Reisekosten (F)', perUnit: 100, source: headcount }, { label: 'Bewirtungskosten (F)', perUnit: 200, source: enterpriseKunden }, + { label: 'Internet/Mobilfunk (F)', perUnit: 50, source: headcount }, ] for (const fr of formulaRows) { @@ -251,6 +252,28 @@ export async function computeFinanzplan(pool: Pool, scenarioId: string): Promise } } + // Berufsgenossenschaft: 2.77% of total brutto payroll + const bgRow = betrieb.find(r => r.row_label.includes('Berufsgenossenschaft')) + if (bgRow) { + const computed = emptyMonthly() + for (let m = 1; m <= MONTHS; m++) { + computed[`m${m}`] = Math.round((totalBrutto[`m${m}`] || 0) * 0.0277) + } + await pool.query('UPDATE fp_betriebliche_aufwendungen SET values = $1 WHERE id = $2', [JSON.stringify(computed), bgRow.id]) + bgRow.values = computed + } + + // Allgemeine Marketingkosten: 10% of revenue + const marketingRow = betrieb.find(r => r.row_label.includes('Allgemeine Marketingkosten')) + if (marketingRow) { + const computed = emptyMonthly() + for (let m = 1; m <= MONTHS; m++) { + computed[`m${m}`] = Math.round((totalRevenue[`m${m}`] || 0) * 0.10) + } + await pool.query('UPDATE fp_betriebliche_aufwendungen SET values = $1 WHERE id = $2', [JSON.stringify(computed), marketingRow.id]) + marketingRow.values = computed + } + // Serverkosten: Bestandskunden * 100 + 500 Basis const totalKunden = emptyMonthly() for (const row of kundenRows.rows) {