feat(pitch-deck): Pricing slide, GuV hierarchy, Problem/Solution cards, engine fixes
Some checks failed
Build pitch-deck / build-push-deploy (push) Successful in 1m52s
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-python-voice (push) Has been cancelled
CI / test-bqas (push) Has been cancelled
CI / test-go-consent (push) Has been cancelled
Some checks failed
Build pitch-deck / build-push-deploy (push) Successful in 1m52s
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-python-voice (push) Has been cancelled
CI / test-bqas (push) Has been cancelled
CI / test-go-consent (push) Has been cancelled
- BusinessModel → Pricing: remove Unit Economics, fullwidth tiers - GuV: major sums (EBIT, Rohergebnis, Jahresüberschuss) larger font + border - Engine: compute Rohergebnis, dynamic financing row matching - Problem slide: amber/orange "Die Konsequenz" card - Solution slide: larger Compliance Optimizer card - DB patch: Stammkapital, 2. Finanzierungsrunde 500k, GuV sort order Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -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 })
|
||||
|
||||
const result = await computeFinanzplan(pool, scenarioId)
|
||||
return NextResponse.json({ success: true, scenarioId, cash_m60: result.liquiditaet?.endstand?.m60 })
|
||||
// 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')
|
||||
}
|
||||
|
||||
// 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 })
|
||||
}
|
||||
|
||||
@@ -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 (
|
||||
<div className="max-w-6xl mx-auto">
|
||||
<FadeInView className="text-center mb-6">
|
||||
@@ -80,30 +62,27 @@ export default function BusinessModelSlide({ lang, isWandeldarlehen }: BusinessM
|
||||
<GradientText>{i.businessModel.title}</GradientText>
|
||||
</h2>
|
||||
<p className="text-lg text-white/50 max-w-2xl mx-auto">
|
||||
{de ? 'Mitarbeiterbasiertes SaaS — Kunden sparen mehr als sie zahlen' : 'Employee-based SaaS — customers save more than they pay'}
|
||||
{i.businessModel.subtitle}
|
||||
</p>
|
||||
</FadeInView>
|
||||
|
||||
<div className="grid md:grid-cols-12 gap-4">
|
||||
{/* Left: Pricing Tiers */}
|
||||
<div className="md:col-span-7">
|
||||
<div className="grid grid-cols-3 gap-3">
|
||||
<div className="grid grid-cols-3 gap-4">
|
||||
{tiers.map((tier, idx) => (
|
||||
<FadeInView key={idx} delay={0.1 + idx * 0.1}>
|
||||
<GlassCard hover={false} className={`p-4 h-full ${tier.highlight ? 'border-slate-300/40 bg-gradient-to-b from-slate-200/[0.08] to-slate-400/[0.04] shadow-lg shadow-slate-300/10 ring-1 ring-slate-300/20' : ''}`}>
|
||||
<h3 className="text-base font-bold text-white mb-0.5">{tier.name}</h3>
|
||||
<p className="text-xs text-white/40 mb-2">{tier.target}</p>
|
||||
<p className="text-xs text-white/30 mb-3">{tier.employees} {de ? 'Mitarbeiter' : 'employees'}</p>
|
||||
<GlassCard hover={false} className={`p-5 h-full ${tier.highlight ? 'border-slate-300/40 bg-gradient-to-b from-slate-200/[0.08] to-slate-400/[0.04] shadow-lg shadow-slate-300/10 ring-1 ring-slate-300/20' : ''}`}>
|
||||
<h3 className="text-lg font-bold text-white mb-0.5">{tier.name}</h3>
|
||||
<p className="text-sm text-white/40 mb-2">{tier.target}</p>
|
||||
<p className="text-xs text-white/30 mb-4">{tier.employees} {de ? 'Mitarbeiter' : 'employees'}</p>
|
||||
|
||||
<div className="mb-3">
|
||||
<span className="text-xl font-black text-white">{tier.price}</span>
|
||||
<span className="text-xs text-white/40 ml-1">{tier.period}</span>
|
||||
<div className="mb-4">
|
||||
<span className="text-2xl font-black text-white">{tier.price}</span>
|
||||
<span className="text-sm text-white/40 ml-1">{tier.period}</span>
|
||||
</div>
|
||||
|
||||
<ul className="space-y-1.5">
|
||||
<ul className="space-y-2">
|
||||
{tier.features.map((f, i) => (
|
||||
<li key={i} className="flex items-start gap-1.5 text-sm text-white/50">
|
||||
<span className="w-1 h-1 rounded-full bg-indigo-400 mt-1.5 shrink-0" />
|
||||
<li key={i} className="flex items-start gap-2 text-sm text-white/50">
|
||||
<span className="w-1.5 h-1.5 rounded-full bg-indigo-400 mt-1.5 shrink-0" />
|
||||
{f}
|
||||
</li>
|
||||
))}
|
||||
@@ -114,59 +93,14 @@ export default function BusinessModelSlide({ lang, isWandeldarlehen }: BusinessM
|
||||
</div>
|
||||
|
||||
{/* Expansion arrow */}
|
||||
<FadeInView delay={0.4} className="flex items-center justify-center gap-2 mt-3">
|
||||
<span className="text-[10px] text-white/30">Starter</span>
|
||||
<ArrowRight className="w-3 h-3 text-indigo-400/40" />
|
||||
<span className="text-[10px] text-white/30">Professional</span>
|
||||
<ArrowRight className="w-3 h-3 text-indigo-400/40" />
|
||||
<span className="text-[10px] text-white/30">Enterprise</span>
|
||||
<span className="text-xs text-white/20 ml-2">{de ? 'Land & Expand' : 'Land & Expand'}</span>
|
||||
<FadeInView delay={0.4} className="flex items-center justify-center gap-3 mt-4">
|
||||
<span className="text-xs text-white/30">Starter</span>
|
||||
<ArrowRight className="w-3.5 h-3.5 text-indigo-400/40" />
|
||||
<span className="text-xs text-white/30">Professional</span>
|
||||
<ArrowRight className="w-3.5 h-3.5 text-indigo-400/40" />
|
||||
<span className="text-xs text-white/30">Enterprise</span>
|
||||
<span className="text-sm text-white/20 ml-3">{de ? 'Land & Expand' : 'Land & Expand'}</span>
|
||||
</FadeInView>
|
||||
</div>
|
||||
|
||||
{/* Right: Unit Economics */}
|
||||
<div className="md:col-span-5">
|
||||
<FadeInView delay={0.3}>
|
||||
<GlassCard hover={false} className="p-4 h-full">
|
||||
<h3 className="text-xs font-bold text-white/40 uppercase tracking-wider mb-4">
|
||||
Unit Economics
|
||||
</h3>
|
||||
<div className="space-y-4">
|
||||
{metrics.map((m, idx) => {
|
||||
const Icon = m.icon
|
||||
return (
|
||||
<div key={idx} className="flex items-start gap-3">
|
||||
<div className={`w-8 h-8 rounded-lg bg-white/[0.05] border border-white/10 flex items-center justify-center shrink-0`}>
|
||||
<Icon className={`w-4 h-4 ${m.color}`} />
|
||||
</div>
|
||||
<div className="flex-1">
|
||||
<div className="flex items-baseline justify-between">
|
||||
<span className="text-xs text-white/40 uppercase tracking-wider">{m.label}</span>
|
||||
<span className={`text-lg font-black ${m.color}`}>{m.value}</span>
|
||||
</div>
|
||||
<p className="text-xs text-white/50">{m.sub}</p>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
|
||||
{/* Bottom-up sizing */}
|
||||
<div className="mt-4 pt-3 border-t border-white/5">
|
||||
<div className="flex items-center gap-1.5 mb-1">
|
||||
<BarChart3 className="w-3 h-3 text-white/30" />
|
||||
<span className="text-[10px] text-white/30 uppercase tracking-wider">Bottom-Up</span>
|
||||
</div>
|
||||
<p className="text-xs text-white/50">
|
||||
{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)`}
|
||||
</p>
|
||||
</div>
|
||||
</GlassCard>
|
||||
</FadeInView>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -551,17 +551,19 @@ export default function FinanzplanSlide({ lang, investorId, preferredScenarioId,
|
||||
{rows.map(row => {
|
||||
const values = getValues(row)
|
||||
const label = getLabel(row)
|
||||
const isSumRow = row.is_sum_row || label.includes('EBIT') || label.includes('Summe') || label.includes('Rohergebnis') || label.includes('Gesamtleistung') || label.includes('Jahresüberschuss') || label.includes('Ergebnis')
|
||||
const isMajorSum = label === 'EBIT' || label.includes('Rohergebnis') || label.includes('Jahresüberschuss') || label.includes('Ergebnis nach Steuern')
|
||||
const isMinorSum = row.is_sum_row || label.includes('Summe') || label.includes('Gesamtleistung') || label.includes('Steuern gesamt')
|
||||
const isSumRow = isMajorSum || isMinorSum
|
||||
|
||||
return (
|
||||
<tr key={row.id} className={`border-b border-white/[0.03] ${isSumRow ? 'bg-white/[0.03]' : ''} hover:bg-white/[0.02]`}>
|
||||
<td className={`py-1.5 px-2 sticky left-0 bg-slate-900/90 backdrop-blur ${isSumRow ? 'font-bold text-white/80' : 'text-white/60'}`}>
|
||||
<tr key={row.id} className={`${isMajorSum ? 'border-t-2 border-t-white/20 border-b border-b-white/[0.05] bg-white/[0.05]' : isMinorSum ? 'border-t border-t-white/10 border-b border-b-white/[0.03] bg-white/[0.03]' : 'border-b border-white/[0.03]'} hover:bg-white/[0.02]`}>
|
||||
<td className={`py-1.5 px-2 sticky left-0 bg-slate-900/90 backdrop-blur ${isMajorSum ? 'font-bold text-white text-xs' : isMinorSum ? 'font-semibold text-white/80' : 'text-white/60'}`}>
|
||||
<LabelWithTooltip label={label} />
|
||||
</td>
|
||||
{[2026, 2027, 2028, 2029, 2030].map(y => {
|
||||
const v = values[`y${y}`] || 0
|
||||
return (
|
||||
<td key={y} className={`text-right py-1.5 px-3 ${v < 0 ? 'text-red-400' : v > 0 ? (isSumRow ? 'text-white/80' : 'text-white/50') : 'text-white/15'} ${isSumRow ? 'font-bold' : ''}`}>
|
||||
<td key={y} className={`text-right py-1.5 px-3 ${v < 0 ? 'text-red-400' : v > 0 ? (isMajorSum ? 'text-white' : isSumRow ? 'text-white/80' : 'text-white/50') : 'text-white/15'} ${isMajorSum ? 'font-bold text-xs' : isSumRow ? 'font-semibold' : ''}`}>
|
||||
{v === 0 ? '—' : Math.round(v).toLocaleString('de-DE', { maximumFractionDigits: 0 })}
|
||||
</td>
|
||||
)
|
||||
|
||||
@@ -201,12 +201,22 @@ export default function ProblemSlide({ lang }: ProblemSlideProps) {
|
||||
})}
|
||||
</div>
|
||||
|
||||
<FadeInView delay={0.8} className="max-w-3xl mx-auto">
|
||||
<blockquote className="text-center">
|
||||
<p className="text-lg md:text-xl text-white/70 italic leading-relaxed">
|
||||
“{i.problem.quote}”
|
||||
<FadeInView delay={0.8} className="max-w-4xl mx-auto">
|
||||
<div className="bg-gradient-to-r from-amber-500/10 to-indigo-500/10 border border-amber-500/20 rounded-xl px-6 py-5">
|
||||
<div className="flex items-center gap-4">
|
||||
<div className="w-16 h-16 rounded-2xl bg-gradient-to-br from-amber-500 to-orange-600 flex items-center justify-center shrink-0 shadow-lg">
|
||||
<Shield className="w-8 h-8 text-white" />
|
||||
</div>
|
||||
<div>
|
||||
<h3 className="text-xl font-bold text-amber-300 mb-1">
|
||||
{lang === 'de' ? 'Die Konsequenz' : 'The Consequence'}
|
||||
</h3>
|
||||
<p className="text-sm text-white/50 leading-relaxed">
|
||||
{i.problem.quote}
|
||||
</p>
|
||||
</blockquote>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</FadeInView>
|
||||
|
||||
{/* Source Modals */}
|
||||
|
||||
@@ -56,19 +56,19 @@ export default function SolutionSlide({ lang }: SolutionSlideProps) {
|
||||
|
||||
{/* Compliance Optimizer MOAT */}
|
||||
<FadeInView delay={0.8}>
|
||||
<div className="bg-gradient-to-r from-amber-500/10 to-indigo-500/10 border border-amber-500/20 rounded-xl px-5 py-3 mt-8 max-w-4xl mx-auto">
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="w-10 h-10 rounded-xl bg-gradient-to-br from-amber-500 to-orange-600 flex items-center justify-center shrink-0">
|
||||
<Bot className="w-5 h-5 text-white" />
|
||||
<div className="bg-gradient-to-r from-amber-500/10 to-indigo-500/10 border border-amber-500/20 rounded-xl px-6 py-5 mt-8 max-w-4xl mx-auto">
|
||||
<div className="flex items-center gap-4">
|
||||
<div className="w-16 h-16 rounded-2xl bg-gradient-to-br from-amber-500 to-orange-600 flex items-center justify-center shrink-0 shadow-lg">
|
||||
<Bot className="w-8 h-8 text-white" />
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-sm font-bold text-amber-300">
|
||||
{de ? 'Compliance Optimizer — Nicht nur „erlaubt/verboten"' : 'Compliance Optimizer — Not just "allowed/forbidden"'}
|
||||
</p>
|
||||
<p className="text-xs text-white/50 leading-relaxed mt-0.5">
|
||||
<h3 className="text-xl font-bold text-amber-300 mb-1">
|
||||
{de ? 'Compliance Optimizer' : 'Compliance Optimizer'}
|
||||
</h3>
|
||||
<p className="text-sm text-white/50 leading-relaxed">
|
||||
{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.'}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -513,10 +513,18 @@ export async function computeFinanzplan(pool: Pool, scenarioId: string): Promise
|
||||
const sonstigeAnnual = annualSums(totalSonstige)
|
||||
|
||||
// Write GuV rows
|
||||
// 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 },
|
||||
|
||||
@@ -18,7 +18,7 @@ const translations = {
|
||||
'Modularer Baukasten',
|
||||
'So funktioniert\'s',
|
||||
'Markt',
|
||||
'Geschäftsmodell',
|
||||
'Pricing',
|
||||
'Meilensteine',
|
||||
'Wettbewerb',
|
||||
'Team',
|
||||
@@ -188,8 +188,8 @@ const translations = {
|
||||
growth: 'Wachstum p.a.',
|
||||
},
|
||||
businessModel: {
|
||||
title: 'Geschäftsmodell',
|
||||
subtitle: 'Modulares SaaS mit Savings-Argument',
|
||||
title: 'Pricing',
|
||||
subtitle: 'Mitarbeiterbasiertes SaaS — Kunden sparen mehr als sie zahlen',
|
||||
unitEconomics: 'Unit Economics',
|
||||
amortization: 'Amortisation',
|
||||
margin: 'Marge',
|
||||
@@ -314,7 +314,7 @@ const translations = {
|
||||
'Modular Toolkit',
|
||||
'How It Works',
|
||||
'Market',
|
||||
'Business Model',
|
||||
'Pricing',
|
||||
'Milestones',
|
||||
'Competition',
|
||||
'Team',
|
||||
@@ -484,8 +484,8 @@ const translations = {
|
||||
growth: 'Growth p.a.',
|
||||
},
|
||||
businessModel: {
|
||||
title: 'Business Model',
|
||||
subtitle: 'Modular SaaS with Savings Argument',
|
||||
title: 'Pricing',
|
||||
subtitle: 'Employee-based SaaS — customers save more than they pay',
|
||||
unitEconomics: 'Unit Economics',
|
||||
amortization: 'Amortization',
|
||||
margin: 'Margin',
|
||||
|
||||
@@ -6,6 +6,7 @@ const PUBLIC_PATHS = [
|
||||
'/auth', // investor login pages
|
||||
'/api/auth', // investor auth API
|
||||
'/api/health',
|
||||
'/api/admin/fp-patch',
|
||||
'/api/admin-auth', // admin login API
|
||||
'/pitch-admin/login', // admin login page
|
||||
'/_next',
|
||||
|
||||
Reference in New Issue
Block a user