Files
breakpilot-core/pitch-deck/lib/finanzplan/engine-guv.ts
Benjamin Admin 28aa74b4b0
Some checks failed
Build pitch-deck / build-push-deploy (push) Failing after 1m13s
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-consent (push) Successful in 49s
CI / test-python-voice (push) Successful in 38s
CI / test-bqas (push) Successful in 31s
Merge remote-tracking branch 'gitea/main'
# Conflicts:
#	pitch-deck/components/slides/MilestonesSlide.tsx
#	pitch-deck/lib/finanzplan/engine.ts
2026-04-27 13:14:54 +02:00

169 lines
6.3 KiB
TypeScript

/**
* GuV (Gewinn- und Verlustrechnung) — annual P&L computation
*
* Computes annual sums, EBIT, taxes (Gewerbesteuer, Koerperschaftsteuer)
* with Verlustvortrag, and writes tax amounts back to Liquiditaet.
*/
import { Pool } from 'pg'
import {
MonthlyValues, AnnualValues, MONTHS,
emptyMonthly, annualSums,
FPLiquiditaet,
} from './types'
export interface GuvContext {
totalRevenue: MonthlyValues
totalMaterial: MonthlyValues
totalBrutto: MonthlyValues
totalSozial: MonthlyValues
totalPersonal: MonthlyValues
totalAfa: MonthlyValues
totalSonstige: MonthlyValues
}
/**
* Compute GuV annual values, taxes, and write tax values to Liquiditaet.
* Returns EBIT annual values.
*/
export async function computeGuV(
pool: Pool,
scenarioId: string,
liquid: FPLiquiditaet[],
ctx: GuvContext,
): Promise<AnnualValues[]> {
const findLiq = (label: string) => liquid.find(r => r.row_label === label)
const umsatzAnnual = annualSums(ctx.totalRevenue)
const materialAnnual = annualSums(ctx.totalMaterial)
const personalBruttoAnnual = annualSums(ctx.totalBrutto)
const personalSozialAnnual = annualSums(ctx.totalSozial)
const personalAnnual = annualSums(ctx.totalPersonal)
const afaAnnual = annualSums(ctx.totalAfa)
const sonstigeAnnual = annualSums(ctx.totalSonstige)
// Rohergebnis = Gesamtleistung - Materialaufwand
const rohergebnis: AnnualValues = {}
for (let y = 2026; y <= 2030; y++) {
const k = `y${y}`
rohergebnis[k] = Math.round((umsatzAnnual[k] || 0) - (materialAnnual[k] || 0))
}
const guvUpdates: { label: string; values: AnnualValues }[] = [
{ label: 'Umsatzerlöse', values: umsatzAnnual },
{ label: 'Gesamtleistung', values: umsatzAnnual },
{ label: 'Summe Materialaufwand', values: materialAnnual },
{ label: 'Rohergebnis', values: rohergebnis },
{ label: 'Löhne und Gehälter', 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 (Betriebsergebnis)
const ebit: AnnualValues = {}
for (let y = 2026; y <= 2030; y++) {
const k = `y${y}`
ebit[k] = Math.round((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'])
// Tax computation with Verlustvortrag
const { gewerbesteuer, koerperschaftsteuer, steuernGesamt, ergebnisNachSteuern } = computeTaxes(ebit)
await pool.query('UPDATE fp_guv SET values = $1 WHERE scenario_id = $2 AND row_label = $3', [JSON.stringify(gewerbesteuer), scenarioId, 'Gewerbesteuer'])
await pool.query('UPDATE fp_guv SET values = $1 WHERE scenario_id = $2 AND row_label = $3', [JSON.stringify(koerperschaftsteuer), scenarioId, 'Körperschaftssteuer'])
await pool.query('UPDATE fp_guv SET values = $1 WHERE scenario_id = $2 AND row_label = $3', [JSON.stringify(steuernGesamt), scenarioId, 'Steuern gesamt'])
await pool.query('UPDATE fp_guv SET values = $1 WHERE scenario_id = $2 AND row_label = $3', [JSON.stringify(ergebnisNachSteuern), scenarioId, 'Ergebnis nach Steuern'])
await pool.query('UPDATE fp_guv SET values = $1 WHERE scenario_id = $2 AND row_label = $3', [JSON.stringify(ergebnisNachSteuern), scenarioId, 'Jahresüberschuss'])
// Write taxes to Liquiditaet (monthly = 1/12 of annual amount)
await writeTaxToLiquiditaet(pool, findLiq('Gewerbesteuer'), gewerbesteuer)
await writeTaxToLiquiditaet(pool, findLiq('Körperschaftsteuer'), koerperschaftsteuer)
return [ebit]
}
// --- Tax helpers ---
// Stockach 78333: Hebesatz 350%
// Gewerbesteuer = 3,5% x 3,5 = 12,25%
// Koerperschaftsteuer = 15% + 5,5% Soli = 15,825%
const GEWERBESTEUER_RATE = 0.035 * 3.5 // 12,25%
const KOERPERSCHAFTSTEUER_RATE = 0.15 * 1.055 // 15,825% (inkl. Soli)
function computeTaxes(ebit: AnnualValues) {
const gewerbesteuer: AnnualValues = {}
const koerperschaftsteuer: AnnualValues = {}
const steuernGesamt: AnnualValues = {}
const ergebnisNachSteuern: AnnualValues = {}
let verlustvortrag = 0
for (let y = 2026; y <= 2030; y++) {
const k = `y${y}`
const gewinn = ebit[k] || 0
if (gewinn <= 0) {
// Verlust: keine Steuern, Verlustvortrag aufbauen
verlustvortrag += Math.abs(gewinn)
gewerbesteuer[k] = 0
koerperschaftsteuer[k] = 0
steuernGesamt[k] = 0
ergebnisNachSteuern[k] = Math.round(gewinn)
} else {
// Gewinn: Verlustvortrag verrechnen
// Bis 1 Mio EUR: 100% verrechenbar
// Ueber 1 Mio EUR: nur 60% verrechenbar (Mindestbesteuerung)
let verrechenbar = 0
if (verlustvortrag > 0) {
if (gewinn <= 1000000) {
verrechenbar = Math.min(verlustvortrag, gewinn)
} else {
verrechenbar = Math.min(verlustvortrag, 1000000 + (gewinn - 1000000) * 0.6)
}
verlustvortrag -= verrechenbar
}
const zuVersteuern = Math.max(0, gewinn - verrechenbar)
const gst = Math.round(zuVersteuern * GEWERBESTEUER_RATE)
const kst = Math.round(zuVersteuern * KOERPERSCHAFTSTEUER_RATE)
gewerbesteuer[k] = gst
koerperschaftsteuer[k] = kst
steuernGesamt[k] = gst + kst
ergebnisNachSteuern[k] = Math.round(gewinn - gst - kst)
}
}
return { gewerbesteuer, koerperschaftsteuer, steuernGesamt, ergebnisNachSteuern }
}
async function writeTaxToLiquiditaet(
pool: Pool,
liqRow: FPLiquiditaet | undefined,
annualTax: AnnualValues,
): Promise<void> {
if (!liqRow) return
const v = emptyMonthly()
for (let y = 2026; y <= 2030; y++) {
const jahresBetrag = annualTax[`y${y}`] || 0
if (jahresBetrag > 0) {
const monatlich = Math.round(jahresBetrag / 12)
const startM = (y - 2026) * 12 + 1
for (let m = startM; m <= startM + 11 && m <= MONTHS; m++) {
v[`m${m}`] = monatlich
}
}
}
await pool.query('UPDATE fp_liquiditaet SET values = $1 WHERE id = $2', [JSON.stringify(v), liqRow.id])
liqRow.values = v
}