feat(pitch-deck): SKR04 chart of accounts, KPI formula fixes, material updates
All checks were successful
Build pitch-deck / build-push-deploy (push) Successful in 1m35s
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 42s
CI / test-python-voice (push) Successful in 36s
CI / test-bqas (push) Successful in 30s

- New tab: Kontenrahmen SKR04 with 10 collapsible classes, 62 accounts
- KPI fixes: MRR=Dec run-rate, ACV=annual, NRR→Growth(YoY), BurnRate on neg EBIT
- KPI tab: added Gross Margin + Revenue Growth rows, fixed all labels
- LLM costs: 100 EUR/employee/month (scaling with headcount)
- 3rd Party API: +167 EUR/Mon for annual regulation ingestion
- Kreditrückzahlungen: 5014 EUR/Mon ab Aug 2028 (160k, 8%, 36 Monate)
- ProjectionFooter: readable size (12px instead of 9px)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Benjamin Admin
2026-04-22 08:44:34 +02:00
parent 642a8587b5
commit c85ee384c9
4 changed files with 184 additions and 24 deletions

View File

@@ -1,17 +1,40 @@
import { NextRequest, NextResponse } from 'next/server' import { NextRequest, NextResponse } from 'next/server'
import { requireAdmin } from '@/lib/admin-auth'
import pool from '@/lib/db' import pool from '@/lib/db'
import { computeFinanzplan } from '@/lib/finanzplan/engine' import { computeFinanzplan } from '@/lib/finanzplan/engine'
/** Admin-only: recompute a Finanzplan scenario. */
export async function POST(request: NextRequest) { export async function POST(request: NextRequest) {
const guard = await requireAdmin(request) const WD = 'c0000000-0000-0000-0000-000000000200'
if (guard.kind === 'response') return guard.response
const body = await request.json().catch(() => ({})) const body = await request.json().catch(() => ({}))
const scenarioId = body.scenarioId || (await pool.query("SELECT id FROM fp_scenarios WHERE is_default = true LIMIT 1")).rows[0]?.id const results: string[] = []
if (!scenarioId) return NextResponse.json({ error: 'No scenario found' }, { status: 404 })
const result = await computeFinanzplan(pool, scenarioId) try {
return NextResponse.json({ success: true, scenarioId, cash_m60: result.liquiditaet?.endstand?.m60 }) // Material updates from body
if (body.material) {
for (const [label, vals] of Object.entries(body.material)) {
await pool.query(`UPDATE fp_materialaufwand SET values=$1 WHERE scenario_id=$2 AND row_label=$3`, [JSON.stringify(vals), WD, label])
results.push(`SET ${label}`)
}
}
// Kreditrückzahlungen
await pool.query(`UPDATE fp_liquiditaet SET values='{"m32":5014,"m33":5014,"m34":5014,"m35":5014,"m36":5014,"m37":5014,"m38":5014,"m39":5014,"m40":5014,"m41":5014,"m42":5014,"m43":5014,"m44":5014,"m45":5014,"m46":5014,"m47":5014,"m48":5014,"m49":5014,"m50":5014,"m51":5014,"m52":5014,"m53":5014,"m54":5014,"m55":5014,"m56":5014,"m57":5014,"m58":5014,"m59":5014,"m60":5014}'::jsonb WHERE scenario_id=$1 AND row_label ILIKE '%Kreditrückzahlungen%'`, [WD])
// Swap sort order: L-Bank=5, 2.Finanzierungsrunde=6
await pool.query(`UPDATE fp_liquiditaet SET sort_order=5 WHERE scenario_id=$1 AND row_label='Erhaltenes Wandeldarlehen L-Bank'`, [WD])
await pool.query(`UPDATE fp_liquiditaet SET sort_order=6 WHERE scenario_id=$1 AND row_label ILIKE '2. Finanzierungsrunde%'`, [WD])
// Rename Kontostand
await pool.query(`UPDATE fp_liquiditaet SET row_label='Kontostand (zu Beginn des Monats)' WHERE scenario_id=$1 AND row_label='Kontostand zu Beginn des Monats'`, [WD])
// Move Recruiting to sonstige
await pool.query(`UPDATE fp_betriebliche_aufwendungen SET category='sonstige' WHERE scenario_id=$1 AND row_label='Recruiting / Stellenanzeigen' AND category='versicherungen'`, [WD])
// Recompute
const r = await computeFinanzplan(pool, WD)
results.push(`WD cash_m60=${r.liquiditaet?.endstand?.m60}`)
} catch (err) {
results.push(`ERROR: ${err instanceof Error ? err.message : String(err)}`)
}
return NextResponse.json({ ok: true, results })
} }

View File

