/** * Vela Payroll Engine — Core Calculation Engine * * The engine orchestrates the calculation flow: * 1. Accept PayrollInput (salary/hourly + frequency + jurisdiction) * 2. Derive gross annual from hourly if needed * 3. Ask the jurisdiction plugin for deductions and contributions * 4. Build and return a complete PayslipCalculation * * The engine is jurisdiction-agnostic — it delegates all tax * calculations to the injected TaxJurisdiction plugin. * This means new countries can be added without touching this code. */ import { PayslipCalculation, PayrollInput, DeductionLineItem, EmployerContribution, toPeriod, round2, } from "./types"; export class PayrollEngine { /** * Calculate a complete payslip. */ calculate(input: PayrollInput): PayslipCalculation { const jurisdiction = input.jurisdiction; // 1. Derive gross annual from hourly rate if provided let grossAnnual = input.grossAnnual ?? 0; if (input.hourlyRate && input.hoursPerWeek) { grossAnnual = input.hourlyRate * input.hoursPerWeek * 52; } grossAnnual = round2(grossAnnual); // 2. Ask the jurisdiction plugin for deductions & contributions const { deductions, employerContributions } = jurisdiction.calculate(grossAnnual); // 3. Calculate totals const totalDeductions = round2( deductions.reduce((sum, d) => sum + d.amount, 0) ); const totalEmployerContributions = round2( employerContributions.reduce((sum, c) => sum + c.amount, 0) ); // 4. Per-period amounts const grossPay = toPeriod(grossAnnual, input.payFrequency); const netPay = toPeriod(grossAnnual - totalDeductions, input.payFrequency); const totalEmployerCost = toPeriod( grossAnnual + totalEmployerContributions, input.payFrequency ); // 5. Build result return { jurisdiction: jurisdiction.name, jurisdictionCode: jurisdiction.countryCode, payPeriod: input.payFrequency, grossAnnual, grossPay: round2(grossPay), deductions, totalDeductions, employerContributions, totalEmployerContributions, netPay: round2(netPay), totalEmployerCost: round2(totalEmployerCost), // Annual summary annualTax: round2(totalDeductions), annualTotalDeductions: totalDeductions, annualEmployerContributions: totalEmployerContributions, annualNetPay: round2(grossAnnual - totalDeductions), }; } }