/** * Sheet Calculators — pure computation functions (no DB dependency) * * Used by the main engine to compute Personalkosten, Investitionen, * and aggregate monthly values. */ import { MonthlyValues, MONTHS, FOUNDING_MONTH, 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 }