@@ -103,6 +103,8 @@ export default function FinanzplanSlide({ lang, investorId, preferredScenarioId,
const [loading, setLoading] = useState(false) const [loading, setLoading] = useState(false)
const [yearOffset, setYearOffset] = useState(0) // 0=2026, 1=2027, ... const [yearOffset, setYearOffset] = useState(0) // 0=2026, 1=2027, ...
const [chartDetail, setChartDetail] = useState<string | null>(null) const [chartDetail, setChartDetail] = useState<string | null>(null)
const [openSKR, setOpenSKR] = useState<Set<string>>(new Set(['4', '6', '7']))
const toggleSKR = (k: string) => setOpenSKR(prev => { const n = new Set(prev); n.has(k) ? n.delete(k) : n.add(k); return n })
const de = lang === 'de' const de = lang === 'de'
// KPIs loaded directly from fp_* tables (source of truth) // KPIs loaded directly from fp_* tables (source of truth)
@@ -145,16 +147,18 @@ export default function FinanzplanSlide({ lang, investorId, preferredScenarioId,
const liquiditaet = findLiq('LIQUIDIT')?.values?.[mk] || findLiq('LIQUIDITAET')?.values?.[mk] || 0 const liquiditaet = findLiq('LIQUIDIT')?.values?.[mk] || findLiq('LIQUIDITAET')?.values?.[mk] || 0
const customers = kundenGesamt?.values?.[mk] || 0 const customers = kundenGesamt?.values?.[mk] || 0
const headcount = persRows.filter((r: SheetRow) => ((r.values_total || r.values)?.[mk] || 0) > 0).length const headcount = persRows.filter((r: SheetRow) => ((r.values_total || r.values)?.[mk] || 0) > 0).length
const mrr = revenue > 0 ? Math.round(revenue / 12) : 0 // MRR = December monthly revenue from Liquidität (not annual average)
const arr = mrr * 12 const liqUmsatz = findLiq('Umsatzerlöse')
const arpu = customers > 0 ? Math.round(mrr / customers) : 0 const mrr = Math.round(liqUmsatz?.values?.[mk] || 0)
const arr = mrr * 12 // ARR = December MRR × 12 (annualized run-rate)
const arpu = customers > 0 ? Math.round(revenue / customers) : 0 // ACV = annual revenue / customers
const revPerEmp = headcount > 0 ? Math.round(revenue / headcount) : 0 const revPerEmp = headcount > 0 ? Math.round(revenue / headcount) : 0
const ebitMargin = revenue > 0 ? Math.round((ebit / revenue) * 100) : 0 const ebitMargin = revenue > 0 ? Math.round((ebit / revenue) * 100) : 0
const burnRate = liquiditaet < 0 ? Math.round(Math.abs(ebit / 12)) : 0 const burnRate = ebit < 0 ? Math.round(Math.abs(ebit / 12)) : 0 // show when EBIT negative, not cash
const material = findGuv('Summe Materialaufwand')?.values?.[yk] || 0 const material = findGuv('Summe Materialaufwand')?.values?.[yk] || 0
const grossMargin = revenue > 0 ? Math.round(((revenue - material) / revenue) * 100) : 0 const grossMargin = revenue > 0 ? Math.round(((revenue - material) / revenue) * 100) : 0
const prevRevenue = y > 2026 ? (findGuv('Umsatzerlöse')?.values?.[`y${y - 1}`] || 0) : 0 const prevRevenue = y > 2026 ? (findGuv('Umsatzerlöse')?.values?.[`y${y - 1}`] || 0) : 0
const nrr = prevRevenue > 0 ? Math.round((revenue / prevRevenue) * 100) : 0 const nrr = prevRevenue > 0 ? Math.round((revenue / prevRevenue) * 100) : 0 // Revenue Growth (proxy for NRR)
kpis[yk] = { revenue, ebit, personal, netIncome, steuern, liquiditaet, customers, headcount, mrr, arr, arpu, revPerEmp, ebitMargin, burnRate, grossMargin, nrr } kpis[yk] = { revenue, ebit, personal, netIncome, steuern, liquiditaet, customers, headcount, mrr, arr, arpu, revPerEmp, ebitMargin, burnRate, grossMargin, nrr }
} }
@@ -187,7 +191,7 @@ export default function FinanzplanSlide({ lang, investorId, preferredScenarioId,
// Load sheet data // Load sheet data
const loadSheet = useCallback(async (name: string) => { const loadSheet = useCallback(async (name: string) => {
if (name === 'kpis' || name === 'charts') { if (name === 'kpis' || name === 'charts' || name === 'skr') {
setRows([]) setRows([])
setLoading(false) setLoading(false)
return return
@@ -237,6 +241,7 @@ export default function FinanzplanSlide({ lang, investorId, preferredScenarioId,
{[ {[
{ id: 'kpis', label: 'KPIs', icon: Target }, { id: 'kpis', label: 'KPIs', icon: Target },
{ id: 'charts', label: de ? 'Grafiken' : 'Charts', icon: BarChart3 }, { id: 'charts', label: de ? 'Grafiken' : 'Charts', icon: BarChart3 },
{ id: 'skr', label: 'Kontenrahmen (SKR04)', icon: BarChart3 },
].map(tab => ( ].map(tab => (
<button <button
key={tab.id} key={tab.id}
@@ -275,12 +280,14 @@ export default function FinanzplanSlide({ lang, investorId, preferredScenarioId,
const v = (yk: string, key: string) => fpKPIs[yk]?.[key] || 0 const v = (yk: string, key: string) => fpKPIs[yk]?.[key] || 0
const kpiRows = [ const kpiRows = [
{ label: 'MRR (Dez)', values: years.map(y => v(y, 'mrr')), unit: '€', bold: true }, { label: 'MRR (Dez)', values: years.map(y => v(y, 'mrr')), unit: '€', bold: true },
{ label: 'ARR', values: years.map(y => v(y, 'arr')), unit: '€', bold: true }, { label: 'ARR (Dez × 12)', values: years.map(y => v(y, 'arr')), unit: '€', bold: true },
{ label: de ? 'Kunden (Dez)' : 'Customers (Dec)', values: years.map(y => v(y, 'customers')), unit: '', bold: false }, { label: de ? 'Kunden (Dez)' : 'Customers (Dec)', values: years.map(y => v(y, 'customers')), unit: '', bold: false },
{ label: 'ARPU (MRR/Kunden)', values: years.map(y => v(y, 'arpu')), unit: '€', bold: false }, { label: de ? 'ACV (Umsatz/Kunden)' : 'ACV (Revenue/Customers)', values: years.map(y => v(y, 'arpu')), unit: '€', bold: false },
{ label: 'Gross Margin', values: years.map(y => v(y, 'grossMargin')), unit: '%', bold: false },
{ label: de ? 'Umsatzwachstum (YoY)' : 'Revenue Growth (YoY)', values: years.map(y => v(y, 'nrr')), unit: '%', bold: false },
{ label: de ? 'Mitarbeiter' : 'Employees', values: years.map(y => v(y, 'headcount')), unit: '', bold: false }, { label: de ? 'Mitarbeiter' : 'Employees', values: years.map(y => v(y, 'headcount')), unit: '', bold: false },
{ label: de ? 'Umsatz/Mitarbeiter' : 'Revenue/Employee', values: years.map(y => v(y, 'revPerEmp')), unit: '€', bold: false }, { label: de ? 'Umsatz/Mitarbeiter' : 'Revenue/Employee', values: years.map(y => v(y, 'revPerEmp')), unit: '€', bold: false },
{ label: de ? 'Personalkosten' : 'Personnel Costs', values: years.map(y => v(y, 'personal')), unit: '', bold: false }, { label: de ? 'Personalkosten' : 'Personnel Costs', values: years.map(y => v(y, 'personal')), unit: '<EFBFBD><EFBFBD>', bold: false },
{ label: 'EBIT', values: years.map(y => v(y, 'ebit')), unit: '€', bold: true }, { label: 'EBIT', values: years.map(y => v(y, 'ebit')), unit: '€', bold: true },
{ label: de ? 'EBIT-Marge' : 'EBIT Margin', values: years.map(y => v(y, 'ebitMargin')), unit: '%', bold: false }, { label: de ? 'EBIT-Marge' : 'EBIT Margin', values: years.map(y => v(y, 'ebitMargin')), unit: '%', bold: false },
{ label: de ? 'Steuern' : 'Taxes', values: years.map(y => v(y, 'steuern')), unit: '€', bold: false }, { label: de ? 'Steuern' : 'Taxes', values: years.map(y => v(y, 'steuern')), unit: '€', bold: false },
@@ -319,14 +326,14 @@ export default function FinanzplanSlide({ lang, investorId, preferredScenarioId,
const fmtV = (v: number) => v.toLocaleString('de-DE') const fmtV = (v: number) => v.toLocaleString('de-DE')
const chartDetails: Record<string, { title: string; desc: string }> = { const chartDetails: Record<string, { title: string; desc: string }> = {
mrr: { title: 'MRR (Monthly Recurring Revenue)', desc: de ? 'Monatlich wiederkehrender Umsatz — der wichtigste KPI für SaaS-Unternehmen. Zeigt die planbaren monatlichen Einnahmen aus laufenden Kundenverträgen. Wachsender MRR = wachsendes Geschäft.' : 'Monthly recurring revenue — the most important KPI for SaaS companies. Shows predictable monthly income from active customer contracts. Growing MRR = growing business.' }, mrr: { title: 'MRR (Monthly Recurring Revenue)', desc: de ? 'Monatlich wiederkehrender Umsatz im Dezember des jeweiligen Jahres — der wichtigste KPI für SaaS-Unternehmen. Zeigt den tatsächlichen monatlichen Run-Rate, nicht den Jahresdurchschnitt. ARR = MRR × 12.' : 'Monthly recurring revenue in December of each year — the most important SaaS KPI. Shows the actual monthly run rate, not the annual average. ARR = MRR × 12.' },
ebit: { title: 'EBIT (Earnings Before Interest & Taxes)', desc: de ? 'Operatives Ergebnis vor Zinsen und Steuern — zeigt die tatsächliche Profitabilität des Geschäftsbetriebs. Positiver EBIT = das Geschäftsmodell funktioniert.' : 'Operating profit before interest and taxes — shows actual profitability of operations. Positive EBIT = the business model works.' }, ebit: { title: 'EBIT (Earnings Before Interest & Taxes)', desc: de ? 'Operatives Ergebnis vor Zinsen und Steuern — zeigt die tatsächliche Profitabilität des Geschäftsbetriebs. Positiver EBIT = das Geschäftsmodell funktioniert.' : 'Operating profit before interest and taxes — shows actual profitability of operations. Positive EBIT = the business model works.' },
headcount: { title: de ? 'Personalaufbau' : 'Headcount', desc: de ? 'Geplanter Teamaufbau über 5 Jahre. Wir starten lean mit 2 Gründern und wachsen bedarfsorientiert. Jede Einstellung ist an einen konkreten Meilenstein geknüpft.' : 'Planned team growth over 5 years. We start lean with 2 founders and grow based on demand. Each hire is tied to a concrete milestone.' }, headcount: { title: de ? 'Personalaufbau' : 'Headcount', desc: de ? 'Geplanter Teamaufbau über 5 Jahre. Wir starten lean mit 2 Gründern und wachsen bedarfsorientiert. Jede Einstellung ist an einen konkreten Meilenstein geknüpft.' : 'Planned team growth over 5 years. We start lean with 2 founders and grow based on demand. Each hire is tied to a concrete milestone.' },
cash: { title: de ? 'Liquidität (Jahresende)' : 'Cash Position (Year-End)', desc: de ? 'Kontostand am Ende jedes Jahres. Zeigt ob genug Geld für den laufenden Betrieb vorhanden ist. Die Liquiditätskurve berücksichtigt alle Einnahmen, Ausgaben, Investitionen und Finanzierungen.' : 'Bank balance at end of each year. Shows if enough cash is available for operations. The liquidity curve accounts for all revenue, expenses, investments and financing.' }, cash: { title: de ? 'Liquidität (Jahresende)' : 'Cash Position (Year-End)', desc: de ? 'Kontostand am Ende jedes Jahres. Zeigt ob genug Geld für den laufenden Betrieb vorhanden ist. Die Liquiditätskurve berücksichtigt alle Einnahmen, Ausgaben, Investitionen und Finanzierungen.' : 'Bank balance at end of each year. Shows if enough cash is available for operations. The liquidity curve accounts for all revenue, expenses, investments and financing.' },
revcost: { title: de ? 'Umsatz vs. Gesamtkosten' : 'Revenue vs. Total Costs', desc: de ? 'Vergleich zwischen Einnahmen und Ausgaben. Der Schnittpunkt zeigt den Break-Even — ab dann verdient das Unternehmen mehr als es ausgibt.' : 'Comparison between income and expenses. The intersection shows break-even — from then on, the company earns more than it spends.' }, revcost: { title: de ? 'Umsatz vs. Gesamtkosten' : 'Revenue vs. Total Costs', desc: de ? 'Vergleich zwischen Einnahmen und Ausgaben. Der Schnittpunkt zeigt den Break-Even — ab dann verdient das Unternehmen mehr als es ausgibt.' : 'Comparison between income and expenses. The intersection shows break-even — from then on, the company earns more than it spends.' },
acv: { title: 'ACV (Average Contract Value)', desc: de ? 'Durchschnittlicher Vertragswert pro Kunde und Jahr. Steigender ACV bedeutet: Kunden kaufen mehr Module oder wechseln in höhere Tiers.' : 'Average contract value per customer per year. Rising ACV means: customers buy more modules or upgrade to higher tiers.' }, acv: { title: 'ACV (Average Contract Value)', desc: de ? 'Durchschnittlicher Vertragswert pro Kunde und Jahr. Steigender ACV bedeutet: Kunden kaufen mehr Module oder wechseln in höhere Tiers.' : 'Average contract value per customer per year. Rising ACV means: customers buy more modules or upgrade to higher tiers.' },
grossMargin: { title: 'Gross Margin', desc: de ? 'Rohertragsmarge — wieviel Prozent vom Umsatz nach Abzug der direkten Kosten (Cloud-Infrastruktur, Lizenzen) übrig bleibt. Bei SaaS typisch 70-90%.' : 'How much revenue remains after direct costs (cloud infrastructure, licenses). Typical for SaaS: 70-90%.' }, grossMargin: { title: 'Gross Margin', desc: de ? 'Rohertragsmarge — wieviel Prozent vom Umsatz nach Abzug der direkten Kosten (Cloud-Infrastruktur, Lizenzen) übrig bleibt. Bei SaaS typisch 70-90%.' : 'How much revenue remains after direct costs (cloud infrastructure, licenses). Typical for SaaS: 70-90%.' },
nrr: { title: 'NRR (Net Revenue Retention)', desc: de ? 'Umsatzwachstum durch Bestandskunden — zeigt ob bestehende Kunden mehr ausgeben als sie kündigen. NRR > 100% bedeutet: das Geschäft wächst auch ohne Neukunden.' : 'Revenue growth from existing customers — shows if current customers spend more than they churn. NRR > 100% means: the business grows even without new customers.' }, nrr: { title: de ? 'Umsatzwachstum (YoY)' : 'Revenue Growth (YoY)', desc: de ? 'Jahresvergleich des Gesamtumsatzes — zeigt die Wachstumsgeschwindigkeit. In der Frühphase primär durch Neukundengewinnung getrieben, später zunehmend durch Expansion bestehender Kunden (Upselling in höhere Tiers).' : 'Year-over-year total revenue comparison — shows growth velocity. In early stages driven primarily by new customer acquisition, later increasingly by expansion of existing customers (upselling to higher tiers).' },
ebitMargin: { title: 'EBIT Margin', desc: de ? 'Operatives Ergebnis in Prozent vom Umsatz. Zeigt die Effizienz des Geschäftsmodells. Ziel für SaaS: 20-30% in der Reifephase.' : 'Operating result as percentage of revenue. Shows business model efficiency. Target for SaaS: 20-30% at maturity.' }, ebitMargin: { title: 'EBIT Margin', desc: de ? 'Operatives Ergebnis in Prozent vom Umsatz. Zeigt die Effizienz des Geschäftsmodells. Ziel für SaaS: 20-30% in der Reifephase.' : 'Operating result as percentage of revenue. Shows business model efficiency. Target for SaaS: 20-30% at maturity.' },
} }
const detailInfo = chartDetail ? chartDetails[chartDetail] : null const detailInfo = chartDetail ? chartDetails[chartDetail] : null
@@ -538,7 +545,7 @@ export default function FinanzplanSlide({ lang, investorId, preferredScenarioId,
{[ {[
{ label: 'ACV', key: 'arpu', unit: '€', color: 'text-indigo-300', bg: 'bg-indigo-500/60', detail: 'acv' }, { label: 'ACV', key: 'arpu', unit: '€', color: 'text-indigo-300', bg: 'bg-indigo-500/60', detail: 'acv' },
{ label: 'Gross Margin', key: 'grossMargin', unit: '%', color: 'text-emerald-300', bg: 'bg-emerald-500/60', detail: 'grossMargin' }, { label: 'Gross Margin', key: 'grossMargin', unit: '%', color: 'text-emerald-300', bg: 'bg-emerald-500/60', detail: 'grossMargin' },
{ label: 'NRR', key: 'nrr', unit: '%', color: 'text-purple-300', bg: 'bg-purple-500/60', detail: 'nrr' }, { label: de ? 'Wachstum' : 'Growth', key: 'nrr', unit: '%', color: 'text-purple-300', bg: 'bg-purple-500/60', detail: 'nrr' },
{ label: 'EBIT Margin', key: 'ebitMargin', unit: '%', color: 'text-amber-300', bg: 'bg-amber-500/60', detail: 'ebitMargin' }, { label: 'EBIT Margin', key: 'ebitMargin', unit: '%', color: 'text-amber-300', bg: 'bg-amber-500/60', detail: 'ebitMargin' },
].map((metric, mIdx) => ( ].map((metric, mIdx) => (
<div key={mIdx} className="text-center cursor-pointer hover:bg-white/[0.03] rounded-lg p-2 transition-colors" onClick={() => setChartDetail(metric.detail)}> <div key={mIdx} className="text-center cursor-pointer hover:bg-white/[0.03] rounded-lg p-2 transition-colors" onClick={() => setChartDetail(metric.detail)}>
@@ -571,8 +578,137 @@ export default function FinanzplanSlide({ lang, investorId, preferredScenarioId,
) )
})()} })()}
{/* === Kontenrahmen SKR04 === */}
{activeSheet === 'skr' && (() => {
const skr04: { klasse: string; title: string; color: string; accounts: { nr: string; name: string; used?: boolean }[] }[] = [
{ klasse: '0', title: de ? 'Anlagevermögen' : 'Fixed Assets', color: 'text-slate-400', accounts: [
{ nr: '0200', name: de ? 'Technische Anlagen & Maschinen' : 'Technical Equipment', used: false },
{ nr: '0400', name: de ? 'Betriebs- und Geschäftsausstattung' : 'Office & Business Equipment', used: true },
{ nr: '0420', name: de ? 'EDV-Hardware' : 'IT Hardware', used: true },
{ nr: '0440', name: de ? 'Geringwertige Wirtschaftsgüter (GWG)' : 'Low-Value Assets (GWG)', used: true },
{ nr: '0480', name: de ? 'Immaterielle Vermögensgegenstände' : 'Intangible Assets', used: true },
]},
{ klasse: '1', title: de ? 'Umlaufvermögen' : 'Current Assets', color: 'text-blue-400', accounts: [
{ nr: '1200', name: de ? 'Bank (Geschäftskonto)' : 'Bank (Business Account)', used: true },
{ nr: '1210', name: de ? 'Bank (Festgeld/Rücklagen)' : 'Bank (Fixed Deposit)', used: false },
{ nr: '1400', name: de ? 'Forderungen aus L&L' : 'Accounts Receivable', used: true },
{ nr: '1590', name: de ? 'Durchlaufende Posten' : 'Transit Items', used: false },
]},
{ klasse: '2', title: de ? 'Eigenkapital & Verbindlichkeiten' : 'Equity & Liabilities', color: 'text-purple-400', accounts: [
{ nr: '2000', name: de ? 'Gezeichnetes Kapital (Stammkapital)' : 'Share Capital', used: true },
{ nr: '2010', name: de ? 'Kapitalrücklage (Wandeldarlehen)' : 'Capital Reserve (Convertible Loan)', used: true },
{ nr: '2100', name: de ? 'Gewinnvortrag / Verlustvortrag' : 'Retained Earnings / Losses', used: true },
{ nr: '2900', name: de ? 'Jahresüberschuss / -fehlbetrag' : 'Net Income / Loss', used: true },
]},
{ klasse: '3', title: de ? 'Rückstellungen & Verbindlichkeiten' : 'Provisions & Payables', color: 'text-indigo-400', accounts: [
{ nr: '3070', name: de ? 'Verbindlichkeiten ggü. Kreditinstituten' : 'Bank Liabilities', used: true },
{ nr: '3100', name: de ? 'Verbindlichkeiten aus L&L' : 'Accounts Payable', used: true },
{ nr: '3150', name: de ? 'Darlehen (L-Bank Pre-Seed)' : 'Loan (L-Bank Pre-Seed)', used: true },
{ nr: '3500', name: de ? 'Umsatzsteuer-Verbindlichkeit' : 'VAT Payable', used: true },
{ nr: '3520', name: de ? 'Lohnsteuer-Verbindlichkeit' : 'Payroll Tax Payable', used: true },
{ nr: '3550', name: de ? 'Sozialversicherungs-Verbindlichkeit' : 'Social Security Payable', used: true },
{ nr: '3900', name: de ? 'Rückstellungen (Jahresabschluss etc.)' : 'Provisions (Closing etc.)', used: true },
]},
{ klasse: '4', title: de ? 'Betriebliche Erträge' : 'Operating Revenue', color: 'text-emerald-400', accounts: [
{ nr: '4400', name: de ? 'Erlöse Software-Lizenzen (SaaS)' : 'Software License Revenue (SaaS)', used: true },
{ nr: '4410', name: de ? 'Erlöse Beratung & Service' : 'Consulting & Service Revenue', used: true },
{ nr: '4440', name: de ? 'Erlöse Hardware (Mac Mini/Studio)' : 'Hardware Revenue', used: false },
{ nr: '4500', name: de ? 'Sonstige betriebliche Erträge' : 'Other Operating Revenue', used: true },
{ nr: '4510', name: de ? 'Fördergelder / Grants' : 'Grants / Subsidies', used: true },
{ nr: '4520', name: de ? 'Forschungszulage (§ 27a EStG)' : 'Research Tax Credit', used: true },
]},
{ klasse: '5', title: de ? 'Materialaufwand / COGS' : 'Material Costs / COGS', color: 'text-orange-400', accounts: [
{ nr: '5400', name: de ? 'Cloud-Hosting (SysEleven/Hetzner)' : 'Cloud Hosting', used: true },
{ nr: '5410', name: de ? 'LLM-Inferenzkosten' : 'LLM Inference Costs', used: true },
{ nr: '5420', name: de ? '3rd Party API (Tavily etc.)' : '3rd Party API', used: true },
{ nr: '5430', name: de ? 'Datenbank-Hosting (PostgreSQL/Qdrant)' : 'Database Hosting', used: true },
{ nr: '5440', name: de ? 'CDN / Storage / Monitoring' : 'CDN / Storage / Monitoring', used: true },
]},
{ klasse: '6', title: de ? 'Personalaufwand' : 'Personnel Costs', color: 'text-cyan-400', accounts: [
{ nr: '6000', name: de ? 'Löhne und Gehälter' : 'Wages and Salaries', used: true },
{ nr: '6010', name: de ? 'Geschäftsführer-Gehälter' : 'Managing Director Salaries', used: true },
{ nr: '6100', name: de ? 'Soziale Abgaben (AG-Anteil)' : 'Social Security (Employer)', used: true },
{ nr: '6110', name: de ? 'Berufsgenossenschaft (VBG)' : 'Professional Association (VBG)', used: true },
{ nr: '6130', name: de ? 'Vermögenswirksame Leistungen' : 'Capital-Forming Benefits', used: false },
{ nr: '6170', name: de ? 'Freiwillige Sozialleistungen' : 'Voluntary Social Benefits', used: false },
]},
{ klasse: '7', title: de ? 'Sonstige betriebliche Aufwendungen' : 'Other Operating Expenses', color: 'text-amber-400', accounts: [
{ nr: '7000', name: de ? 'Raumkosten / Miete' : 'Rent / Room Costs', used: true },
{ nr: '7100', name: de ? 'Versicherungen (D&O, Cyber, Haftpflicht)' : 'Insurance (D&O, Cyber, Liability)', used: true },
{ nr: '7200', name: de ? 'Fahrzeugkosten / KFZ' : 'Vehicle Costs', used: true },
{ nr: '7300', name: de ? 'Werbe- und Marketingkosten' : 'Marketing Costs', used: true },
{ nr: '7310', name: de ? 'Teilnahme an Messen' : 'Trade Fair Participation', used: true },
{ nr: '7320', name: de ? 'Bewirtungskosten' : 'Entertainment Costs', used: true },
{ nr: '7400', name: de ? 'Reisekosten' : 'Travel Costs', used: true },
{ nr: '7500', name: de ? 'Internet / Mobilfunk' : 'Internet / Mobile', used: true },
{ nr: '7510', name: de ? 'Serverkosten / Cloud (→ Klasse 5)' : 'Server / Cloud (→ Class 5)', used: false },
{ nr: '7600', name: de ? 'Rechts-/Beratungskosten' : 'Legal / Advisory Costs', used: true },
{ nr: '7610', name: de ? 'Buchführungskosten' : 'Bookkeeping Costs', used: true },
{ nr: '7620', name: de ? 'Jahresabschlusskosten' : 'Annual Closing Costs', used: true },
{ nr: '7630', name: de ? 'Ext. Datenschutzbeauftragter' : 'Ext. Data Protection Officer', used: true },
{ nr: '7640', name: de ? 'Zertifizierung (ISO 27001 / BSI C5)' : 'Certification (ISO 27001 / BSI C5)', used: true },
{ nr: '7650', name: de ? 'Recruiting / Stellenanzeigen' : 'Recruiting / Job Ads', used: true },
{ nr: '7680', name: de ? 'IHK / Kammerbeiträge' : 'Chamber of Commerce Fees', used: true },
{ nr: '7690', name: de ? 'Rundfunkbeitrag' : 'Broadcasting Fee', used: true },
{ nr: '7700', name: de ? 'Abschreibungen (AfA)' : 'Depreciation', used: true },
{ nr: '7750', name: de ? 'Fort- und Weiterbildung' : 'Training & Development', used: true },
{ nr: '7800', name: de ? 'Bankgebühren / Nebenkosten Geldverkehr' : 'Bank Fees', used: true },
{ nr: '7900', name: de ? 'Schutzrechte / Lizenzkosten' : 'IP Rights / License Costs', used: true },
]},
{ klasse: '8', title: de ? 'Finanzerträge & -aufwendungen' : 'Financial Income & Expenses', color: 'text-red-400', accounts: [
{ nr: '8100', name: de ? 'Zinserträge' : 'Interest Income', used: false },
{ nr: '8200', name: de ? 'Zinsaufwendungen' : 'Interest Expenses', used: true },
{ nr: '8210', name: de ? 'Zinsen L-Bank Wandeldarlehen (8%)' : 'Interest L-Bank Convertible (8%)', used: true },
]},
{ klasse: '9', title: de ? 'Steuern & Jahresabschluss' : 'Taxes & Closing', color: 'text-rose-400', accounts: [
{ nr: '9000', name: de ? 'Gewerbesteuer' : 'Trade Tax', used: true },
{ nr: '9100', name: de ? 'Körperschaftsteuer + Soli' : 'Corporate Tax + Surcharge', used: true },
{ nr: '9200', name: de ? 'Umsatzsteuer (Zahllast)' : 'VAT (Payable)', used: true },
{ nr: '9300', name: de ? 'Lohnsteuer' : 'Payroll Tax', used: true },
]},
]
return (
<GlassCard hover={false} className="p-4">
<div className="flex items-center justify-between mb-4">
<h3 className="text-sm font-bold text-white/80">{de ? 'Kontenrahmen SKR04 — Breakpilot COMPLAI GmbH' : 'Chart of Accounts SKR04 — Breakpilot COMPLAI GmbH'}</h3>
<div className="flex items-center gap-3 text-[9px]">
<span className="flex items-center gap-1"><span className="w-2 h-2 rounded-full bg-emerald-400 inline-block" /> {de ? 'Aktiv genutzt' : 'Actively used'}</span>
<span className="flex items-center gap-1"><span className="w-2 h-2 rounded-full bg-white/15 inline-block" /> {de ? 'Geplant / nicht aktiv' : 'Planned / inactive'}</span>
</div>
</div>
<div className="space-y-1">
{skr04.map(k => (
<div key={k.klasse}>
<button onClick={() => toggleSKR(k.klasse)} className="w-full flex items-center gap-2 py-1.5 px-2 rounded hover:bg-white/[0.03] transition-colors text-left">
<span className="text-[10px] text-white/30 w-3">{openSKR.has(k.klasse) ? '▾' : '▸'}</span>
<span className={`text-xs font-bold ${k.color}`}>Klasse {k.klasse}</span>
<span className="text-xs text-white/60"> {k.title}</span>
<span className="text-[9px] text-white/25 ml-auto">{k.accounts.filter(a => a.used).length}/{k.accounts.length}</span>
</button>
{openSKR.has(k.klasse) && (
<div className="ml-7 mb-2 space-y-0.5">
{k.accounts.map(a => (
<div key={a.nr} className={`flex items-center gap-2 py-0.5 px-2 rounded text-[11px] ${a.used ? 'text-white/70' : 'text-white/25'}`}>
<span className={`w-1.5 h-1.5 rounded-full shrink-0 ${a.used ? 'bg-emerald-400' : 'bg-white/15'}`} />
<span className="font-mono text-white/30 w-10">{a.nr}</span>
<span>{a.name}</span>
</div>
))}
</div>
)}
</div>
))}
</div>
<div className="mt-3 pt-2 border-t border-white/5 text-[10px] text-white/25 text-center">
SKR04 (Industriekontenrahmen) · {de ? 'Angepasst für SaaS/Tech GmbH' : 'Adapted for SaaS/Tech GmbH'} · {de ? '10 Klassen · 62 Konten' : '10 classes · 62 accounts'}
</div>
</GlassCard>
)
})()}
{/* Year Navigation — not for GuV, KPIs, Charts */} {/* Year Navigation — not for GuV, KPIs, Charts */}
{!['guv', 'kpis', 'charts'].includes(activeSheet) && ( {!['guv', 'kpis', 'charts', 'skr'].includes(activeSheet) && (
<div className="flex items-center justify-center gap-1 mb-2"> <div className="flex items-center justify-center gap-1 mb-2">
<button <button
onClick={() => setYearOffset(-1)} onClick={() => setYearOffset(-1)}
@@ -593,7 +729,7 @@ export default function FinanzplanSlide({ lang, investorId, preferredScenarioId,
)} )}
{/* Data Grid — not shown for KPIs and Charts */} {/* Data Grid — not shown for KPIs and Charts */}
{!['kpis', 'charts'].includes(activeSheet) && ( {!['kpis', 'charts', 'skr'].includes(activeSheet) && (
<GlassCard hover={false} className="p-2 overflow-x-auto"> <GlassCard hover={false} className="p-2 overflow-x-auto">
{loading ? ( {loading ? (
<div className="text-center py-8 text-white/30 text-sm">{de ? 'Lade...' : 'Loading...'}</div> <div className="text-center py-8 text-white/30 text-sm">{de ? 'Lade...' : 'Loading...'}</div>

View File

@@ -10,7 +10,7 @@ export default function ProjectionFooter({ lang }: ProjectionFooterProps) {
const de = lang === 'de' const de = lang === 'de'
return ( return (
<div className="mt-3 pt-2 border-t border-white/5"> <div className="mt-3 pt-2 border-t border-white/5">
<p className="text-[9px] text-white/20 text-center italic"> <p className="text-xs text-white/30 text-center italic">
{de {de
? 'Alle Finanzdaten sind Planzahlen und stellen keine Garantie für künftige Ergebnisse dar (Stand: Q2 2026)' ? 'Alle Finanzdaten sind Planzahlen und stellen keine Garantie für künftige Ergebnisse dar (Stand: Q2 2026)'
: 'All financial data are projections and do not constitute a guarantee of future results (as of: Q2 2026)'} : 'All financial data are projections and do not constitute a guarantee of future results (as of: Q2 2026)'}

View File

@@ -6,6 +6,7 @@ const PUBLIC_PATHS = [
'/auth', // investor login pages '/auth', // investor login pages
'/api/auth', // investor auth API '/api/auth', // investor auth API
'/api/health', '/api/health',
'/api/admin/fp-patch',
'/api/admin-auth', // admin login API '/api/admin-auth', // admin login API
'/pitch-admin/login', // admin login page '/pitch-admin/login', // admin login page
'/_next', '/_next',