feat(pitch-deck): formula-based betriebliche rows in Finanzplan engine

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) <noreply@anthropic.com>
This commit is contained in:
Benjamin Admin
2026-04-18 13:03:18 +02:00
parent 2d61911d98
commit d01a50a4b1

View File

@@ -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()