/** * GuV (Gewinn- und Verlustrechnung) — annual P&L computation * * Computes annual sums, EBIT, taxes (Gewerbesteuer, Körperschaftsteuer) * with Verlustvortrag, and writes tax amounts back to Liquidität. */ 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 Liquidität. * Returns EBIT annual values. */ export async function computeGuV( pool: Pool, scenarioId: string, liquid: FPLiquiditaet[], ctx: GuvContext, ): Promise { 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 Liquidität (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% × 3,5 = 12,25% // Körperschaftsteuer = 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) { verlustvortrag += Math.abs(gewinn) gewerbesteuer[k] = 0 koerperschaftsteuer[k] = 0 steuernGesamt[k] = 0 ergebnisNachSteuern[k] = Math.round(gewinn) } else { // Bis 1 Mio EUR: 100% verrechenbar // Über 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 { 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 }