/** * Vela Payroll Engine — Type Definitions * * Plug-and-play multi-jurisdiction payroll calculation. * Each country's tax rules implement the `TaxJurisdiction` interface. * The core `PayrollEngine` orchestrates the calculation. */ // ── Tax Brackets ───────────────────────────────────────────── export interface TaxBracket { min: number; // inclusive lower bound (annual) max: number | null; // exclusive upper bound, null for no cap rate: number; // tax rate as decimal (e.g. 0.19 = 19%) } // ── Deduction Line Items ───────────────────────────────────── export interface DeductionLineItem { name: string; amount: number; // positive = deducted from employee type: "tax" | "levy" | "insurance" | "pension" | "other"; description?: string; } // ── Employer Contributions ─────────────────────────────────── export interface EmployerContribution { name: string; amount: number; // employer-paid (NOT deducted from employee) description?: string; } // ── Payslip Calculation ────────────────────────────────────── export interface PayslipCalculation { jurisdiction: string; // e.g. "Australia", "United States" jurisdictionCode: string; // e.g. "AU", "US" payPeriod: "weekly" | "fortnightly" | "monthly"; grossAnnual: number; grossPay: number; // per pay period // Employee deductions (subtracted from gross to get net) deductions: DeductionLineItem[]; totalDeductions: number; // Employer contributions (additional cost to employer, NOT deducted) employerContributions: EmployerContribution[]; totalEmployerContributions: number; // Final netPay: number; // per pay period (after deductions) totalEmployerCost: number; // gross pay + employer contributions // Annual summary for reference annualTax: number; annualTotalDeductions: number; annualEmployerContributions: number; annualNetPay: number; } // ── Jurisdiction Plugin Interface ──────────────────────────── export interface TaxJurisdiction { /** Display name, e.g. "Australia" */ readonly name: string; /** ISO country code, e.g. "AU" */ readonly countryCode: string; /** Supported currency code */ readonly currency: string; /** * Calculate all deductions for a given gross annual salary. * Returns all employee deductions and employer contributions. */ calculate(grossAnnual: number): { deductions: DeductionLineItem[]; employerContributions: EmployerContribution[]; }; } // ── Input Types ────────────────────────────────────────────── export type PayFrequency = "weekly" | "fortnightly" | "monthly"; export interface PayrollInput { grossAnnual?: number; // annual gross salary (or derived from hourly) hourlyRate?: number; // if provided, grossAnnual = hourlyRate × hoursPerWeek × 52 hoursPerWeek?: number; // used with hourlyRate payFrequency: PayFrequency; jurisdiction: TaxJurisdiction; } // ── Utility Helpers ────────────────────────────────────────── /** Calculate gross annual from hourly rate + weekly hours. */ export function hourlyToAnnual(rate: number, hoursPerWeek: number): number { return rate * hoursPerWeek * 52; } /** Convert annual amount to per-period amount. */ export function toPeriod(annual: number, frequency: PayFrequency): number { switch (frequency) { case "weekly": return annual / 52; case "fortnightly": return annual / 26; case "monthly": return annual / 12; } } /** Calculate tax using progressive brackets. */ export function progressiveTaxCalc(annualIncome: number, brackets: TaxBracket[]): number { let tax = 0; for (const bracket of brackets) { if (annualIncome <= bracket.min) break; const taxableInBracket = bracket.max ? Math.min(annualIncome, bracket.max) - bracket.min : annualIncome - bracket.min; if (taxableInBracket > 0) { tax += taxableInBracket * bracket.rate; } } return Math.round(tax * 100) / 100; } /** Round to 2 decimal places. */ export function round2(n: number): number { return Math.round(n * 100) / 100; }