Files
breakpilot-core/pitch-deck/lib/finanzplan/engine-sheets.ts
Benjamin Admin 28aa74b4b0
Some checks failed
Build pitch-deck / build-push-deploy (push) Failing after 1m13s
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 49s
CI / test-python-voice (push) Successful in 38s
CI / test-bqas (push) Successful in 31s
Merge remote-tracking branch 'gitea/main'
# Conflicts:
#	pitch-deck/components/slides/MilestonesSlide.tsx
#	pitch-deck/lib/finanzplan/engine.ts
2026-04-27 13:14:54 +02:00

107 lines
3.5 KiB
TypeScript

/**
* Sheet Calculators — pure computation functions (no DB dependency)
*
* Used by the main engine to compute Personalkosten, Investitionen,
* and aggregate monthly values.
*/
import {
MonthlyValues, MONTHS,
emptyMonthly, dateToMonth, monthToDate,
FPPersonalkosten, FPInvestitionen,
} from './types'
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)
brutto[`m${m}`] = monthlyBrutto
sozial[`m${m}`] = Math.round(monthlyBrutto * (p.ag_sozial_pct || 20.425) / 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)
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 }
})
}
export 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
}
export 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
}
/**
* Compute headcount per month from personal positions.
*/
export function computeHeadcount(personal: FPPersonalkosten[]): MonthlyValues {
const headcount = emptyMonthly()
for (let m = 1; m <= MONTHS; m++) {
headcount[`m${m}`] = personal.filter(p => (p.values_total[`m${m}`] || 0) > 0).length
}
return headcount
}