Überschuss = NUR operativer Cashflow: Einzahlungen: Umsatz + Sonst.Erträge + Anzahlungen (OHNE EK/FK) Auszahlungen: Material + Personal + Sonstige + Steuern (OHNE Kredit) = Operativer Überschuss Kontostand = Vormonat + Operativer Überschuss + Finanzierung Finanzierung = EK + FK - Kreditrückzahlungen (separat) So zeigt der Überschuss die echte operative Performance, die Kapitaleinzahlung erscheint nur im Kontostand. Marketing: 5.000€/Mo ab Jul 2027 (statt 20k) Alle Werte Math.round() — ganzzahlig Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
436 lines
19 KiB
TypeScript
436 lines
19 KiB
TypeScript
/**
|
||
* Finanzplan Compute Engine
|
||
*
|
||
* Dependency order:
|
||
* Personalkosten (independent inputs)
|
||
* Investitionen (independent inputs)
|
||
* Kunden → Umsatzerlöse → Materialaufwand
|
||
* Betriebliche Aufwendungen (needs Personal + Invest)
|
||
* Sonst. betr. Erträge (independent)
|
||
* Liquidität (aggregates all above)
|
||
* GuV (annual summary)
|
||
*/
|
||
|
||
import { Pool } from 'pg'
|
||
import {
|
||
MonthlyValues, AnnualValues, MONTHS, FOUNDING_MONTH,
|
||
emptyMonthly, sumMonthly, annualSums, dateToMonth, monthToDate,
|
||
FPPersonalkosten, FPInvestitionen, FPBetrieblicheAufwendungen,
|
||
FPLiquiditaet, FPComputeResult
|
||
} from './types'
|
||
|
||
// --- Sheet Calculators ---
|
||
|
||
export function computePersonalkosten(positions: FPPersonalkosten[]): FPPersonalkosten[] {
|
||
return positions.map(p => {
|
||
const brutto = emptyMonthly()
|
||
const sozial = emptyMonthly()
|
||
const total = emptyMonthly()
|
||
|
||
if (!p.start_date || !p.brutto_monthly) return { ...p, values_brutto: brutto, values_sozial: sozial, values_total: total }
|
||
|
||
const startDate = new Date(p.start_date)
|
||
const startM = dateToMonth(startDate.getFullYear(), startDate.getMonth() + 1)
|
||
const endM = p.end_date
|
||
? dateToMonth(new Date(p.end_date).getFullYear(), new Date(p.end_date).getMonth() + 1)
|
||
: MONTHS
|
||
|
||
for (let m = Math.max(1, startM); m <= Math.min(MONTHS, endM); m++) {
|
||
const { year } = monthToDate(m)
|
||
const yearsFromStart = year - startDate.getFullYear()
|
||
const raise = Math.pow(1 + (p.annual_raise_pct || 0) / 100, yearsFromStart)
|
||
const monthlyBrutto = Math.round(p.brutto_monthly * raise)
|
||
|
||
brutto[`m${m}`] = monthlyBrutto
|
||
sozial[`m${m}`] = Math.round(monthlyBrutto * (p.ag_sozial_pct || 20.425) / 100)
|
||
total[`m${m}`] = brutto[`m${m}`] + sozial[`m${m}`]
|
||
}
|
||
|
||
return { ...p, values_brutto: brutto, values_sozial: sozial, values_total: total }
|
||
})
|
||
}
|
||
|
||
export function computeInvestitionen(items: FPInvestitionen[]): FPInvestitionen[] {
|
||
return items.map(item => {
|
||
const invest = emptyMonthly()
|
||
const afa = emptyMonthly()
|
||
|
||
if (!item.purchase_date || !item.purchase_amount) return { ...item, values_invest: invest, values_afa: afa }
|
||
|
||
const d = new Date(item.purchase_date)
|
||
const purchaseM = dateToMonth(d.getFullYear(), d.getMonth() + 1)
|
||
|
||
if (purchaseM >= 1 && purchaseM <= MONTHS) {
|
||
invest[`m${purchaseM}`] = item.purchase_amount
|
||
}
|
||
|
||
// AfA (linear depreciation)
|
||
if (item.afa_years && item.afa_years > 0) {
|
||
const afaMonths = item.afa_years * 12
|
||
const monthlyAfa = Math.round(item.purchase_amount / afaMonths)
|
||
for (let m = purchaseM; m < purchaseM + afaMonths && m <= MONTHS; m++) {
|
||
if (m >= 1) afa[`m${m}`] = monthlyAfa
|
||
}
|
||
} else {
|
||
// GWG: full depreciation in purchase month
|
||
if (purchaseM >= 1 && purchaseM <= MONTHS) {
|
||
afa[`m${purchaseM}`] = item.purchase_amount
|
||
}
|
||
}
|
||
|
||
return { ...item, values_invest: invest, values_afa: afa }
|
||
})
|
||
}
|
||
|
||
function sumRows(rows: { values: MonthlyValues }[]): MonthlyValues {
|
||
const result = emptyMonthly()
|
||
for (const row of rows) {
|
||
for (let m = 1; m <= MONTHS; m++) {
|
||
result[`m${m}`] += row.values[`m${m}`] || 0
|
||
}
|
||
}
|
||
return result
|
||
}
|
||
|
||
function sumField(rows: { [key: string]: MonthlyValues }[], field: string): MonthlyValues {
|
||
const result = emptyMonthly()
|
||
for (const row of rows) {
|
||
const v = row[field] as MonthlyValues
|
||
if (!v) continue
|
||
for (let m = 1; m <= MONTHS; m++) {
|
||
result[`m${m}`] += v[`m${m}`] || 0
|
||
}
|
||
}
|
||
return result
|
||
}
|
||
|
||
// --- Main Engine ---
|
||
|
||
export async function computeFinanzplan(pool: Pool, scenarioId: string): Promise<FPComputeResult> {
|
||
// 1. Load all editable data from DB
|
||
const [
|
||
personalRows,
|
||
investRows,
|
||
betriebRows,
|
||
liquidRows,
|
||
kundenSummary,
|
||
umsatzRows,
|
||
materialRows,
|
||
] = await Promise.all([
|
||
pool.query('SELECT * FROM fp_personalkosten WHERE scenario_id = $1 ORDER BY sort_order', [scenarioId]),
|
||
pool.query('SELECT * FROM fp_investitionen WHERE scenario_id = $1 ORDER BY sort_order', [scenarioId]),
|
||
pool.query('SELECT * FROM fp_betriebliche_aufwendungen WHERE scenario_id = $1 ORDER BY sort_order', [scenarioId]),
|
||
pool.query('SELECT * FROM fp_liquiditaet WHERE scenario_id = $1 ORDER BY sort_order', [scenarioId]),
|
||
pool.query('SELECT * FROM fp_kunden_summary WHERE scenario_id = $1 ORDER BY sort_order', [scenarioId]),
|
||
pool.query('SELECT * FROM fp_umsatzerloese WHERE scenario_id = $1 ORDER BY sort_order', [scenarioId]),
|
||
pool.query('SELECT * FROM fp_materialaufwand WHERE scenario_id = $1 ORDER BY sort_order', [scenarioId]),
|
||
])
|
||
|
||
// 2. Compute Personalkosten
|
||
const personal = computePersonalkosten(personalRows.rows as FPPersonalkosten[])
|
||
const totalBrutto = sumField(personal as any, 'values_brutto')
|
||
const totalSozial = sumField(personal as any, 'values_sozial')
|
||
const totalPersonal = sumField(personal as any, 'values_total')
|
||
const headcount = emptyMonthly()
|
||
for (let m = 1; m <= MONTHS; m++) {
|
||
headcount[`m${m}`] = personal.filter(p => (p.values_total[`m${m}`] || 0) > 0).length
|
||
}
|
||
|
||
// Write computed values back to DB
|
||
for (const p of personal) {
|
||
await pool.query(
|
||
'UPDATE fp_personalkosten SET values_brutto = $1, values_sozial = $2, values_total = $3 WHERE id = $4',
|
||
[JSON.stringify(p.values_brutto), JSON.stringify(p.values_sozial), JSON.stringify(p.values_total), p.id]
|
||
)
|
||
}
|
||
|
||
// 3. Compute Investitionen
|
||
const invest = computeInvestitionen(investRows.rows as FPInvestitionen[])
|
||
const totalInvest = sumField(invest as any, 'values_invest')
|
||
const totalAfa = sumField(invest as any, 'values_afa')
|
||
|
||
for (const i of invest) {
|
||
await pool.query(
|
||
'UPDATE fp_investitionen SET values_invest = $1, values_afa = $2 WHERE id = $3',
|
||
[JSON.stringify(i.values_invest), JSON.stringify(i.values_afa), i.id]
|
||
)
|
||
}
|
||
|
||
// 4. Umsatzerlöse (quantity × price)
|
||
const prices = (umsatzRows.rows as FPUmsatzerloese[]).filter(r => r.section === 'price')
|
||
const quantities = (umsatzRows.rows as FPUmsatzerloese[]).filter(r => r.section === 'quantity')
|
||
const revenueRows = (umsatzRows.rows as FPUmsatzerloese[]).filter(r => r.section === 'revenue')
|
||
const totalRevenue = emptyMonthly()
|
||
|
||
// Revenue = quantity × price for each module
|
||
for (const rev of revenueRows) {
|
||
if (rev.row_label === 'GESAMTUMSATZ') continue
|
||
const qty = quantities.find(q => q.row_label === rev.row_label)
|
||
const price = prices.find(p => p.row_label === rev.row_label)
|
||
if (qty && price) {
|
||
for (let m = 1; m <= MONTHS; m++) {
|
||
const v = (qty.values[`m${m}`] || 0) * (price.values[`m${m}`] || 0)
|
||
rev.values[`m${m}`] = Math.round(v)
|
||
totalRevenue[`m${m}`] += rev.values[`m${m}`]
|
||
}
|
||
await pool.query('UPDATE fp_umsatzerloese SET values = $1 WHERE id = $2', [JSON.stringify(rev.values), rev.id])
|
||
}
|
||
}
|
||
// Update GESAMTUMSATZ
|
||
const gesamtUmsatz = revenueRows.find(r => r.row_label === 'GESAMTUMSATZ')
|
||
if (gesamtUmsatz) {
|
||
await pool.query('UPDATE fp_umsatzerloese SET values = $1 WHERE id = $2', [JSON.stringify(totalRevenue), gesamtUmsatz.id])
|
||
}
|
||
|
||
// 5. Materialaufwand (quantity × unit_cost) — simplified
|
||
const matCosts = (materialRows.rows as FPMaterialaufwand[]).filter(r => r.section === 'cost')
|
||
const matUnitCosts = (materialRows.rows as FPMaterialaufwand[]).filter(r => r.section === 'unit_cost')
|
||
const totalMaterial = emptyMonthly()
|
||
|
||
for (const cost of matCosts) {
|
||
if (cost.row_label === 'SUMME') continue
|
||
const uc = matUnitCosts.find(u => u.row_label === cost.row_label)
|
||
const qty = quantities.find(q => q.row_label === cost.row_label)
|
||
if (uc && qty) {
|
||
for (let m = 1; m <= MONTHS; m++) {
|
||
const v = (qty.values[`m${m}`] || 0) * (uc.values[`m${m}`] || 0)
|
||
cost.values[`m${m}`] = Math.round(v)
|
||
totalMaterial[`m${m}`] += cost.values[`m${m}`]
|
||
}
|
||
await pool.query('UPDATE fp_materialaufwand SET values = $1 WHERE id = $2', [JSON.stringify(cost.values), cost.id])
|
||
}
|
||
}
|
||
const matSumme = matCosts.find(r => r.row_label === 'SUMME')
|
||
if (matSumme) {
|
||
await pool.query('UPDATE fp_materialaufwand SET values = $1 WHERE id = $2', [JSON.stringify(totalMaterial), matSumme.id])
|
||
}
|
||
|
||
// 6. Betriebliche Aufwendungen — compute sum rows
|
||
const betrieb = betriebRows.rows as FPBetrieblicheAufwendungen[]
|
||
// Update Personalkosten row
|
||
const persBetrieb = betrieb.find(r => r.row_label === 'Personalkosten')
|
||
if (persBetrieb) {
|
||
await pool.query('UPDATE fp_betriebliche_aufwendungen SET values = $1 WHERE id = $2', [JSON.stringify(totalPersonal), persBetrieb.id])
|
||
persBetrieb.values = totalPersonal
|
||
}
|
||
// Update Abschreibungen row
|
||
const abrBetrieb = betrieb.find(r => r.row_label === 'Abschreibungen')
|
||
if (abrBetrieb) {
|
||
await pool.query('UPDATE fp_betriebliche_aufwendungen SET values = $1 WHERE id = $2', [JSON.stringify(totalAfa), abrBetrieb.id])
|
||
abrBetrieb.values = totalAfa
|
||
}
|
||
|
||
// Compute category sums
|
||
const categories = ['steuern', 'versicherungen', 'besondere', 'marketing', 'sonstige']
|
||
for (const cat of categories) {
|
||
const sumRow = betrieb.find(r => r.category === cat && r.is_sum_row)
|
||
const detailRows = betrieb.filter(r => r.category === cat && !r.is_sum_row)
|
||
if (sumRow && detailRows.length > 0) {
|
||
const s = sumRows(detailRows)
|
||
await pool.query('UPDATE fp_betriebliche_aufwendungen SET values = $1 WHERE id = $2', [JSON.stringify(s), sumRow.id])
|
||
sumRow.values = s
|
||
}
|
||
}
|
||
|
||
// Summe sonstige (ohne Personal, Abschreibungen)
|
||
const sonstSumme = betrieb.find(r => r.row_label.includes('Summe sonstige'))
|
||
if (sonstSumme) {
|
||
const nonPersonNonAbr = betrieb.filter(r =>
|
||
r.row_label !== 'Personalkosten' && r.row_label !== 'Abschreibungen' &&
|
||
!r.row_label.includes('Summe sonstige') && !r.row_label.includes('Gesamtkosten') &&
|
||
!r.is_sum_row && r.category !== 'personal' && r.category !== 'abschreibungen'
|
||
)
|
||
const s = sumRows(nonPersonNonAbr)
|
||
await pool.query('UPDATE fp_betriebliche_aufwendungen SET values = $1 WHERE id = $2', [JSON.stringify(s), sonstSumme.id])
|
||
sonstSumme.values = s
|
||
}
|
||
|
||
// Gesamtkosten
|
||
const gesamtBetrieb = betrieb.find(r => r.row_label.includes('Gesamtkosten'))
|
||
const totalSonstige = sonstSumme?.values || emptyMonthly()
|
||
if (gesamtBetrieb) {
|
||
const g = emptyMonthly()
|
||
for (let m = 1; m <= MONTHS; m++) {
|
||
g[`m${m}`] = (totalPersonal[`m${m}`] || 0) + (totalAfa[`m${m}`] || 0) + (totalSonstige[`m${m}`] || 0)
|
||
}
|
||
await pool.query('UPDATE fp_betriebliche_aufwendungen SET values = $1 WHERE id = $2', [JSON.stringify(g), gesamtBetrieb.id])
|
||
gesamtBetrieb.values = g
|
||
}
|
||
|
||
// 7. Liquidität
|
||
const liquid = liquidRows.rows as FPLiquiditaet[]
|
||
const findLiq = (label: string) => liquid.find(r => r.row_label === label)
|
||
|
||
// Computed rows
|
||
const liqUmsatz = findLiq('Umsatzerloese')
|
||
if (liqUmsatz) {
|
||
await pool.query('UPDATE fp_liquiditaet SET values = $1 WHERE id = $2', [JSON.stringify(totalRevenue), liqUmsatz.id])
|
||
liqUmsatz.values = totalRevenue
|
||
}
|
||
const liqMaterial = findLiq('Materialaufwand')
|
||
if (liqMaterial) {
|
||
await pool.query('UPDATE fp_liquiditaet SET values = $1 WHERE id = $2', [JSON.stringify(totalMaterial), liqMaterial.id])
|
||
liqMaterial.values = totalMaterial
|
||
}
|
||
const liqPersonal = findLiq('Personalkosten')
|
||
if (liqPersonal) {
|
||
await pool.query('UPDATE fp_liquiditaet SET values = $1 WHERE id = $2', [JSON.stringify(totalPersonal), liqPersonal.id])
|
||
liqPersonal.values = totalPersonal
|
||
}
|
||
const liqSonstige = findLiq('Sonstige Kosten')
|
||
if (liqSonstige) {
|
||
await pool.query('UPDATE fp_liquiditaet SET values = $1 WHERE id = $2', [JSON.stringify(totalSonstige), liqSonstige.id])
|
||
liqSonstige.values = totalSonstige
|
||
}
|
||
const liqInvest = findLiq('Investitionen')
|
||
if (liqInvest) {
|
||
await pool.query('UPDATE fp_liquiditaet SET values = $1 WHERE id = $2', [JSON.stringify(totalInvest), liqInvest.id])
|
||
liqInvest.values = totalInvest
|
||
}
|
||
|
||
// Compute sums and rolling balance
|
||
// WICHTIG: Überschuss = nur operativer Cashflow (ohne Kapitaleinzahlungen)
|
||
const sumEin = findLiq('Summe EINZAHLUNGEN')
|
||
const sumAus = findLiq('Summe AUSZAHLUNGEN')
|
||
const uebVorInv = findLiq('UEBERSCHUSS VOR INVESTITIONEN')
|
||
const uebVorEnt = findLiq('UEBERSCHUSS VOR ENTNAHMEN')
|
||
const ueberschuss = findLiq('UEBERSCHUSS')
|
||
const kontostand = findLiq('Kontostand zu Beginn des Monats')
|
||
const liquiditaet = findLiq('LIQUIDITAET')
|
||
|
||
// Operative Einzahlungen (OHNE Eigenkapital und Fremdkapital)
|
||
const einzahlungenOperativ = ['Umsatzerloese', 'Sonst. betriebl. Ertraege', 'Anzahlungen']
|
||
// Finanzierung (separat)
|
||
const finanzierung = ['Neuer Eigenkapitalzugang', 'Erhaltenes Fremdkapital']
|
||
// Operative Auszahlungen (OHNE Kreditrückzahlungen)
|
||
const auszahlungenOperativ = ['Materialaufwand', 'Personalkosten', 'Sonstige Kosten', 'Umsatzsteuer', 'Gewerbesteuer', 'Koerperschaftsteuer']
|
||
const finanzAuszahlungen = ['Kreditrueckzahlungen']
|
||
|
||
// Summe EINZAHLUNGEN = nur operativ (für die Zeile "Summe Einzahlungen")
|
||
if (sumEin) {
|
||
const s = emptyMonthly()
|
||
for (const label of einzahlungenOperativ) {
|
||
const row = findLiq(label)
|
||
if (row) for (let m = 1; m <= MONTHS; m++) s[`m${m}`] += Math.round(row.values[`m${m}`] || 0)
|
||
}
|
||
await pool.query('UPDATE fp_liquiditaet SET values = $1 WHERE id = $2', [JSON.stringify(s), sumEin.id])
|
||
sumEin.values = s
|
||
}
|
||
|
||
// Summe AUSZAHLUNGEN = nur operativ
|
||
if (sumAus) {
|
||
const s = emptyMonthly()
|
||
for (const label of auszahlungenOperativ) {
|
||
const row = findLiq(label)
|
||
if (row) for (let m = 1; m <= MONTHS; m++) s[`m${m}`] += Math.round(row.values[`m${m}`] || 0)
|
||
}
|
||
await pool.query('UPDATE fp_liquiditaet SET values = $1 WHERE id = $2', [JSON.stringify(s), sumAus.id])
|
||
sumAus.values = s
|
||
}
|
||
|
||
// OPERATIVER ÜBERSCHUSS VOR INVESTITIONEN = operative Einzahlungen - operative Auszahlungen
|
||
if (uebVorInv && sumEin && sumAus) {
|
||
const s = emptyMonthly()
|
||
for (let m = 1; m <= MONTHS; m++) s[`m${m}`] = Math.round((sumEin.values[`m${m}`] || 0) - (sumAus.values[`m${m}`] || 0))
|
||
await pool.query('UPDATE fp_liquiditaet SET values = $1 WHERE id = $2', [JSON.stringify(s), uebVorInv.id])
|
||
uebVorInv.values = s
|
||
}
|
||
|
||
// ÜBERSCHUSS VOR ENTNAHMEN = Operativer Überschuss - Investitionen
|
||
if (uebVorEnt && uebVorInv && liqInvest) {
|
||
const s = emptyMonthly()
|
||
for (let m = 1; m <= MONTHS; m++) s[`m${m}`] = Math.round((uebVorInv.values[`m${m}`] || 0) - (liqInvest.values[`m${m}`] || 0))
|
||
await pool.query('UPDATE fp_liquiditaet SET values = $1 WHERE id = $2', [JSON.stringify(s), uebVorEnt.id])
|
||
uebVorEnt.values = s
|
||
}
|
||
|
||
// ÜBERSCHUSS = Überschuss vor Entnahmen - Entnahmen (immer noch rein operativ)
|
||
const entnahmen = findLiq('Kapitalentnahmen/Ausschuettungen')
|
||
if (ueberschuss && uebVorEnt && entnahmen) {
|
||
const s = emptyMonthly()
|
||
for (let m = 1; m <= MONTHS; m++) s[`m${m}`] = Math.round((uebVorEnt.values[`m${m}`] || 0) - (entnahmen.values[`m${m}`] || 0))
|
||
await pool.query('UPDATE fp_liquiditaet SET values = $1 WHERE id = $2', [JSON.stringify(s), ueberschuss.id])
|
||
ueberschuss.values = s
|
||
}
|
||
|
||
// Rolling Kontostand: Vormonat + Operativer Überschuss + Finanzierung
|
||
// Finanzierung = Eigenkapital + Fremdkapital - Kreditrückzahlungen
|
||
if (kontostand && liquiditaet && ueberschuss) {
|
||
// Berechne monatliche Finanzierungs-Cashflows
|
||
const finCF = emptyMonthly()
|
||
for (const label of finanzierung) {
|
||
const row = findLiq(label)
|
||
if (row) for (let m = 1; m <= MONTHS; m++) finCF[`m${m}`] += Math.round(row.values[`m${m}`] || 0)
|
||
}
|
||
for (const label of finanzAuszahlungen) {
|
||
const row = findLiq(label)
|
||
if (row) for (let m = 1; m <= MONTHS; m++) finCF[`m${m}`] -= Math.round(row.values[`m${m}`] || 0)
|
||
}
|
||
|
||
const ks = emptyMonthly()
|
||
const lq = emptyMonthly()
|
||
for (let m = 1; m <= MONTHS; m++) {
|
||
ks[`m${m}`] = m === 1 ? 0 : Math.round(lq[`m${m - 1}`])
|
||
// LIQUIDITÄT = Kontostand + Operativer Überschuss + Finanzierung
|
||
lq[`m${m}`] = Math.round(ks[`m${m}`] + (ueberschuss.values[`m${m}`] || 0) + (finCF[`m${m}`] || 0))
|
||
}
|
||
await pool.query('UPDATE fp_liquiditaet SET values = $1 WHERE id = $2', [JSON.stringify(ks), kontostand.id])
|
||
await pool.query('UPDATE fp_liquiditaet SET values = $1 WHERE id = $2', [JSON.stringify(lq), liquiditaet.id])
|
||
kontostand.values = ks
|
||
liquiditaet.values = lq
|
||
}
|
||
|
||
// 8. GuV — compute annual values
|
||
const guv: AnnualValues[] = []
|
||
const umsatzAnnual = annualSums(totalRevenue)
|
||
const materialAnnual = annualSums(totalMaterial)
|
||
const personalBruttoAnnual = annualSums(totalBrutto)
|
||
const personalSozialAnnual = annualSums(totalSozial)
|
||
const personalAnnual = annualSums(totalPersonal)
|
||
const afaAnnual = annualSums(totalAfa)
|
||
const sonstigeAnnual = annualSums(totalSonstige)
|
||
|
||
// Write GuV rows
|
||
const guvUpdates: { label: string; values: AnnualValues }[] = [
|
||
{ label: 'Umsatzerloese', values: umsatzAnnual },
|
||
{ label: 'Gesamtleistung', values: umsatzAnnual },
|
||
{ label: 'Summe Materialaufwand', values: materialAnnual },
|
||
{ label: 'Loehne und Gehaelter', values: personalBruttoAnnual },
|
||
{ label: 'Soziale Abgaben', values: personalSozialAnnual },
|
||
{ label: 'Summe Personalaufwand', values: personalAnnual },
|
||
{ label: 'Abschreibungen', values: afaAnnual },
|
||
{ label: 'Sonst. betriebl. Aufwendungen', values: sonstigeAnnual },
|
||
]
|
||
|
||
for (const { label, values } of guvUpdates) {
|
||
await pool.query(
|
||
'UPDATE fp_guv SET values = $1 WHERE scenario_id = $2 AND row_label = $3',
|
||
[JSON.stringify(values), scenarioId, label]
|
||
)
|
||
}
|
||
|
||
// EBIT
|
||
const ebit: AnnualValues = {}
|
||
for (let y = 2026; y <= 2030; y++) {
|
||
const k = `y${y}`
|
||
ebit[k] = (umsatzAnnual[k] || 0) - (materialAnnual[k] || 0) - (personalAnnual[k] || 0) - (afaAnnual[k] || 0) - (sonstigeAnnual[k] || 0)
|
||
}
|
||
await pool.query('UPDATE fp_guv SET values = $1 WHERE scenario_id = $2 AND row_label = $3', [JSON.stringify(ebit), scenarioId, 'EBIT'])
|
||
await pool.query('UPDATE fp_guv SET values = $1 WHERE scenario_id = $2 AND row_label = $3', [JSON.stringify(ebit), scenarioId, 'Ergebnis nach Steuern'])
|
||
await pool.query('UPDATE fp_guv SET values = $1 WHERE scenario_id = $2 AND row_label = $3', [JSON.stringify(ebit), scenarioId, 'Jahresueberschuss'])
|
||
|
||
return {
|
||
personalkosten: { total_brutto: totalBrutto, total_sozial: totalSozial, total: totalPersonal, positions: personal, headcount },
|
||
investitionen: { total_invest: totalInvest, total_afa: totalAfa, items: invest },
|
||
umsatzerloese: { total: totalRevenue },
|
||
materialaufwand: { total: totalMaterial },
|
||
betriebliche: { total_sonstige: totalSonstige, total_gesamt: gesamtBetrieb?.values || emptyMonthly() },
|
||
liquiditaet: { rows: liquid, endstand: liquiditaet?.values || emptyMonthly() },
|
||
guv: [ebit],
|
||
}
|
||
}
|
||
|
||
// Import to fix type errors
|
||
type FPUmsatzerloese = import('./types').FPUmsatzerloese
|
||
type FPMaterialaufwand = import('./types').FPMaterialaufwand
|