From d01a50a4b14e2c377ab6f7fd5579f462a5dc1db4 Mon Sep 17 00:00:00 2001 From: Benjamin Admin Date: Sat, 18 Apr 2026 13:03:18 +0200 Subject: [PATCH] feat(pitch-deck): formula-based betriebliche rows in Finanzplan engine MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Compute engine now auto-calculates these rows from headcount/customers: - Fort-/Weiterbildungskosten (F): MA (excl. founders) × 500 EUR/Mon - Fahrzeugkosten (F): MA (excl. founders) × 400 EUR/Mon - KFZ-Steuern (F): MA (excl. founders) × 50 EUR/Mon - KFZ-Versicherung (F): MA (excl. founders) × 500 EUR/Mon - Reisekosten (F): Headcount × 100 EUR/Mon - Bewirtungskosten (F): Enterprise-Kunden × 200 EUR/Mon - Serverkosten Cloud (F): Bestandskunden × 100 EUR + 500 EUR Basis Labels marked (F) for formula, (M) for manual in production DB. Gesamtkosten matcher updated for renamed "SUMME Betriebliche Aufwendungen". Co-Authored-By: Claude Opus 4.6 (1M context) --- pitch-deck/lib/finanzplan/engine.ts | 65 ++++++++++++++++++++++++++++- 1 file changed, 63 insertions(+), 2 deletions(-) diff --git a/pitch-deck/lib/finanzplan/engine.ts b/pitch-deck/lib/finanzplan/engine.ts index fd6a385..0360d39 100644 --- a/pitch-deck/lib/finanzplan/engine.ts +++ b/pitch-deck/lib/finanzplan/engine.ts @@ -205,8 +205,69 @@ export async function computeFinanzplan(pool: Pool, scenarioId: string): Promise await pool.query('UPDATE fp_materialaufwand SET values = $1 WHERE id = $2', [JSON.stringify(totalMaterial), matSumme.id]) } - // 6. Betriebliche Aufwendungen — compute sum rows + // 5b. Headcount without founders (for formula-based costs) + const NUM_FOUNDERS = 2 + const hcWithoutFounders = emptyMonthly() + for (let m = 1; m <= MONTHS; m++) { + hcWithoutFounders[`m${m}`] = Math.max(0, headcount[`m${m}`] - NUM_FOUNDERS) + } + + // 5c. Enterprise customers (for Bewirtungskosten) + const kundenRows = await pool.query( + "SELECT segment_name, row_label, values FROM fp_kunden WHERE scenario_id = $1 AND row_label = 'Bestandskunden' ORDER BY sort_order", + [scenarioId] + ) + const enterpriseKunden = emptyMonthly() + for (const row of kundenRows.rows) { + if (row.segment_name?.toLowerCase().includes('enterprise')) { + for (let m = 1; m <= MONTHS; m++) { + enterpriseKunden[`m${m}`] = row.values?.[`m${m}`] || 0 + } + } + } + + // 6. Betriebliche Aufwendungen — compute formula-based rows + sum rows const betrieb = betriebRows.rows as FPBetrieblicheAufwendungen[] + + // Formula-based rows: derive from headcount (excl. founders) or customers + const formulaRows: { label: string; perUnit: number; source: MonthlyValues }[] = [ + { label: 'Fort-/Weiterbildungskosten (F)', perUnit: 500, source: hcWithoutFounders }, + { label: 'Fahrzeugkosten (F)', perUnit: 400, source: hcWithoutFounders }, + { label: 'KFZ-Steuern (F)', perUnit: 50, source: hcWithoutFounders }, + { label: 'KFZ-Versicherung (F)', perUnit: 500, source: hcWithoutFounders }, + { label: 'Reisekosten (F)', perUnit: 100, source: headcount }, + { label: 'Bewirtungskosten (F)', perUnit: 200, source: enterpriseKunden }, + ] + + for (const fr of formulaRows) { + const row = betrieb.find(r => r.row_label === fr.label) + if (row) { + const computed = emptyMonthly() + for (let m = 1; m <= MONTHS; m++) { + computed[`m${m}`] = Math.round((fr.source[`m${m}`] || 0) * fr.perUnit) + } + await pool.query('UPDATE fp_betriebliche_aufwendungen SET values = $1 WHERE id = $2', [JSON.stringify(computed), row.id]) + row.values = computed + } + } + + // Serverkosten: Bestandskunden * 100 + 500 Basis + const totalKunden = emptyMonthly() + for (const row of kundenRows.rows) { + for (let m = 1; m <= MONTHS; m++) { + totalKunden[`m${m}`] += row.values?.[`m${m}`] || 0 + } + } + const serverRow = betrieb.find(r => r.row_label === 'Serverkosten Cloud (F)' || r.row_label === 'Serverkosten (Cloud)') + if (serverRow) { + const computed = emptyMonthly() + for (let m = 1; m <= MONTHS; m++) { + computed[`m${m}`] = Math.round((totalKunden[`m${m}`] || 0) * 100 + 500) + } + await pool.query('UPDATE fp_betriebliche_aufwendungen SET values = $1 WHERE id = $2', [JSON.stringify(computed), serverRow.id]) + serverRow.values = computed + } + // Update Personalkosten row const persBetrieb = betrieb.find(r => r.row_label === 'Personalkosten') if (persBetrieb) { @@ -246,7 +307,7 @@ export async function computeFinanzplan(pool: Pool, scenarioId: string): Promise } // Gesamtkosten - const gesamtBetrieb = betrieb.find(r => r.row_label.includes('Gesamtkosten')) + const gesamtBetrieb = betrieb.find(r => r.row_label.includes('Gesamtkosten') || r.row_label.includes('SUMME Betriebliche')) const totalSonstige = sonstSumme?.values || emptyMonthly() if (gesamtBetrieb) { const g = emptyMonthly()