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:
@@ -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()
|
||||
|
||||
Reference in New Issue
Block a user