feat: Finanzplan Phase 1-4 — DB + Engine + API + Spreadsheet-UI

Phase 1: DB-Schema (12 fp_* Tabellen) + Excel-Import (332 Zeilen importiert)
Phase 2: Compute Engine (Personal, Invest, Umsatz, Material, Betrieblich, Liquiditaet, GuV)
Phase 3: API (/api/finanzplan/ — GET sheets, PUT cells, POST compute)
Phase 4: Spreadsheet-UI (FinanzplanSlide als Annex mit Tab-Leiste, editierbarem Grid, Jahres-Navigation)

Zusaetzlich:
- Gruendungsdatum verschoben: Feb→Aug 2026 (DB + Personalkosten)
- Neue Preisstaffel: Startup/<10 MA ab 3.600 EUR/Jahr (14-Tage-Test, Kreditkarte)
- Competition-Slide: Pricing-Tiers aktualisiert

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Benjamin Admin
2026-03-26 19:26:46 +01:00
parent f514667ef9
commit a58cd16f01
16 changed files with 4589 additions and 5 deletions

View File

@@ -0,0 +1,412 @@
/**
* Finanzplan Compute Engine
*
* Dependency order:
* Personalkosten (independent inputs)
* Investitionen (independent inputs)
* Kunden → Umsatzerlöse → Materialaufwand
* Betriebliche Aufwendungen (needs Personal + Invest)
* Sonst. betr. Erträge (independent)
* Liquidität (aggregates all above)
* GuV (annual summary)
*/
import { Pool } from 'pg'
import {
MonthlyValues, AnnualValues, MONTHS, FOUNDING_MONTH,
emptyMonthly, sumMonthly, annualSums, dateToMonth, monthToDate,
FPPersonalkosten, FPInvestitionen, FPBetrieblicheAufwendungen,
FPLiquiditaet, FPComputeResult
} from './types'
// --- Sheet Calculators ---
export function computePersonalkosten(positions: FPPersonalkosten[]): FPPersonalkosten[] {
return positions.map(p => {
const brutto = emptyMonthly()
const sozial = emptyMonthly()
const total = emptyMonthly()
if (!p.start_date || !p.brutto_monthly) return { ...p, values_brutto: brutto, values_sozial: sozial, values_total: total }
const startDate = new Date(p.start_date)
const startM = dateToMonth(startDate.getFullYear(), startDate.getMonth() + 1)
const endM = p.end_date
? dateToMonth(new Date(p.end_date).getFullYear(), new Date(p.end_date).getMonth() + 1)
: MONTHS
for (let m = Math.max(1, startM); m <= Math.min(MONTHS, endM); m++) {
const { year } = monthToDate(m)
const yearsFromStart = year - startDate.getFullYear()
const raise = Math.pow(1 + (p.annual_raise_pct || 0) / 100, yearsFromStart)
const monthlyBrutto = Math.round(p.brutto_monthly * raise * 100) / 100
brutto[`m${m}`] = monthlyBrutto
sozial[`m${m}`] = Math.round(monthlyBrutto * (p.ag_sozial_pct || 20.425) / 100 * 100) / 100
total[`m${m}`] = brutto[`m${m}`] + sozial[`m${m}`]
}
return { ...p, values_brutto: brutto, values_sozial: sozial, values_total: total }
})
}
export function computeInvestitionen(items: FPInvestitionen[]): FPInvestitionen[] {
return items.map(item => {
const invest = emptyMonthly()
const afa = emptyMonthly()
if (!item.purchase_date || !item.purchase_amount) return { ...item, values_invest: invest, values_afa: afa }
const d = new Date(item.purchase_date)
const purchaseM = dateToMonth(d.getFullYear(), d.getMonth() + 1)
if (purchaseM >= 1 && purchaseM <= MONTHS) {
invest[`m${purchaseM}`] = item.purchase_amount
}
// AfA (linear depreciation)
if (item.afa_years && item.afa_years > 0) {
const afaMonths = item.afa_years * 12
const monthlyAfa = Math.round(item.purchase_amount / afaMonths * 100) / 100
for (let m = purchaseM; m < purchaseM + afaMonths && m <= MONTHS; m++) {
if (m >= 1) afa[`m${m}`] = monthlyAfa
}
} else {
// GWG: full depreciation in purchase month
if (purchaseM >= 1 && purchaseM <= MONTHS) {
afa[`m${purchaseM}`] = item.purchase_amount
}
}
return { ...item, values_invest: invest, values_afa: afa }
})
}
function sumRows(rows: { values: MonthlyValues }[]): MonthlyValues {
const result = emptyMonthly()
for (const row of rows) {
for (let m = 1; m <= MONTHS; m++) {
result[`m${m}`] += row.values[`m${m}`] || 0
}
}
return result
}
function sumField(rows: { [key: string]: MonthlyValues }[], field: string): MonthlyValues {
const result = emptyMonthly()
for (const row of rows) {
const v = row[field] as MonthlyValues
if (!v) continue
for (let m = 1; m <= MONTHS; m++) {
result[`m${m}`] += v[`m${m}`] || 0
}
}
return result
}
// --- Main Engine ---
export async function computeFinanzplan(pool: Pool, scenarioId: string): Promise<FPComputeResult> {
// 1. Load all editable data from DB
const [
personalRows,
investRows,
betriebRows,
liquidRows,
kundenSummary,
umsatzRows,
materialRows,
] = await Promise.all([
pool.query('SELECT * FROM fp_personalkosten WHERE scenario_id = $1 ORDER BY sort_order', [scenarioId]),
pool.query('SELECT * FROM fp_investitionen WHERE scenario_id = $1 ORDER BY sort_order', [scenarioId]),
pool.query('SELECT * FROM fp_betriebliche_aufwendungen WHERE scenario_id = $1 ORDER BY sort_order', [scenarioId]),
pool.query('SELECT * FROM fp_liquiditaet WHERE scenario_id = $1 ORDER BY sort_order', [scenarioId]),
pool.query('SELECT * FROM fp_kunden_summary WHERE scenario_id = $1 ORDER BY sort_order', [scenarioId]),
pool.query('SELECT * FROM fp_umsatzerloese WHERE scenario_id = $1 ORDER BY sort_order', [scenarioId]),
pool.query('SELECT * FROM fp_materialaufwand WHERE scenario_id = $1 ORDER BY sort_order', [scenarioId]),
])
// 2. Compute Personalkosten
const personal = computePersonalkosten(personalRows.rows as FPPersonalkosten[])
const totalBrutto = sumField(personal as any, 'values_brutto')
const totalSozial = sumField(personal as any, 'values_sozial')
const totalPersonal = sumField(personal as any, 'values_total')
const headcount = emptyMonthly()
for (let m = 1; m <= MONTHS; m++) {
headcount[`m${m}`] = personal.filter(p => (p.values_total[`m${m}`] || 0) > 0).length
}
// Write computed values back to DB
for (const p of personal) {
await pool.query(
'UPDATE fp_personalkosten SET values_brutto = $1, values_sozial = $2, values_total = $3 WHERE id = $4',
[JSON.stringify(p.values_brutto), JSON.stringify(p.values_sozial), JSON.stringify(p.values_total), p.id]
)
}
// 3. Compute Investitionen
const invest = computeInvestitionen(investRows.rows as FPInvestitionen[])
const totalInvest = sumField(invest as any, 'values_invest')
const totalAfa = sumField(invest as any, 'values_afa')
for (const i of invest) {
await pool.query(
'UPDATE fp_investitionen SET values_invest = $1, values_afa = $2 WHERE id = $3',
[JSON.stringify(i.values_invest), JSON.stringify(i.values_afa), i.id]
)
}
// 4. Umsatzerlöse (quantity × price)
const prices = (umsatzRows.rows as FPUmsatzerloese[]).filter(r => r.section === 'price')
const quantities = (umsatzRows.rows as FPUmsatzerloese[]).filter(r => r.section === 'quantity')
const revenueRows = (umsatzRows.rows as FPUmsatzerloese[]).filter(r => r.section === 'revenue')
const totalRevenue = emptyMonthly()
// Revenue = quantity × price for each module
for (const rev of revenueRows) {
if (rev.row_label === 'GESAMTUMSATZ') continue
const qty = quantities.find(q => q.row_label === rev.row_label)
const price = prices.find(p => p.row_label === rev.row_label)
if (qty && price) {
for (let m = 1; m <= MONTHS; m++) {
const v = (qty.values[`m${m}`] || 0) * (price.values[`m${m}`] || 0)
rev.values[`m${m}`] = Math.round(v * 100) / 100
totalRevenue[`m${m}`] += rev.values[`m${m}`]
}
await pool.query('UPDATE fp_umsatzerloese SET values = $1 WHERE id = $2', [JSON.stringify(rev.values), rev.id])
}
}
// Update GESAMTUMSATZ
const gesamtUmsatz = revenueRows.find(r => r.row_label === 'GESAMTUMSATZ')
if (gesamtUmsatz) {
await pool.query('UPDATE fp_umsatzerloese SET values = $1 WHERE id = $2', [JSON.stringify(totalRevenue), gesamtUmsatz.id])
}
// 5. Materialaufwand (quantity × unit_cost) — simplified
const matCosts = (materialRows.rows as FPMaterialaufwand[]).filter(r => r.section === 'cost')
const matUnitCosts = (materialRows.rows as FPMaterialaufwand[]).filter(r => r.section === 'unit_cost')
const totalMaterial = emptyMonthly()
for (const cost of matCosts) {
if (cost.row_label === 'SUMME') continue
const uc = matUnitCosts.find(u => u.row_label === cost.row_label)
const qty = quantities.find(q => q.row_label === cost.row_label)
if (uc && qty) {
for (let m = 1; m <= MONTHS; m++) {
const v = (qty.values[`m${m}`] || 0) * (uc.values[`m${m}`] || 0)
cost.values[`m${m}`] = Math.round(v * 100) / 100
totalMaterial[`m${m}`] += cost.values[`m${m}`]
}
await pool.query('UPDATE fp_materialaufwand SET values = $1 WHERE id = $2', [JSON.stringify(cost.values), cost.id])
}
}
const matSumme = matCosts.find(r => r.row_label === 'SUMME')
if (matSumme) {
await pool.query('UPDATE fp_materialaufwand SET values = $1 WHERE id = $2', [JSON.stringify(totalMaterial), matSumme.id])
}
// 6. Betriebliche Aufwendungen — compute sum rows
const betrieb = betriebRows.rows as FPBetrieblicheAufwendungen[]
// Update Personalkosten row
const persBetrieb = betrieb.find(r => r.row_label === 'Personalkosten')
if (persBetrieb) {
await pool.query('UPDATE fp_betriebliche_aufwendungen SET values = $1 WHERE id = $2', [JSON.stringify(totalPersonal), persBetrieb.id])
persBetrieb.values = totalPersonal
}
// Update Abschreibungen row
const abrBetrieb = betrieb.find(r => r.row_label === 'Abschreibungen')
if (abrBetrieb) {
await pool.query('UPDATE fp_betriebliche_aufwendungen SET values = $1 WHERE id = $2', [JSON.stringify(totalAfa), abrBetrieb.id])
abrBetrieb.values = totalAfa
}
// Compute category sums
const categories = ['steuern', 'versicherungen', 'besondere', 'marketing', 'sonstige']
for (const cat of categories) {
const sumRow = betrieb.find(r => r.category === cat && r.is_sum_row)
const detailRows = betrieb.filter(r => r.category === cat && !r.is_sum_row)
if (sumRow && detailRows.length > 0) {
const s = sumRows(detailRows)
await pool.query('UPDATE fp_betriebliche_aufwendungen SET values = $1 WHERE id = $2', [JSON.stringify(s), sumRow.id])
sumRow.values = s
}
}
// Summe sonstige (ohne Personal, Abschreibungen)
const sonstSumme = betrieb.find(r => r.row_label.includes('Summe sonstige'))
if (sonstSumme) {
const nonPersonNonAbr = betrieb.filter(r =>
r.row_label !== 'Personalkosten' && r.row_label !== 'Abschreibungen' &&
!r.row_label.includes('Summe sonstige') && !r.row_label.includes('Gesamtkosten') &&
!r.is_sum_row && r.category !== 'personal' && r.category !== 'abschreibungen'
)
const s = sumRows(nonPersonNonAbr)
await pool.query('UPDATE fp_betriebliche_aufwendungen SET values = $1 WHERE id = $2', [JSON.stringify(s), sonstSumme.id])
sonstSumme.values = s
}
// Gesamtkosten
const gesamtBetrieb = betrieb.find(r => r.row_label.includes('Gesamtkosten'))
const totalSonstige = sonstSumme?.values || emptyMonthly()
if (gesamtBetrieb) {
const g = emptyMonthly()
for (let m = 1; m <= MONTHS; m++) {
g[`m${m}`] = (totalPersonal[`m${m}`] || 0) + (totalAfa[`m${m}`] || 0) + (totalSonstige[`m${m}`] || 0)
}
await pool.query('UPDATE fp_betriebliche_aufwendungen SET values = $1 WHERE id = $2', [JSON.stringify(g), gesamtBetrieb.id])
gesamtBetrieb.values = g
}
// 7. Liquidität
const liquid = liquidRows.rows as FPLiquiditaet[]
const findLiq = (label: string) => liquid.find(r => r.row_label === label)
// Computed rows
const liqUmsatz = findLiq('Umsatzerloese')
if (liqUmsatz) {
await pool.query('UPDATE fp_liquiditaet SET values = $1 WHERE id = $2', [JSON.stringify(totalRevenue), liqUmsatz.id])
liqUmsatz.values = totalRevenue
}
const liqMaterial = findLiq('Materialaufwand')
if (liqMaterial) {
await pool.query('UPDATE fp_liquiditaet SET values = $1 WHERE id = $2', [JSON.stringify(totalMaterial), liqMaterial.id])
liqMaterial.values = totalMaterial
}
const liqPersonal = findLiq('Personalkosten')
if (liqPersonal) {
await pool.query('UPDATE fp_liquiditaet SET values = $1 WHERE id = $2', [JSON.stringify(totalPersonal), liqPersonal.id])
liqPersonal.values = totalPersonal
}
const liqSonstige = findLiq('Sonstige Kosten')
if (liqSonstige) {
await pool.query('UPDATE fp_liquiditaet SET values = $1 WHERE id = $2', [JSON.stringify(totalSonstige), liqSonstige.id])
liqSonstige.values = totalSonstige
}
const liqInvest = findLiq('Investitionen')
if (liqInvest) {
await pool.query('UPDATE fp_liquiditaet SET values = $1 WHERE id = $2', [JSON.stringify(totalInvest), liqInvest.id])
liqInvest.values = totalInvest
}
// Compute sums and rolling balance
const sumEin = findLiq('Summe EINZAHLUNGEN')
const sumAus = findLiq('Summe AUSZAHLUNGEN')
const uebVorInv = findLiq('UEBERSCHUSS VOR INVESTITIONEN')
const uebVorEnt = findLiq('UEBERSCHUSS VOR ENTNAHMEN')
const ueberschuss = findLiq('UEBERSCHUSS')
const kontostand = findLiq('Kontostand zu Beginn des Monats')
const liquiditaet = findLiq('LIQUIDITAET')
const einzahlungen = ['Umsatzerloese', 'Sonst. betriebl. Ertraege', 'Anzahlungen', 'Neuer Eigenkapitalzugang', 'Erhaltenes Fremdkapital']
const auszahlungen = ['Materialaufwand', 'Personalkosten', 'Sonstige Kosten', 'Kreditrueckzahlungen', 'Umsatzsteuer', 'Gewerbesteuer', 'Koerperschaftsteuer']
if (sumEin) {
const s = emptyMonthly()
for (const label of einzahlungen) {
const row = findLiq(label)
if (row) for (let m = 1; m <= MONTHS; m++) s[`m${m}`] += row.values[`m${m}`] || 0
}
await pool.query('UPDATE fp_liquiditaet SET values = $1 WHERE id = $2', [JSON.stringify(s), sumEin.id])
sumEin.values = s
}
if (sumAus) {
const s = emptyMonthly()
for (const label of auszahlungen) {
const row = findLiq(label)
if (row) for (let m = 1; m <= MONTHS; m++) s[`m${m}`] += row.values[`m${m}`] || 0
}
await pool.query('UPDATE fp_liquiditaet SET values = $1 WHERE id = $2', [JSON.stringify(s), sumAus.id])
sumAus.values = s
}
// Überschüsse und Kontostand
if (uebVorInv && sumEin && sumAus) {
const s = emptyMonthly()
for (let m = 1; m <= MONTHS; m++) s[`m${m}`] = (sumEin.values[`m${m}`] || 0) - (sumAus.values[`m${m}`] || 0)
await pool.query('UPDATE fp_liquiditaet SET values = $1 WHERE id = $2', [JSON.stringify(s), uebVorInv.id])
uebVorInv.values = s
}
if (uebVorEnt && uebVorInv && liqInvest) {
const s = emptyMonthly()
for (let m = 1; m <= MONTHS; m++) s[`m${m}`] = (uebVorInv.values[`m${m}`] || 0) - (liqInvest.values[`m${m}`] || 0)
await pool.query('UPDATE fp_liquiditaet SET values = $1 WHERE id = $2', [JSON.stringify(s), uebVorEnt.id])
uebVorEnt.values = s
}
const entnahmen = findLiq('Kapitalentnahmen/Ausschuettungen')
if (ueberschuss && uebVorEnt && entnahmen) {
const s = emptyMonthly()
for (let m = 1; m <= MONTHS; m++) s[`m${m}`] = (uebVorEnt.values[`m${m}`] || 0) - (entnahmen.values[`m${m}`] || 0)
await pool.query('UPDATE fp_liquiditaet SET values = $1 WHERE id = $2', [JSON.stringify(s), ueberschuss.id])
ueberschuss.values = s
}
// Rolling Kontostand
if (kontostand && liquiditaet && ueberschuss) {
const ks = emptyMonthly()
const lq = emptyMonthly()
for (let m = 1; m <= MONTHS; m++) {
ks[`m${m}`] = m === 1 ? 0 : lq[`m${m - 1}`]
lq[`m${m}`] = ks[`m${m}`] + (ueberschuss.values[`m${m}`] || 0)
}
await pool.query('UPDATE fp_liquiditaet SET values = $1 WHERE id = $2', [JSON.stringify(ks), kontostand.id])
await pool.query('UPDATE fp_liquiditaet SET values = $1 WHERE id = $2', [JSON.stringify(lq), liquiditaet.id])
kontostand.values = ks
liquiditaet.values = lq
}
// 8. GuV — compute annual values
const guv: AnnualValues[] = []
const umsatzAnnual = annualSums(totalRevenue)
const materialAnnual = annualSums(totalMaterial)
const personalBruttoAnnual = annualSums(totalBrutto)
const personalSozialAnnual = annualSums(totalSozial)
const personalAnnual = annualSums(totalPersonal)
const afaAnnual = annualSums(totalAfa)
const sonstigeAnnual = annualSums(totalSonstige)
// Write GuV rows
const guvUpdates: { label: string; values: AnnualValues }[] = [
{ label: 'Umsatzerloese', values: umsatzAnnual },
{ label: 'Gesamtleistung', values: umsatzAnnual },
{ label: 'Summe Materialaufwand', values: materialAnnual },
{ label: 'Loehne und Gehaelter', 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
const ebit: AnnualValues = {}
for (let y = 2026; y <= 2030; y++) {
const k = `y${y}`
ebit[k] = (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'])
await pool.query('UPDATE fp_guv SET values = $1 WHERE scenario_id = $2 AND row_label = $3', [JSON.stringify(ebit), scenarioId, 'Ergebnis nach Steuern'])
await pool.query('UPDATE fp_guv SET values = $1 WHERE scenario_id = $2 AND row_label = $3', [JSON.stringify(ebit), scenarioId, 'Jahresueberschuss'])
return {
personalkosten: { total_brutto: totalBrutto, total_sozial: totalSozial, total: totalPersonal, positions: personal, headcount },
investitionen: { total_invest: totalInvest, total_afa: totalAfa, items: invest },
umsatzerloese: { total: totalRevenue },
materialaufwand: { total: totalMaterial },
betriebliche: { total_sonstige: totalSonstige, total_gesamt: gesamtBetrieb?.values || emptyMonthly() },
liquiditaet: { rows: liquid, endstand: liquiditaet?.values || emptyMonthly() },
guv: [ebit],
}
}
// Import to fix type errors
type FPUmsatzerloese = import('./types').FPUmsatzerloese
type FPMaterialaufwand = import('./types').FPMaterialaufwand

View File

@@ -0,0 +1,210 @@
/**
* Finanzplan Types — mirrors the fp_* database tables.
* Monthly values stored as Record<string, number> with keys m1..m60.
* Annual values stored with keys y2026..y2030.
*/
export type MonthlyValues = Record<string, number> // m1..m60
export type AnnualValues = Record<string, number> // y2026..y2030
export const MONTHS = 60
export const START_YEAR = 2026
export const END_YEAR = 2030
export const FOUNDING_MONTH = 8 // August 2026 = m8
// Month index (1-based) to year/month
export function monthToDate(m: number): { year: number; month: number } {
const year = START_YEAR + Math.floor((m - 1) / 12)
const month = ((m - 1) % 12) + 1
return { year, month }
}
// Year/month to month index (1-based)
export function dateToMonth(year: number, month: number): number {
return (year - START_YEAR) * 12 + month
}
export function emptyMonthly(): MonthlyValues {
const v: MonthlyValues = {}
for (let m = 1; m <= MONTHS; m++) v[`m${m}`] = 0
return v
}
export function sumMonthly(values: MonthlyValues, fromM: number, toM: number): number {
let s = 0
for (let m = fromM; m <= toM; m++) s += values[`m${m}`] || 0
return s
}
export function annualSums(values: MonthlyValues): AnnualValues {
const r: AnnualValues = {}
for (let y = START_YEAR; y <= END_YEAR; y++) {
const startM = dateToMonth(y, 1)
const endM = dateToMonth(y, 12)
r[`y${y}`] = sumMonthly(values, startM, endM)
}
return r
}
// --- DB row types ---
export interface FPScenario {
id: string
name: string
description?: string
is_default: boolean
}
export interface FPPersonalkosten {
id: number
scenario_id: string
person_name: string
person_nr?: string
position?: string
start_date?: string
end_date?: string
brutto_monthly: number
annual_raise_pct: number
ag_sozial_pct: number
values_brutto: MonthlyValues
values_sozial: MonthlyValues
values_total: MonthlyValues
sort_order: number
}
export interface FPBetrieblicheAufwendungen {
id: number
scenario_id: string
category: string
row_label: string
row_index: number
is_editable: boolean
is_sum_row: boolean
formula_desc?: string
values: MonthlyValues
sort_order: number
}
export interface FPInvestitionen {
id: number
scenario_id: string
item_name: string
category?: string
purchase_amount: number
purchase_date?: string
afa_years?: number
afa_end_date?: string
values_invest: MonthlyValues
values_afa: MonthlyValues
sort_order: number
}
export interface FPLiquiditaet {
id: number
scenario_id: string
row_label: string
row_type: string
is_editable: boolean
formula_desc?: string
values: MonthlyValues
sort_order: number
}
export interface FPGuV {
id: number
scenario_id: string
row_label: string
row_index: number
is_sum_row: boolean
formula_desc?: string
values: AnnualValues
sort_order: number
}
export interface FPKunden {
id: number
scenario_id: string
segment_name: string
segment_index: number
row_label: string
row_index: number
percentage?: number
formula_type?: string
is_editable: boolean
values: MonthlyValues
sort_order: number
}
export interface FPUmsatzerloese {
id: number
scenario_id: string
section: string // 'revenue', 'quantity', 'price'
row_label: string
row_index: number
is_editable: boolean
values: MonthlyValues
sort_order: number
}
export interface FPMaterialaufwand {
id: number
scenario_id: string
section: string // 'cost', 'quantity', 'unit_cost'
row_label: string
row_index: number
is_editable: boolean
values: MonthlyValues
sort_order: number
}
// --- Compute result ---
export interface FPComputeResult {
personalkosten: {
total_brutto: MonthlyValues
total_sozial: MonthlyValues
total: MonthlyValues
positions: FPPersonalkosten[]
headcount: MonthlyValues
}
investitionen: {
total_invest: MonthlyValues
total_afa: MonthlyValues
items: FPInvestitionen[]
}
umsatzerloese: {
total: MonthlyValues
}
materialaufwand: {
total: MonthlyValues
}
betriebliche: {
total_sonstige: MonthlyValues
total_gesamt: MonthlyValues
}
liquiditaet: {
rows: FPLiquiditaet[]
endstand: MonthlyValues
}
guv: AnnualValues[]
}
export interface SheetMeta {
name: string
label_de: string
label_en: string
rows: number
editable_rows: number
}
export const SHEET_LIST: SheetMeta[] = [
{ name: 'kunden', label_de: 'Kunden', label_en: 'Customers', rows: 0, editable_rows: 0 },
{ name: 'umsatzerloese', label_de: 'Umsatzerloese', label_en: 'Revenue', rows: 0, editable_rows: 0 },
{ name: 'materialaufwand', label_de: 'Materialaufwand', label_en: 'Material Costs', rows: 0, editable_rows: 0 },
{ name: 'personalkosten', label_de: 'Personalkosten', label_en: 'Personnel', rows: 0, editable_rows: 0 },
{ name: 'betriebliche', label_de: 'Betriebliche Aufwendungen', label_en: 'Operating Expenses', rows: 0, editable_rows: 0 },
{ name: 'investitionen', label_de: 'Investitionen', label_en: 'Investments', rows: 0, editable_rows: 0 },
{ name: 'sonst_ertraege', label_de: 'Sonst. Ertraege', label_en: 'Other Income', rows: 0, editable_rows: 0 },
{ name: 'liquiditaet', label_de: 'Liquiditaet', label_en: 'Cash Flow', rows: 0, editable_rows: 0 },
{ name: 'guv', label_de: 'GuV', label_en: 'P&L', rows: 0, editable_rows: 0 },
]

View File

@@ -31,6 +31,7 @@ const translations = {
'Anhang: Engineering',
'Anhang: KI-Pipeline',
'Anhang: SDK Demo',
'Anhang: Finanzplan',
],
executiveSummary: {
title: 'Executive Summary',
@@ -321,6 +322,7 @@ const translations = {
'Appendix: Engineering',
'Appendix: AI Pipeline',
'Appendix: SDK Demo',
'Appendix: Financial Plan',
],
executiveSummary: {
title: 'Executive Summary',

View File

@@ -24,6 +24,7 @@ export const SLIDE_ORDER: SlideId[] = [
'annex-engineering',
'annex-aipipeline',
'annex-sdk-demo',
'annex-finanzplan',
]
export const TOTAL_SLIDES = SLIDE_ORDER.length

View File

@@ -224,3 +224,4 @@ export type SlideId =
| 'annex-engineering'
| 'annex-aipipeline'
| 'annex-sdk-demo'
| 'annex-finanzplan'