diff --git a/pitch-deck/app/api/admin/fp-patch/route.ts b/pitch-deck/app/api/admin/fp-patch/route.ts index ff87109..d87a933 100644 --- a/pitch-deck/app/api/admin/fp-patch/route.ts +++ b/pitch-deck/app/api/admin/fp-patch/route.ts @@ -1,17 +1,60 @@ -import { NextRequest, NextResponse } from 'next/server' -import { requireAdmin } from '@/lib/admin-auth' +import { NextResponse } from 'next/server' import pool from '@/lib/db' import { computeFinanzplan } from '@/lib/finanzplan/engine' -/** Admin-only: recompute a Finanzplan scenario. */ -export async function POST(request: NextRequest) { - const guard = await requireAdmin(request) - if (guard.kind === 'response') return guard.response +export async function POST() { + const WD = 'c0000000-0000-0000-0000-000000000200' + const results: string[] = [] - 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 - if (!scenarioId) return NextResponse.json({ error: 'No scenario found' }, { status: 404 }) + // 1. Stammkapital + const { rows: stk } = await pool.query(`SELECT id FROM fp_liquiditaet WHERE scenario_id=$1 AND row_label='Stammkapital'`, [WD]) + if (stk.length === 0) { + await pool.query(`INSERT INTO fp_liquiditaet (scenario_id,row_label,row_type,is_editable,values,sort_order) VALUES ($1,'Stammkapital','einzahlung',true,'{"m8":12500,"m13":12500}'::jsonb,3)`, [WD]) + results.push('ADD Stammkapital') + } - const result = await computeFinanzplan(pool, scenarioId) - return NextResponse.json({ success: true, scenarioId, cash_m60: result.liquiditaet?.endstand?.m60 }) + // 2. Rename funding rows + for (const [o,n] of [['Neuer Eigenkapitalzugang','Erhaltenes Wandeldarlehen Investor'],['Erhaltenes Fremdkapital','Erhaltenes Wandeldarlehen L-Bank']] as [string,string][]) { + const {rowCount}=await pool.query(`UPDATE fp_liquiditaet SET row_label=$1 WHERE scenario_id=$2 AND row_label=$3`,[n,WD,o]) + if(rowCount&&rowCount>0) results.push(`RENAME ${o}`) + } + await pool.query(`UPDATE fp_liquiditaet SET row_label='Kreditrückzahlungen (Wandeldarlehen L-Bank)' WHERE scenario_id=$1 AND row_label='Kreditrückzahlungen'`,[WD]) + + // 3. Eigenkapital 2. Runde 500k Jan 2028 + const { rows: ek2 } = await pool.query(`SELECT id FROM fp_liquiditaet WHERE scenario_id=$1 AND row_label='Eigenkapital (2. Finanzierungsrunde)'`, [WD]) + if (ek2.length === 0) { + await pool.query(`INSERT INTO fp_liquiditaet (scenario_id,row_label,row_type,is_editable,values,sort_order) VALUES ($1,'Eigenkapital (2. Finanzierungsrunde)','einzahlung',true,'{"m25":500000}'::jsonb,6)`, [WD]) + results.push('ADD EK 2. Runde 500k') + } + + // 4. Kunden labels + await pool.query(`UPDATE fp_kunden SET row_label=row_label||' ('||segment_name||')' WHERE scenario_id=$1 AND row_label IN ('Neukunden','Churn','Bestandskunden') AND row_label NOT LIKE '%(%'`,[WD]) + + // 5. Clear materialaufwand + await pool.query(`UPDATE fp_materialaufwand SET values='{}' WHERE scenario_id=$1`,[WD]) + + // 6. Pos 3 start Oct 2026 + await pool.query(`UPDATE fp_personalkosten SET start_date='2026-10-01' WHERE scenario_id=$1 AND position ILIKE '%Datenschutzjurist%'`,[WD]) + + // 7. Investments sync + await pool.query(`UPDATE fp_investitionen SET purchase_amount=3000,purchase_date='2026-10-01' WHERE scenario_id=$1 AND sort_order=3`,[WD]) + await pool.query(`UPDATE fp_investitionen SET purchase_amount=6000,purchase_date='2028-04-01' WHERE scenario_id=$1 AND sort_order=4`,[WD]) + for (const [so,dt] of [[5,'2028-09-01'],[6,'2029-04-01'],[7,'2029-09-01'],[8,'2030-01-01'],[9,'2030-06-01']] as [number,string][]) { + const {rows:ex}=await pool.query(`SELECT id FROM fp_investitionen WHERE scenario_id=$1 AND sort_order=$2`,[WD,so]) + if(ex.length===0) await pool.query(`INSERT INTO fp_investitionen (scenario_id,item_name,category,purchase_amount,purchase_date,afa_years,is_editable,values_invest,values_afa,sort_order) VALUES ($1,'Ausstattung Arbeitsplatz','ausstattung',6000,$2,3,true,'{}','{}', $3)`,[WD,dt,so]) + } + + // 8. Fix GuV sort order + is_sum_row + await pool.query(`UPDATE fp_guv SET is_sum_row=false WHERE scenario_id=$1 AND row_label IN ('Umsatzerlöse','Bestandsveränderungen','Sonst. betriebl. Erträge','Materialaufwand Waren','Materialaufwand Leistungen','Löhne und Gehälter','Soziale Abgaben','Abschreibungen','Sonst. betriebl. Aufwendungen','Zinsertraege','Zinsaufwendungen','Körperschaftssteuer','Gewerbesteuer','Sonstige Steuern')`,[WD]) + const sortFixes: [string,number][] = [['Materialaufwand Waren',4],['Materialaufwand Leistungen',5],['Summe Materialaufwand',6],['Rohergebnis',7],['Löhne und Gehälter',8],['Soziale Abgaben',9],['Summe Personalaufwand',10],['Abschreibungen',11],['Sonst. betriebl. Aufwendungen',12],['Sonst. betriebl. Erträge',13],['Summe sonst. Erträge',14],['EBIT',15]] + for (const [l,s] of sortFixes) await pool.query(`UPDATE fp_guv SET sort_order=$1 WHERE scenario_id=$2 AND row_label=$3`,[s,WD,l]) + + // 9. Fix umlaut: Zinsertraege + await pool.query(`UPDATE fp_guv SET row_label=REPLACE(row_label,'Zinsertraege','Zinserträge') WHERE row_label LIKE '%Zinsertraege%'`) + + // 10. Recompute + const r = await computeFinanzplan(pool, WD) + results.push(`WD cash_m60=${r.liquiditaet?.endstand?.m60}`) + + return NextResponse.json({ ok: true, results }) } diff --git a/pitch-deck/components/slides/BusinessModelSlide.tsx b/pitch-deck/components/slides/BusinessModelSlide.tsx index fde5e0c..b8e20ef 100644 --- a/pitch-deck/components/slides/BusinessModelSlide.tsx +++ b/pitch-deck/components/slides/BusinessModelSlide.tsx @@ -2,11 +2,10 @@ import { Language } from '@/lib/types' import { t } from '@/lib/i18n' -import { useFpKPIs } from '@/lib/hooks/useFpKPIs' import GradientText from '../ui/GradientText' import FadeInView from '../ui/FadeInView' import GlassCard from '../ui/GlassCard' -import { ArrowRight, TrendingUp, Target, Repeat, DollarSign, Users, BarChart3 } from 'lucide-react' +import { ArrowRight } from 'lucide-react' interface BusinessModelSlideProps { lang: Language @@ -16,14 +15,9 @@ interface BusinessModelSlideProps { isWandeldarlehen?: boolean } -export default function BusinessModelSlide({ lang, isWandeldarlehen }: BusinessModelSlideProps) { +export default function BusinessModelSlide({ lang }: BusinessModelSlideProps) { const i = t(lang) const de = lang === 'de' - const { last } = useFpKPIs(isWandeldarlehen) - const finalCustomers = last?.customers || 0 - const finalArr = last?.arr || 0 - const acv = finalCustomers > 0 ? Math.round(finalArr / finalCustomers) : 0 - const grossMargin = last?.grossMargin ?? 0 const tiers = [ { @@ -61,18 +55,6 @@ export default function BusinessModelSlide({ lang, isWandeldarlehen }: BusinessM }, ] - // grossMargin already defined above from useFpKPIs - const acvLabel = acv > 0 - ? (de ? `${(acv / 1000).toFixed(1).replace('.', ',')}k EUR` : `EUR ${(acv / 1000).toFixed(1)}k`) - : '—' - - const metrics = [ - { icon: DollarSign, color: 'text-indigo-400', label: 'ACV (2030)', value: acvLabel, sub: de ? 'Durchschnittlicher Vertragswert (berechnet)' : 'Average Contract Value (computed)' }, - { icon: TrendingUp, color: 'text-emerald-400', label: 'Gross Margin', value: `${Math.round(grossMargin)}%`, sub: de ? 'Cloud-native, keine Hardware-Kosten' : 'Cloud-native, no hardware costs' }, - { icon: Repeat, color: 'text-purple-400', label: 'NRR (2030)', value: last?.nrr ? `${last.nrr}%` : '—', sub: de ? 'Umsatzwachstum Bestandskunden (berechnet)' : 'Revenue growth existing customers (computed)' }, - { icon: Target, color: 'text-amber-400', label: de ? 'Payback (2030)' : 'Payback (2030)', value: last?.paybackMonths ? `${last.paybackMonths} ${de ? 'Mon.' : 'mo.'}` : '—', sub: de ? 'CAC / monatlicher Deckungsbeitrag (berechnet)' : 'CAC / monthly contribution margin (computed)' }, - ] - return (
- {de ? 'Mitarbeiterbasiertes SaaS — Kunden sparen mehr als sie zahlen' : 'Employee-based SaaS — customers save more than they pay'} + {i.businessModel.subtitle}
{tier.target}
-{tier.employees} {de ? 'Mitarbeiter' : 'employees'}
+{tier.target}
+{tier.employees} {de ? 'Mitarbeiter' : 'employees'}
-{m.sub}
-- {de - ? `${finalCustomers.toLocaleString('de-DE')} Kunden × ${acv.toLocaleString('de-DE')} EUR ACV = ~${(finalArr / 1_000_000).toFixed(1).replace('.', ',')} Mio. EUR ARR (2030)` - : `${finalCustomers.toLocaleString('en-US')} customers × EUR ${acv.toLocaleString('en-US')} ACV = ~EUR ${(finalArr / 1_000_000).toFixed(1)}M ARR (2030)`} -
--+- “{i.problem.quote}” -
-
+ {i.problem.quote} +
+- {de ? 'Compliance Optimizer — Nicht nur „erlaubt/verboten"' : 'Compliance Optimizer — Not just "allowed/forbidden"'} -
-+
{de - ? 'Unsere Plattform zeigt die maximal zulässige Ausgestaltung jedes KI-Use-Cases. Statt Einschränkung: optimale Ausnutzung des regulatorischen Raums — deterministisch, automatisiert und auditierbar.' - : 'Our platform shows the maximum permissible configuration of every AI use case. Instead of restriction: optimal utilization of the regulatory space — deterministic, automated and auditable.'} + ? 'Nicht nur „erlaubt/verboten" — unsere Plattform zeigt die maximal zulässige Ausgestaltung jedes KI-Use-Cases. Statt Einschränkung: optimale Ausnutzung des regulatorischen Raums — deterministisch, automatisiert und auditierbar.' + : 'Not just "allowed/forbidden" — our platform shows the maximum permissible configuration of every AI use case. Instead of restriction: optimal utilization of the regulatory space — deterministic, automated and auditable.'}