@classytic/payroll 2.7.5 → 2.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/utils/money.ts","../../src/utils/calculation.ts","../../src/utils/date.ts","../../src/core/config.ts","../../src/calculators/prorating.calculator.ts","../../src/calculators/attendance.calculator.ts","../../src/calculators/salary.calculator.ts"],"names":["periodWorkingDays"],"mappings":"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","file":"index.js","sourcesContent":["/**\n * Money calculation utilities for payroll compliance\n *\n * Uses banker's rounding (round half to even) to prevent systematic bias\n * in financial calculations, as recommended for payroll systems.\n *\n * PRECISION: All functions default to 2 decimal places (cents/paise precision)\n * to maintain accuracy in payroll calculations. Amounts are stored as floating\n * point numbers with decimal precision (e.g., 1000.50 for $1,000.50).\n *\n * ## Design Decision: Floating Point Storage\n *\n * This module uses floating-point numbers (IEEE 754 double precision) for money\n * storage rather than integer minor units (cents) or MongoDB Decimal128.\n *\n * ### Tradeoffs:\n *\n * **Why floating point:**\n * - Simpler API (developers think in dollars, not cents)\n * - More intuitive JSON serialization (100.50 vs 10050)\n * - Sufficient precision for payroll (15 significant digits)\n * - Banker's rounding mitigates cumulative drift\n *\n * **Why NOT integer cents:**\n * - Would require breaking schema changes\n * - All consumer code needs conversion (amount / 100)\n * - More verbose for reporting/display\n *\n * **Why NOT Decimal128:**\n * - MongoDB-specific, reduces portability\n * - Requires special BSON handling in application code\n * - Overkill for typical payroll amounts (< $10M/employee/year)\n *\n * ### Mitigation:\n * - All calculations go through roundMoney() with banker's rounding\n * - Final amounts always rounded to 2 decimal places before storage\n * - Aggregations use MongoDB $round operator for consistency\n *\n * @see https://en.wikipedia.org/wiki/Banker%27s_rounding\n */\n\n/**\n * Banker's rounding (round half to even) with decimal precision\n *\n * When rounding 0.5, rounds to the nearest even number:\n * - 2.5 → 2 (even)\n * - 3.5 → 4 (even)\n * - 4.5 → 4 (even)\n *\n * This prevents systematic bias in cumulative rounding that occurs\n * with standard rounding (always up), which is critical for payroll compliance.\n *\n * @param value - The number to round\n * @param decimals - Number of decimal places (default: 2 for cent precision)\n * @returns The rounded value\n */\nexport function roundMoney(value: number, decimals = 2): number {\n const multiplier = Math.pow(10, decimals);\n const scaled = value * multiplier;\n const fraction = scaled - Math.floor(scaled);\n\n // If exactly 0.5 (within tolerance), round to nearest even\n // Use 1e-10 tolerance to handle floating-point imprecision\n if (Math.abs(fraction - 0.5) < 1e-10) {\n const floor = Math.floor(scaled);\n const rounded = floor % 2 === 0 ? floor : floor + 1;\n return rounded / multiplier;\n }\n\n // Otherwise use standard rounding\n return Math.round(scaled) / multiplier;\n}\n\n/**\n * Round money with validation for negative values\n *\n * @param value - The number to round\n * @param decimals - Number of decimal places (default: 2 for cent precision)\n * @returns The rounded value (never negative for deductions)\n */\nexport function roundMoneyPositive(value: number, decimals = 2): number {\n return Math.max(0, roundMoney(value, decimals));\n}\n\n/**\n * Calculate percentage of an amount with banker's rounding\n *\n * @param amount - Base amount\n * @param percentage - Percentage (e.g., 10 for 10%)\n * @param decimals - Decimal places to round to (default: 2 for cent precision)\n * @returns Rounded percentage amount\n */\nexport function percentageOf(amount: number, percentage: number, decimals = 2): number {\n return roundMoney((amount * percentage) / 100, decimals);\n}\n\n/**\n * Prorate an amount by a ratio with banker's rounding\n *\n * @param amount - Base amount\n * @param ratio - Proration ratio (0 to 1)\n * @param decimals - Decimal places (default: 2 for cent precision)\n * @returns Prorated amount\n */\nexport function prorateAmount(amount: number, ratio: number, decimals = 2): number {\n return roundMoney(amount * ratio, decimals);\n}\n","/**\n * @classytic/payroll - Calculation Utilities\n *\n * Pure, functional, composable financial calculations\n * No side effects, highly testable\n */\n\nimport type {\n Allowance,\n Deduction,\n Compensation,\n TaxCalculationResult,\n CompensationBreakdownResult,\n} from '../types.js';\nimport { roundMoney } from './money.js';\n\n// ============================================================================\n// Basic Math Operations\n// ============================================================================\n\n/**\n * Sum array of numbers\n */\nexport function sum(numbers: number[]): number {\n return numbers.reduce((total, n) => total + n, 0);\n}\n\n/**\n * Sum by property\n */\nexport function sumBy<T>(items: T[], getter: (item: T) => number): number {\n return items.reduce((total, item) => total + getter(item), 0);\n}\n\n/**\n * Sum allowances\n */\nexport function sumAllowances(allowances: Array<{ amount: number }>): number {\n return sumBy(allowances, (a) => a.amount);\n}\n\n/**\n * Sum deductions\n */\nexport function sumDeductions(deductions: Array<{ amount: number }>): number {\n return sumBy(deductions, (d) => d.amount);\n}\n\n/**\n * ROUNDING POLICY FOR FINANCIAL CALCULATIONS\n *\n * Monetary amounts are stored as floating point numbers in major units with\n * decimal precision (e.g., 1000.50 for $1,000.50 or ₹1,000.50).\n *\n * PRECISION: All calculations preserve 2 decimal places (cent/paise precision)\n * to maintain accuracy required for payroll compliance.\n *\n * Rounding Rules:\n * 1. Banker's Rounding (Round Half to Even): Used for fair rounding over many transactions\n * 2. All intermediate calculations maintain full precision\n * 3. Final amounts rounded to 2 decimals using banker's rounding\n * 4. Tax calculations use banker's rounding for compliance\n *\n * Example:\n * Input: 1000.50 base + 15% tax\n * Calculation: 1000.50 * 0.15 = 150.075 → rounds to 150.08 (banker's rounding to 2 decimals)\n * Result: Tax = 150.08\n *\n * @see https://en.wikipedia.org/wiki/Rounding#Round_half_to_even\n */\n\n/**\n * Banker's Rounding (Round Half to Even) - Integer precision\n *\n * Rounds to the nearest integer using banker's rounding (round half to even).\n * This prevents systematic bias in rounding over many transactions.\n *\n * Uses epsilon check for safe floating-point comparison.\n *\n * Examples:\n * 0.5 → 0 (even)\n * 1.5 → 2 (even)\n * 2.5 → 2 (even)\n * 3.5 → 4 (even)\n *\n * @param value - The number to round\n * @returns Rounded integer\n * @note For money calculations with decimal precision, use `roundMoney()` instead\n */\nexport function bankersRound(value: number): number {\n const floor = Math.floor(value);\n const fraction = value - floor;\n\n // Use epsilon check for safer floating-point comparison\n if (Math.abs(fraction - 0.5) < Number.EPSILON) {\n // If halfway, round to even\n return floor % 2 === 0 ? floor : floor + 1;\n }\n\n // Otherwise use standard rounding\n return Math.round(value);\n}\n\n/**\n * Apply percentage to amount with banker's rounding (2 decimal precision)\n *\n * @param amount - Amount in major units (e.g., dollars, rupees)\n * @param percentage - Percentage to apply (e.g., 15 for 15%)\n * @param decimals - Decimal places for precision (default: 2 for cent precision)\n * @returns Result in major units, properly rounded to 2 decimals\n * @note Uses banker's rounding (round half to even) to preserve cent precision.\n * Equivalent to percentageOf() from money.ts.\n */\nexport function applyPercentage(amount: number, percentage: number, decimals = 2): number {\n // Use centralized roundMoney for consistent banker's rounding across codebase\n const result = (amount * percentage) / 100;\n return roundMoney(result, decimals);\n}\n\n/**\n * Calculate percentage of total\n */\nexport function calculatePercentage(part: number, total: number): number {\n return total > 0 ? bankersRound((part / total) * 100) : 0;\n}\n\n/**\n * Round to decimal places using banker's rounding\n */\nexport function roundTo(value: number, decimals = 2): number {\n const factor = Math.pow(10, decimals);\n return bankersRound(value * factor) / factor;\n}\n\n// ============================================================================\n// Salary Calculations\n// ============================================================================\n\n/**\n * Calculate gross salary from base and allowances\n */\nexport function calculateGross(\n baseAmount: number,\n allowances: Array<{ amount: number }>\n): number {\n return baseAmount + sumAllowances(allowances);\n}\n\n/**\n * Calculate net salary from gross and deductions\n */\nexport function calculateNet(\n gross: number,\n deductions: Array<{ amount: number }>\n): number {\n return Math.max(0, gross - sumDeductions(deductions));\n}\n\n/**\n * Calculate total compensation\n */\nexport function calculateTotalCompensation(\n baseAmount: number,\n allowances: Array<{ amount: number }>,\n deductions: Array<{ amount: number }>\n): { gross: number; net: number; deductions: number } {\n const gross = calculateGross(baseAmount, allowances);\n const totalDeductions = sumDeductions(deductions);\n const net = calculateNet(gross, deductions);\n return { gross, net, deductions: totalDeductions };\n}\n\n// ============================================================================\n// Allowance & Deduction Calculation\n// ============================================================================\n\n/**\n * Calculate allowance amount (handles percentage-based)\n */\nexport function calculateAllowanceAmount(\n allowance: Pick<Allowance, 'amount' | 'isPercentage' | 'value'>,\n baseAmount: number\n): number {\n if (allowance.isPercentage && allowance.value !== undefined) {\n return applyPercentage(baseAmount, allowance.value);\n }\n return allowance.amount;\n}\n\n/**\n * Calculate deduction amount (handles percentage-based)\n */\nexport function calculateDeductionAmount(\n deduction: Pick<Deduction, 'amount' | 'isPercentage' | 'value'>,\n baseAmount: number\n): number {\n if (deduction.isPercentage && deduction.value !== undefined) {\n return applyPercentage(baseAmount, deduction.value);\n }\n return deduction.amount;\n}\n\n/**\n * Calculate all allowances with their actual amounts\n */\nexport function calculateAllowances(\n allowances: Allowance[],\n baseAmount: number\n): Array<Allowance & { calculatedAmount: number }> {\n return allowances.map((allowance) => ({\n ...allowance,\n calculatedAmount: calculateAllowanceAmount(allowance, baseAmount),\n }));\n}\n\n/**\n * Calculate all deductions with their actual amounts\n */\nexport function calculateDeductions(\n deductions: Deduction[],\n baseAmount: number\n): Array<Deduction & { calculatedAmount: number }> {\n return deductions.map((deduction) => ({\n ...deduction,\n calculatedAmount: calculateDeductionAmount(deduction, baseAmount),\n }));\n}\n\n// ============================================================================\n// Compensation Breakdown\n// ============================================================================\n\n/**\n * Calculate full compensation breakdown\n */\nexport function calculateCompensationBreakdown(\n compensation: Pick<Compensation, 'baseAmount' | 'allowances' | 'deductions'>\n): CompensationBreakdownResult {\n const { baseAmount, allowances = [], deductions = [] } = compensation;\n\n const calculatedAllowances = calculateAllowances(allowances, baseAmount);\n const calculatedDeductions = calculateDeductions(deductions, baseAmount);\n\n const grossAmount =\n baseAmount + sumBy(calculatedAllowances, (a) => a.calculatedAmount);\n const netAmount =\n grossAmount - sumBy(calculatedDeductions, (d) => d.calculatedAmount);\n\n return {\n baseAmount,\n allowances: calculatedAllowances,\n deductions: calculatedDeductions,\n grossAmount,\n netAmount: Math.max(0, netAmount),\n };\n}\n\n// ============================================================================\n// Tax Calculations\n// ============================================================================\n\n/**\n * Apply tax brackets to calculate tax\n *\n * Uses banker's rounding for compliance (rounds to 2 decimal places).\n * Consistent with all other money calculations in the system.\n */\nexport function applyTaxBrackets(\n amount: number,\n brackets: Array<{ min: number; max: number; rate: number }>\n): number {\n let tax = 0;\n\n for (const bracket of brackets) {\n if (amount > bracket.min) {\n const taxableAmount = Math.min(amount, bracket.max) - bracket.min;\n tax += taxableAmount * bracket.rate;\n }\n }\n\n // Use roundMoney for consistency with all other money calculations\n return roundMoney(tax);\n}\n\n/**\n * Calculate tax with result\n */\nexport function calculateTax(\n amount: number,\n brackets: Array<{ min: number; max: number; rate: number }>\n): TaxCalculationResult {\n const tax = applyTaxBrackets(amount, brackets);\n return {\n gross: amount,\n tax,\n net: amount - tax,\n };\n}\n\n// ============================================================================\n// Overtime Calculations\n// ============================================================================\n\n/**\n * Calculate overtime pay\n */\nexport function calculateOvertime(\n hourlyRate: number,\n overtimeHours: number,\n multiplier = 1.5\n): number {\n return Math.round(hourlyRate * overtimeHours * multiplier);\n}\n\n/**\n * Calculate hourly rate from monthly salary\n */\nexport function calculateHourlyRate(\n monthlySalary: number,\n hoursPerMonth = 176 // 44 hours/week * 4 weeks\n): number {\n return Math.round(monthlySalary / hoursPerMonth);\n}\n\n/**\n * Calculate daily rate from monthly salary\n */\nexport function calculateDailyRate(\n monthlySalary: number,\n daysPerMonth = 22\n): number {\n return Math.round(monthlySalary / daysPerMonth);\n}\n\n// ============================================================================\n// Default Export\n// ============================================================================\n\nexport default {\n sum,\n sumBy,\n sumAllowances,\n sumDeductions,\n applyPercentage,\n calculatePercentage,\n roundTo,\n calculateGross,\n calculateNet,\n calculateTotalCompensation,\n calculateAllowanceAmount,\n calculateDeductionAmount,\n calculateAllowances,\n calculateDeductions,\n calculateCompensationBreakdown,\n applyTaxBrackets,\n calculateTax,\n calculateOvertime,\n calculateHourlyRate,\n calculateDailyRate,\n};\n\n","/**\n * @classytic/payroll - Date Utilities\n *\n * Pure, composable, testable date operations\n * No side effects, no mutations\n */\n\nimport type { PayPeriodInfo, PaymentFrequency } from '../types.js';\n\n// ============================================================================\n// Date Arithmetic\n// ============================================================================\n\n/**\n * Add days to a date\n */\nexport function addDays(date: Date, days: number): Date {\n const result = new Date(date);\n result.setDate(result.getDate() + days);\n return result;\n}\n\n/**\n * Add months to a date\n */\nexport function addMonths(date: Date, months: number): Date {\n const result = new Date(date);\n result.setMonth(result.getMonth() + months);\n return result;\n}\n\n/**\n * Add years to a date\n */\nexport function addYears(date: Date, years: number): Date {\n const result = new Date(date);\n result.setFullYear(result.getFullYear() + years);\n return result;\n}\n\n/**\n * Subtract days from a date\n */\nexport function subDays(date: Date, days: number): Date {\n return addDays(date, -days);\n}\n\n/**\n * Subtract months from a date\n */\nexport function subMonths(date: Date, months: number): Date {\n return addMonths(date, -months);\n}\n\n// ============================================================================\n// Date Boundaries\n// ============================================================================\n\n/**\n * Get the start of a month\n */\nexport function startOfMonth(date: Date): Date {\n const result = new Date(date);\n result.setDate(1);\n result.setHours(0, 0, 0, 0);\n return result;\n}\n\n/**\n * Get the end of a month\n */\nexport function endOfMonth(date: Date): Date {\n const result = new Date(date);\n result.setMonth(result.getMonth() + 1, 0);\n result.setHours(23, 59, 59, 999);\n return result;\n}\n\n/**\n * Get the start of a year\n */\nexport function startOfYear(date: Date): Date {\n const result = new Date(date);\n result.setMonth(0, 1);\n result.setHours(0, 0, 0, 0);\n return result;\n}\n\n/**\n * Get the end of a year\n */\nexport function endOfYear(date: Date): Date {\n const result = new Date(date);\n result.setMonth(11, 31);\n result.setHours(23, 59, 59, 999);\n return result;\n}\n\n/**\n * Get the start of a day\n */\nexport function startOfDay(date: Date): Date {\n const result = new Date(date);\n result.setHours(0, 0, 0, 0);\n return result;\n}\n\n/**\n * Get the end of a day\n */\nexport function endOfDay(date: Date): Date {\n const result = new Date(date);\n result.setHours(23, 59, 59, 999);\n return result;\n}\n\n// ============================================================================\n// Date Differences\n// ============================================================================\n\n/**\n * Calculate difference in days between two dates\n */\nexport function diffInDays(start: Date, end: Date): number {\n return Math.ceil(\n (new Date(end).getTime() - new Date(start).getTime()) / (1000 * 60 * 60 * 24)\n );\n}\n\n/**\n * Calculate difference in months between two dates\n */\nexport function diffInMonths(start: Date, end: Date): number {\n const startDate = new Date(start);\n const endDate = new Date(end);\n return (\n (endDate.getFullYear() - startDate.getFullYear()) * 12 +\n (endDate.getMonth() - startDate.getMonth())\n );\n}\n\n/**\n * Calculate difference in years between two dates\n */\nexport function diffInYears(start: Date, end: Date): number {\n return Math.floor(diffInMonths(start, end) / 12);\n}\n\n// Aliases for backwards compatibility\nexport const daysBetween = diffInDays;\nexport const monthsBetween = diffInMonths;\n\n// ============================================================================\n// Day Type Checks\n// ============================================================================\n\n/**\n * Check if date is a weekday (Mon-Fri)\n */\nexport function isWeekday(date: Date): boolean {\n const day = new Date(date).getDay();\n return day >= 1 && day <= 5;\n}\n\n/**\n * Check if date is a weekend (Sat-Sun)\n */\nexport function isWeekend(date: Date): boolean {\n const day = new Date(date).getDay();\n return day === 0 || day === 6;\n}\n\n/**\n * Get day of week (0=Sunday, 6=Saturday)\n */\nexport function getDayOfWeek(date: Date): number {\n return new Date(date).getDay();\n}\n\n/**\n * Get day name\n */\nexport function getDayName(date: Date): string {\n const days = [\n 'Sunday',\n 'Monday',\n 'Tuesday',\n 'Wednesday',\n 'Thursday',\n 'Friday',\n 'Saturday',\n ];\n return days[getDayOfWeek(date)];\n}\n\n// ============================================================================\n// Pay Period Functions\n// ============================================================================\n\n/**\n * Get pay period for a given month and year\n */\nexport function getPayPeriod(month: number, year: number): PayPeriodInfo {\n const startDate = new Date(year, month - 1, 1);\n return {\n month,\n year,\n startDate: startOfMonth(startDate),\n endDate: endOfMonth(startDate),\n };\n}\n\n/**\n * Get current pay period\n */\nexport function getCurrentPeriod(date = new Date()): { year: number; month: number } {\n const d = new Date(date);\n return {\n year: d.getFullYear(),\n month: d.getMonth() + 1,\n };\n}\n\n/**\n * Get working days in a month\n */\nexport function getWorkingDaysInMonth(year: number, month: number): number {\n const start = new Date(year, month - 1, 1);\n const end = endOfMonth(start);\n let count = 0;\n \n const current = new Date(start);\n while (current <= end) {\n if (isWeekday(current)) {\n count++;\n }\n current.setDate(current.getDate() + 1);\n }\n \n return count;\n}\n\n/**\n * Get total days in a month\n */\nexport function getDaysInMonth(year: number, month: number): number {\n return new Date(year, month, 0).getDate();\n}\n\n// ============================================================================\n// Employment Date Functions\n// ============================================================================\n\n/**\n * Calculate probation end date\n */\nexport function calculateProbationEnd(\n hireDate: Date,\n probationMonths: number\n): Date | null {\n if (!probationMonths || probationMonths <= 0) return null;\n return addMonths(hireDate, probationMonths);\n}\n\n/**\n * Check if employee is on probation\n */\nexport function isOnProbation(\n probationEndDate: Date | null | undefined,\n now = new Date()\n): boolean {\n if (!probationEndDate) return false;\n return now < new Date(probationEndDate);\n}\n\n/**\n * Calculate years of service\n */\nexport function calculateYearsOfService(\n hireDate: Date,\n terminationDate?: Date | null\n): number {\n const end = terminationDate || new Date();\n const days = diffInDays(hireDate, end);\n return Math.max(0, Math.floor((days / 365.25) * 10) / 10);\n}\n\n// ============================================================================\n// Range Functions\n// ============================================================================\n\n/**\n * Check if a date is within a range\n */\nexport function isDateInRange(date: Date, start: Date, end: Date): boolean {\n const checkDate = new Date(date);\n return checkDate >= new Date(start) && checkDate <= new Date(end);\n}\n\n/**\n * Check if an item with effectiveFrom/effectiveTo dates is effective for a given period.\n *\n * Used for filtering allowances, deductions, and other time-bounded compensation items.\n * An item is considered effective if its date range overlaps with the period.\n *\n * @param item - Object with optional effectiveFrom and effectiveTo dates\n * @param periodStart - Start of the period to check\n * @param periodEnd - End of the period to check\n * @returns true if the item is effective during any part of the period\n *\n * @example\n * ```typescript\n * const allowance = { effectiveFrom: new Date('2024-01-01'), effectiveTo: null };\n * const periodStart = new Date('2024-03-01');\n * const periodEnd = new Date('2024-03-31');\n *\n * isEffectiveForPeriod(allowance, periodStart, periodEnd); // true\n * ```\n */\nexport function isEffectiveForPeriod(\n item: { effectiveFrom?: Date | null; effectiveTo?: Date | null },\n periodStart: Date,\n periodEnd: Date\n): boolean {\n const effectiveFrom = item.effectiveFrom ? new Date(item.effectiveFrom) : new Date(0);\n const effectiveTo = item.effectiveTo ? new Date(item.effectiveTo) : new Date('2099-12-31');\n\n // Item is effective if its range overlaps with the period\n return effectiveFrom <= periodEnd && effectiveTo >= periodStart;\n}\n\n/**\n * Get date range for a pay period\n */\nexport function getPayPeriodDateRange(\n month: number,\n year: number\n): { start: Date; end: Date } {\n const period = getPayPeriod(month, year);\n return { start: period.startDate, end: period.endDate };\n}\n\n// ============================================================================\n// Formatting Functions\n// ============================================================================\n\n/**\n * Format date for database storage\n */\nexport function formatDateForDB(date: Date): string {\n if (!date) return '';\n return new Date(date).toISOString();\n}\n\n/**\n * Parse date from database\n */\nexport function parseDBDate(dateString: string): Date | null {\n if (!dateString) return null;\n return new Date(dateString);\n}\n\n/**\n * Format period as string (e.g., \"01/2025\")\n */\nexport function formatPeriod({ month, year }: { month: number; year: number }): string {\n return `${String(month).padStart(2, '0')}/${year}`;\n}\n\n/**\n * Parse period string back to object\n */\nexport function parsePeriod(periodString: string): { month: number; year: number } {\n const [month, year] = periodString.split('/').map(Number);\n return { month, year };\n}\n\n/**\n * Format month name\n */\nexport function getMonthName(month: number): string {\n const months = [\n 'January', 'February', 'March', 'April', 'May', 'June',\n 'July', 'August', 'September', 'October', 'November', 'December',\n ];\n return months[month - 1] || '';\n}\n\n/**\n * Format short month name\n */\nexport function getShortMonthName(month: number): string {\n const months = [\n 'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun',\n 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec',\n ];\n return months[month - 1] || '';\n}\n\n// ============================================================================\n// Default Export\n// ============================================================================\n\nexport default {\n addDays,\n addMonths,\n addYears,\n subDays,\n subMonths,\n startOfMonth,\n endOfMonth,\n startOfYear,\n endOfYear,\n startOfDay,\n endOfDay,\n diffInDays,\n diffInMonths,\n diffInYears,\n daysBetween,\n monthsBetween,\n isWeekday,\n isWeekend,\n getDayOfWeek,\n getDayName,\n getPayPeriod,\n getCurrentPeriod,\n getWorkingDaysInMonth,\n getDaysInMonth,\n calculateProbationEnd,\n isOnProbation,\n calculateYearsOfService,\n isDateInRange,\n isEffectiveForPeriod,\n getPayPeriodDateRange,\n formatDateForDB,\n parseDBDate,\n formatPeriod,\n parsePeriod,\n getMonthName,\n getShortMonthName,\n};\n\n","/**\n * @classytic/payroll - Configuration & Calculation Utilities\n *\n * DESIGN PRINCIPLES:\n * 1. Accept data, don't manage it\n * 2. Pure functions - easy to test, no side effects\n * 3. Smart defaults that work out of the box\n * 4. Override at operation time when needed\n *\n * The payroll package CALCULATES, it doesn't STORE calendars/holidays.\n * Your app manages that data and passes it when needed.\n */\n\n// ============================================================================\n// Types\n// ============================================================================\n\n/** Work schedule configuration */\nexport interface WorkSchedule {\n /** Working days (0=Sun, 1=Mon, ..., 6=Sat). Default: Mon-Fri */\n workingDays: number[];\n /** Hours per work day. Default: 8 */\n hoursPerDay: number;\n}\n\n/** Options passed when processing payroll */\nexport interface PayrollProcessingOptions {\n /** Holidays in this period (from YOUR app's holiday model) */\n holidays?: Date[];\n /** Override work schedule for this operation */\n workSchedule?: Partial<WorkSchedule>;\n /** Skip tax calculation */\n skipTax?: boolean;\n /** Skip proration (pay full amount regardless of hire/termination date) */\n skipProration?: boolean;\n /** Skip attendance deduction */\n skipAttendance?: boolean;\n}\n\n/** Working days calculation result */\nexport interface WorkingDaysResult {\n /** Total calendar days in period */\n totalDays: number;\n /** Working days (excluding weekends and holidays) */\n workingDays: number;\n /** Weekend days */\n weekends: number;\n /** Holiday count */\n holidays: number;\n}\n\n/** Proration calculation result */\nexport interface ProrationResult {\n /** Proration ratio (0-1) */\n ratio: number;\n /** Reason for proration */\n reason: 'full' | 'new_hire' | 'termination' | 'both';\n /** Whether salary should be prorated */\n isProrated: boolean;\n}\n\n/** Tax calculation result */\nexport interface TaxResult {\n /** Tax amount */\n amount: number;\n /** Effective tax rate */\n effectiveRate: number;\n}\n\n/** Attendance data (from YOUR attendance system) */\nexport interface AttendanceInput {\n /**\n * Expected work days in period.\n * If not provided, derived from employee's workSchedule and period dates.\n */\n expectedDays?: number;\n /** Actual days worked */\n actualDays: number;\n}\n\n/** Complete salary calculation result */\nexport interface SalaryCalculationResult {\n /** Original base salary */\n baseSalary: number;\n /** Prorated base salary */\n proratedBase: number;\n /** Total allowances */\n totalAllowances: number;\n /** Total deductions (excluding tax) */\n totalDeductions: number;\n /** Attendance deduction */\n attendanceDeduction: number;\n /** Gross salary (prorated base + allowances) */\n grossSalary: number;\n /** Tax amount */\n taxAmount: number;\n /** Net salary (gross - all deductions - tax) */\n netSalary: number;\n /** Proration details */\n proration: ProrationResult;\n /** Working days details */\n workingDays: WorkingDaysResult;\n /** Itemized breakdown */\n breakdown: {\n allowances: Array<{ type: string; amount: number; taxable: boolean }>;\n deductions: Array<{ type: string; amount: number }>;\n };\n}\n\n// ============================================================================\n// Default Configuration\n// ============================================================================\n\n/**\n * Default tax brackets (US federal example)\n * For multi-jurisdiction support, use the jurisdiction system instead\n */\nexport const DEFAULT_TAX_BRACKETS: Array<{ min: number; max: number; rate: number }> = [\n { min: 0, max: 10000, rate: 0.10 },\n { min: 10000, max: 40000, rate: 0.12 },\n { min: 40000, max: 85000, rate: 0.22 },\n { min: 85000, max: 165000, rate: 0.24 },\n { min: 165000, max: 215000, rate: 0.32 },\n { min: 215000, max: 540000, rate: 0.35 },\n { min: 540000, max: Infinity, rate: 0.37 },\n];\n\nexport const DEFAULT_WORK_SCHEDULE: WorkSchedule = {\n workingDays: [1, 2, 3, 4, 5], // Monday to Friday\n hoursPerDay: 8,\n};\n\n// ============================================================================\n// Pure Calculation Functions\n// ============================================================================\n\n/**\n * Count working days in a date range\n *\n * @example\n * const result = countWorkingDays(\n * new Date('2024-03-01'),\n * new Date('2024-03-31'),\n * { workingDays: [1,2,3,4,5], holidays: companyHolidays }\n * );\n */\nexport function countWorkingDays(\n startDate: Date,\n endDate: Date,\n options: {\n workingDays?: number[];\n holidays?: Date[];\n } = {}\n): WorkingDaysResult {\n const workDays = options.workingDays || DEFAULT_WORK_SCHEDULE.workingDays;\n const holidaySet = new Set(\n (options.holidays || []).map(d => new Date(d).toDateString())\n );\n\n let totalDays = 0;\n let workingDays = 0;\n let holidays = 0;\n let weekends = 0;\n\n const current = new Date(startDate);\n current.setHours(0, 0, 0, 0);\n const end = new Date(endDate);\n end.setHours(0, 0, 0, 0);\n\n while (current <= end) {\n totalDays++;\n const isHoliday = holidaySet.has(current.toDateString());\n const isWorkDay = workDays.includes(current.getDay());\n\n if (isHoliday) {\n holidays++;\n } else if (isWorkDay) {\n workingDays++;\n } else {\n weekends++;\n }\n\n current.setDate(current.getDate() + 1);\n }\n\n return { totalDays, workingDays, weekends, holidays };\n}\n\n/**\n * Calculate proration ratio for partial months\n *\n * @example\n * const proration = calculateProration(\n * employee.hireDate,\n * employee.terminationDate,\n * periodStart,\n * periodEnd\n * );\n */\nexport function calculateProration(\n hireDate: Date,\n terminationDate: Date | null | undefined,\n periodStart: Date,\n periodEnd: Date\n): ProrationResult {\n const hire = new Date(hireDate);\n hire.setHours(0, 0, 0, 0);\n const term = terminationDate ? new Date(terminationDate) : null;\n if (term) term.setHours(0, 0, 0, 0);\n const start = new Date(periodStart);\n start.setHours(0, 0, 0, 0);\n const end = new Date(periodEnd);\n end.setHours(0, 0, 0, 0);\n\n // Employee not active in this period\n if (hire > end || (term && term < start)) {\n return { ratio: 0, reason: 'full', isProrated: true };\n }\n\n // Effective dates within the period\n const effectiveStart = hire > start ? hire : start;\n const effectiveEnd = term && term < end ? term : end;\n\n // Calculate days\n const totalDays = Math.ceil((end.getTime() - start.getTime()) / 86400000) + 1;\n const actualDays = Math.ceil((effectiveEnd.getTime() - effectiveStart.getTime()) / 86400000) + 1;\n const ratio = Math.min(1, Math.max(0, actualDays / totalDays));\n\n // Determine reason\n const isNewHire = hire > start;\n const isTermination = term !== null && term < end;\n \n let reason: ProrationResult['reason'] = 'full';\n if (isNewHire && isTermination) {\n reason = 'both';\n } else if (isNewHire) {\n reason = 'new_hire';\n } else if (isTermination) {\n reason = 'termination';\n }\n\n return { ratio, reason, isProrated: ratio < 1 };\n}\n\n/**\n * Internal simple tax calculation\n * For multi-jurisdiction tax, use the jurisdiction system\n * @internal\n */\nfunction calculateSimpleTax(\n monthlyIncome: number,\n brackets: Array<{ min: number; max: number; rate: number }> = DEFAULT_TAX_BRACKETS\n): TaxResult {\n const annualIncome = monthlyIncome * 12;\n let annualTax = 0;\n\n for (const bracket of brackets) {\n if (annualIncome <= bracket.min) continue;\n const taxableInBracket = Math.min(annualIncome, bracket.max) - bracket.min;\n annualTax += taxableInBracket * bracket.rate;\n }\n\n const monthlyTax = Math.round(annualTax / 12);\n const effectiveRate = monthlyIncome > 0 ? monthlyTax / monthlyIncome : 0;\n\n return { amount: monthlyTax, effectiveRate };\n}\n\n/**\n * Calculate attendance deduction\n *\n * @example\n * const deduction = calculateAttendanceDeduction(22, 20, dailyRate);\n */\nexport function calculateAttendanceDeduction(\n expectedDays: number,\n actualDays: number,\n dailyRate: number,\n maxDeductionPercent = 100\n): number {\n const absentDays = Math.max(0, expectedDays - actualDays);\n const deduction = Math.round(absentDays * dailyRate);\n const maxDeduction = Math.round((dailyRate * expectedDays * maxDeductionPercent) / 100);\n return Math.min(deduction, maxDeduction);\n}\n\n/**\n * Calculate complete salary breakdown\n *\n * This is the main function for salary calculation.\n * Pass all data from YOUR app, get back complete breakdown.\n *\n * Note: Uses simple tax calculation. For multi-jurisdiction tax,\n * use the jurisdiction system instead.\n *\n * @example\n * const result = calculateSalaryBreakdown({\n * baseSalary: 100000,\n * hireDate: employee.hireDate,\n * terminationDate: employee.terminationDate,\n * periodStart: new Date('2024-03-01'),\n * periodEnd: new Date('2024-03-31'),\n * allowances: [{ type: 'housing', amount: 20000, taxable: true }],\n * deductions: [{ type: 'provident_fund', amount: 5000 }],\n * options: { holidays: companyHolidays },\n * attendance: { expectedDays: 22, actualDays: 20 },\n * });\n */\nexport function calculateSalaryBreakdown(params: {\n baseSalary: number;\n hireDate: Date;\n terminationDate?: Date | null;\n periodStart: Date;\n periodEnd: Date;\n allowances?: Array<{ type: string; amount: number; taxable?: boolean }>;\n deductions?: Array<{ type: string; amount: number }>;\n options?: PayrollProcessingOptions;\n attendance?: AttendanceInput;\n}): SalaryCalculationResult {\n const {\n baseSalary,\n hireDate,\n terminationDate,\n periodStart,\n periodEnd,\n allowances = [],\n deductions = [],\n options = {},\n attendance,\n } = params;\n\n // 1. Calculate working days\n const workSchedule = { ...DEFAULT_WORK_SCHEDULE, ...options.workSchedule };\n const workingDays = countWorkingDays(periodStart, periodEnd, {\n workingDays: workSchedule.workingDays,\n holidays: options.holidays,\n });\n\n // 2. Calculate proration\n const proration = options.skipProration\n ? { ratio: 1, reason: 'full' as const, isProrated: false }\n : calculateProration(hireDate, terminationDate, periodStart, periodEnd);\n\n // 3. Prorate base salary\n const proratedBase = Math.round(baseSalary * proration.ratio);\n\n // 4. Process allowances (prorate)\n const processedAllowances = allowances.map(a => ({\n type: a.type,\n amount: Math.round(a.amount * proration.ratio),\n taxable: a.taxable ?? true,\n }));\n const totalAllowances = processedAllowances.reduce((sum, a) => sum + a.amount, 0);\n\n // 5. Process deductions (prorate)\n const processedDeductions = deductions.map(d => ({\n type: d.type,\n amount: Math.round(d.amount * proration.ratio),\n }));\n\n // 6. Attendance deduction\n let attendanceDeduction = 0;\n if (attendance && !options.skipAttendance && workingDays.workingDays > 0) {\n // Use expectedDays from attendance if provided, otherwise use working days from schedule\n const expectedDays = attendance.expectedDays ?? workingDays.workingDays;\n const dailyRate = proratedBase / expectedDays;\n attendanceDeduction = calculateAttendanceDeduction(\n expectedDays,\n attendance.actualDays,\n dailyRate\n );\n if (attendanceDeduction > 0) {\n processedDeductions.push({ type: 'attendance', amount: attendanceDeduction });\n }\n }\n\n // 7. Calculate gross salary\n const grossSalary = proratedBase + totalAllowances;\n\n // 8. Calculate tax (simple calculation - for multi-jurisdiction, use jurisdiction system)\n let taxAmount = 0;\n if (!options.skipTax) {\n const taxableAllowances = processedAllowances\n .filter(a => a.taxable)\n .reduce((sum, a) => sum + a.amount, 0);\n const taxableIncome = proratedBase + taxableAllowances;\n const taxResult = calculateSimpleTax(taxableIncome);\n taxAmount = taxResult.amount;\n if (taxAmount > 0) {\n processedDeductions.push({ type: 'tax', amount: taxAmount });\n }\n }\n\n // 9. Calculate net salary\n const totalDeductions = processedDeductions\n .filter(d => d.type !== 'tax') // Exclude only tax, include attendance\n .reduce((sum, d) => sum + d.amount, 0);\n const netSalary = grossSalary - totalDeductions - taxAmount;\n\n return {\n baseSalary,\n proratedBase,\n totalAllowances,\n totalDeductions,\n attendanceDeduction,\n grossSalary,\n taxAmount,\n netSalary,\n proration,\n workingDays,\n breakdown: {\n allowances: processedAllowances,\n deductions: processedDeductions,\n },\n };\n}\n\n/**\n * Get pay period dates for a given month\n *\n * @example\n * const period = getPayPeriod(3, 2024); // March 2024\n */\nexport function getPayPeriod(\n month: number,\n year: number,\n payDay = 28\n): { startDate: Date; endDate: Date; payDate: Date } {\n const startDate = new Date(year, month - 1, 1);\n const endDate = new Date(year, month, 0); // Last day of month\n const payDate = new Date(year, month - 1, Math.min(payDay, endDate.getDate()));\n return { startDate, endDate, payDate };\n}\n\n","/**\n * @classytic/payroll - Pro-Rating Calculator\n *\n * Pure functions for salary pro-rating calculations.\n * No database dependencies - can be used client-side!\n *\n * Handles:\n * - Mid-period hires\n * - Mid-period terminations\n * - Working days (not calendar days)\n * - Holidays exclusion\n *\n * @packageDocumentation\n */\n\nimport { countWorkingDays } from '../core/config.js';\n\n// ============================================================================\n// Types\n// ============================================================================\n\n/**\n * Input for pro-rating calculation\n */\nexport interface ProRatingInput {\n /**\n * Employee hire date\n */\n hireDate: Date;\n\n /**\n * Employee termination date (null if still employed)\n */\n terminationDate: Date | null;\n\n /**\n * Start of the salary period\n */\n periodStart: Date;\n\n /**\n * End of the salary period\n */\n periodEnd: Date;\n\n /**\n * Working days of the week (1=Monday, 7=Sunday)\n * @default [1, 2, 3, 4, 5] (Monday-Friday)\n */\n workingDays: number[];\n\n /**\n * Public holidays to exclude from working days\n * @default []\n */\n holidays?: Date[];\n}\n\n/**\n * Result of pro-rating calculation\n */\nexport interface ProRatingResult {\n /**\n * Whether the salary needs to be pro-rated\n */\n isProRated: boolean;\n\n /**\n * Pro-rating ratio (0-1)\n * 1 = full salary, 0.5 = half salary, etc.\n */\n ratio: number;\n\n /**\n * Total working days in the period\n */\n periodWorkingDays: number;\n\n /**\n * Working days the employee was actually employed\n */\n effectiveWorkingDays: number;\n\n /**\n * Effective start date (max of hire date and period start)\n */\n effectiveStart: Date;\n\n /**\n * Effective end date (min of termination date and period end)\n */\n effectiveEnd: Date;\n}\n\n// ============================================================================\n// Pure Functions\n// ============================================================================\n\n/**\n * Calculate pro-rating for mid-period hires/terminations\n *\n * This function uses WORKING DAYS (not calendar days) for accurate pro-rating.\n *\n * @example\n * ```typescript\n * // Employee hired on March 15th, process March salary\n * const result = calculateProRating({\n * hireDate: new Date('2024-03-15'),\n * terminationDate: null,\n * periodStart: new Date('2024-03-01'),\n * periodEnd: new Date('2024-03-31'),\n * workingDays: [1, 2, 3, 4, 5], // Mon-Fri\n * });\n * \n * console.log(result);\n * // {\n * // isProRated: true,\n * // ratio: 0.64, // Worked 14 out of 22 working days\n * // periodWorkingDays: 22,\n * // effectiveWorkingDays: 14\n * // }\n * ```\n *\n * @param input - Pro-rating calculation parameters\n * @returns Pro-rating result with ratio and working days breakdown\n *\n * @pure This function has no side effects and doesn't access external state\n */\nexport function calculateProRating(input: ProRatingInput): ProRatingResult {\n const { hireDate, terminationDate, periodStart, periodEnd, workingDays, holidays = [] } = input;\n\n const hire = new Date(hireDate);\n const termination = terminationDate ? new Date(terminationDate) : null;\n\n // Determine the actual start and end dates for this employee in this period\n const effectiveStart = hire > periodStart ? hire : periodStart;\n const effectiveEnd = termination && termination < periodEnd ? termination : periodEnd;\n\n // If employee wasn't active during this period at all\n if (effectiveStart > periodEnd || (termination && termination < periodStart)) {\n const periodWorkingDays = countWorkingDays(periodStart, periodEnd, { workingDays, holidays }).workingDays;\n return {\n isProRated: true,\n ratio: 0,\n periodWorkingDays,\n effectiveWorkingDays: 0,\n effectiveStart: periodStart,\n effectiveEnd: periodStart, // Effectively zero days\n };\n }\n\n // Calculate working days for the full period\n const periodWorkingDays = countWorkingDays(periodStart, periodEnd, { workingDays, holidays }).workingDays;\n\n // Calculate working days the employee was actually employed\n const effectiveWorkingDays = countWorkingDays(effectiveStart, effectiveEnd, { workingDays, holidays }).workingDays;\n\n // Calculate ratio\n const ratio = periodWorkingDays > 0 \n ? Math.min(1, Math.max(0, effectiveWorkingDays / periodWorkingDays)) \n : 0;\n\n // Is pro-rated if ratio is less than 1\n const isProRated = ratio < 1;\n\n return {\n isProRated,\n ratio,\n periodWorkingDays,\n effectiveWorkingDays,\n effectiveStart,\n effectiveEnd,\n };\n}\n\n/**\n * Calculate pro-rated amount from base amount and ratio\n *\n * @example\n * ```typescript\n * const proRatedSalary = applyProRating(100000, 0.64); // 64000\n * ```\n *\n * @param baseAmount - Original amount\n * @param ratio - Pro-rating ratio (0-1)\n * @returns Pro-rated amount (rounded)\n *\n * @pure No side effects\n */\nexport function applyProRating(baseAmount: number, ratio: number): number {\n return Math.round(baseAmount * ratio);\n}\n\n/**\n * Check if pro-rating should be applied for a given hire/termination scenario\n *\n * @param hireDate - Employee hire date\n * @param terminationDate - Employee termination date (null if active)\n * @param periodStart - Salary period start\n * @param periodEnd - Salary period end\n * @returns True if pro-rating is needed\n *\n * @pure No side effects\n */\nexport function shouldProRate(\n hireDate: Date,\n terminationDate: Date | null,\n periodStart: Date,\n periodEnd: Date\n): boolean {\n const hire = new Date(hireDate);\n const termination = terminationDate ? new Date(terminationDate) : null;\n\n // Pro-rate if hired after period start\n if (hire > periodStart) return true;\n\n // Pro-rate if terminated before period end\n if (termination && termination < periodEnd) return true;\n\n return false;\n}\n\n","/**\n * @classytic/payroll - Attendance Deduction Calculator\n *\n * Pure functions for calculating salary deductions based on attendance.\n * No database dependencies - can be used client-side!\n *\n * @packageDocumentation\n */\n\n// ============================================================================\n// Types\n// ============================================================================\n\n/**\n * Input for attendance deduction calculation\n */\nexport interface AttendanceDeductionInput {\n /**\n * Expected working days in the period (for this specific employee)\n * Should account for hire/termination dates\n */\n expectedWorkingDays: number;\n\n /**\n * Actual working days the employee was present\n */\n actualWorkingDays: number;\n\n /**\n * Daily salary rate for this employee\n * Calculated as: baseAmount / expectedWorkingDays\n */\n dailyRate: number;\n}\n\n/**\n * Result of attendance deduction calculation\n */\nexport interface AttendanceDeductionResult {\n /**\n * Number of absent days\n */\n absentDays: number;\n\n /**\n * Total deduction amount\n */\n deductionAmount: number;\n\n /**\n * Daily rate used for calculation\n */\n dailyRate: number;\n\n /**\n * Whether any deduction was applied\n */\n hasDeduction: boolean;\n}\n\n// ============================================================================\n// Pure Functions\n// ============================================================================\n\n/**\n * Calculate attendance deduction based on absent days\n *\n * @example\n * ```typescript\n * const result = calculateAttendanceDeduction({\n * expectedWorkingDays: 22,\n * actualWorkingDays: 20, // 2 days absent\n * dailyRate: 4545, // 100000 / 22\n * });\n *\n * console.log(result);\n * // {\n * // absentDays: 2,\n * // deductionAmount: 9090,\n * // dailyRate: 4545,\n * // hasDeduction: true\n * // }\n * ```\n *\n * @param input - Attendance deduction parameters\n * @returns Deduction result with breakdown\n *\n * @pure This function has no side effects\n */\nexport function calculateAttendanceDeduction(input: AttendanceDeductionInput): AttendanceDeductionResult {\n const { expectedWorkingDays, actualWorkingDays, dailyRate } = input;\n\n // Guard against negative values\n const expected = Math.max(0, expectedWorkingDays);\n const actual = Math.max(0, actualWorkingDays);\n const rate = Math.max(0, dailyRate);\n\n // Calculate absent days (cannot be negative)\n const absentDays = Math.max(0, expected - actual);\n\n // Calculate deduction amount\n const deductionAmount = Math.round(absentDays * rate);\n\n return {\n absentDays,\n deductionAmount,\n dailyRate: rate,\n hasDeduction: deductionAmount > 0,\n };\n}\n\n/**\n * Calculate daily rate from monthly salary and working days\n *\n * @example\n * ```typescript\n * const daily = calculateDailyRate(100000, 22); // 4545\n * ```\n *\n * @param monthlySalary - Monthly base salary\n * @param workingDays - Working days in the month\n * @returns Daily rate (rounded)\n *\n * @pure No side effects\n */\nexport function calculateDailyRate(monthlySalary: number, workingDays: number): number {\n if (workingDays <= 0) return 0;\n return Math.round(monthlySalary / workingDays);\n}\n\n/**\n * Calculate hourly rate from monthly salary\n *\n * @example\n * ```typescript\n * const hourly = calculateHourlyRate(100000, 22, 8); // 568\n * ```\n *\n * @param monthlySalary - Monthly base salary\n * @param workingDays - Working days in the month\n * @param hoursPerDay - Hours per working day (default: 8)\n * @returns Hourly rate (rounded)\n *\n * @pure No side effects\n */\nexport function calculateHourlyRate(\n monthlySalary: number,\n workingDays: number,\n hoursPerDay: number = 8\n): number {\n const dailyRate = calculateDailyRate(monthlySalary, workingDays);\n if (hoursPerDay <= 0) return 0;\n return Math.round(dailyRate / hoursPerDay);\n}\n\n/**\n * Calculate deduction for partial day absence (half-day, quarter-day, etc.)\n *\n * @example\n * ```typescript\n * // Half-day absence\n * const deduction = calculatePartialDayDeduction(4545, 0.5); // 2272\n * ```\n *\n * @param dailyRate - Daily salary rate\n * @param fractionAbsent - Fraction of day absent (0-1)\n * @returns Deduction amount (rounded)\n *\n * @pure No side effects\n */\nexport function calculatePartialDayDeduction(dailyRate: number, fractionAbsent: number): number {\n const fraction = Math.min(1, Math.max(0, fractionAbsent));\n return Math.round(dailyRate * fraction);\n}\n\n/**\n * Calculate total attendance deduction including full and partial day absences\n *\n * @example\n * ```typescript\n * const result = calculateTotalAttendanceDeduction({\n * dailyRate: 4545,\n * fullDayAbsences: 2,\n * partialDayAbsences: [0.5, 0.25], // Half-day + quarter-day\n * });\n * \n * console.log(result);\n * // {\n * // fullDayDeduction: 9090,\n * // partialDayDeduction: 3408,\n * // totalDeduction: 12498\n * // }\n * ```\n *\n * @param input - Absence breakdown\n * @returns Deduction breakdown and total\n *\n * @pure No side effects\n */\nexport function calculateTotalAttendanceDeduction(input: {\n dailyRate: number;\n fullDayAbsences?: number;\n partialDayAbsences?: number[];\n}): {\n fullDayDeduction: number;\n partialDayDeduction: number;\n totalDeduction: number;\n} {\n const { dailyRate, fullDayAbsences = 0, partialDayAbsences = [] } = input;\n\n // Full day deductions\n const fullDayDeduction = Math.round(dailyRate * Math.max(0, fullDayAbsences));\n\n // Partial day deductions\n const partialDayDeduction = partialDayAbsences.reduce(\n (sum, fraction) => sum + calculatePartialDayDeduction(dailyRate, fraction),\n 0\n );\n\n return {\n fullDayDeduction,\n partialDayDeduction,\n totalDeduction: fullDayDeduction + partialDayDeduction,\n };\n}\n\n","/**\n * @classytic/payroll - Salary Calculator\n *\n * Pure functions for complete salary breakdown calculations.\n * No database dependencies - can be used client-side!\n *\n * This is the SINGLE SOURCE OF TRUTH for all salary calculations.\n *\n * @packageDocumentation\n */\n\nimport type {\n Compensation,\n PayrollBreakdown,\n Allowance,\n Deduction,\n TaxBracket,\n} from '../types.js';\nimport { calculateGross, calculateNet, sumAllowances, sumDeductions, applyTaxBrackets } from '../utils/calculation.js';\nimport { roundMoney, percentageOf, prorateAmount } from '../utils/money.js';\nimport { isEffectiveForPeriod } from '../utils/date.js';\nimport { countWorkingDays } from '../core/config.js';\nimport { calculateProRating, type ProRatingInput, type ProRatingResult } from './prorating.calculator.js';\nimport { calculateAttendanceDeduction, calculateDailyRate, type AttendanceDeductionInput } from './attendance.calculator.js';\n\n// ============================================================================\n// Types\n// ============================================================================\n\n/**\n * Input for salary breakdown calculation\n */\nexport interface SalaryCalculationInput {\n /**\n * Employee data (minimal subset needed for calculation)\n */\n employee: {\n hireDate: Date;\n terminationDate?: Date | null;\n compensation: Compensation;\n workSchedule?: {\n workingDays?: number[];\n hoursPerDay?: number;\n };\n };\n\n /**\n * Salary period\n */\n period: {\n month: number;\n year: number;\n startDate: Date;\n endDate: Date;\n };\n\n /**\n * Attendance data (optional)\n */\n attendance?: {\n expectedDays?: number;\n actualDays?: number;\n } | null;\n\n /**\n * Processing options\n */\n options?: {\n holidays?: Date[];\n workSchedule?: {\n workingDays?: number[];\n hoursPerDay?: number;\n };\n skipTax?: boolean;\n skipAttendance?: boolean;\n skipProration?: boolean;\n };\n\n /**\n * Configuration (minimal subset)\n */\n config: {\n allowProRating: boolean;\n autoDeductions: boolean;\n defaultCurrency: string;\n attendanceIntegration: boolean;\n };\n\n /**\n * Tax brackets for the employee's currency\n */\n taxBrackets: TaxBracket[];\n}\n\n/**\n * Processed allowance with calculated amount\n */\nexport interface ProcessedAllowance {\n type: string;\n amount: number;\n taxable: boolean;\n originalAmount?: number; // Before pro-rating\n isPercentage?: boolean;\n value?: number;\n}\n\n/**\n * Processed deduction with calculated amount\n */\nexport interface ProcessedDeduction {\n type: string;\n amount: number;\n description?: string;\n originalAmount?: number; // Before pro-rating\n isPercentage?: boolean;\n value?: number;\n}\n\n// ============================================================================\n// Pure Functions\n// ============================================================================\n\n/**\n * Calculate complete salary breakdown\n *\n * This is the SINGLE SOURCE OF TRUTH for salary calculations.\n * All payroll processing uses this function.\n *\n * @example\n * ```typescript\n * const breakdown = calculateSalaryBreakdown({\n * employee: {\n * hireDate: new Date('2024-01-01'),\n * compensation: {\n * baseAmount: 100000,\n * currency: 'USD',\n * allowances: [{ type: 'housing', amount: 20000, taxable: true }],\n * deductions: [{ type: 'insurance', amount: 5000 }],\n * },\n * },\n * period: {\n * month: 3,\n * year: 2024,\n * startDate: new Date('2024-03-01'),\n * endDate: new Date('2024-03-31'),\n * },\n * attendance: {\n * expectedDays: 22,\n * actualDays: 20, // 2 days absent\n * },\n * options: {\n * holidays: [new Date('2024-03-26')],\n * },\n * config: {\n * allowProRating: true,\n * autoDeductions: true,\n * defaultCurrency: 'USD',\n * attendanceIntegration: true,\n * },\n * taxBrackets: [...],\n * });\n * ```\n *\n * @param input - Salary calculation parameters\n * @returns Complete payroll breakdown\n *\n * @pure This function has no side effects and doesn't access database\n */\nexport function calculateSalaryBreakdown(input: SalaryCalculationInput): PayrollBreakdown {\n const { employee, period, attendance, options = {}, config, taxBrackets } = input;\n\n const comp = employee.compensation;\n const originalBaseAmount = comp.baseAmount;\n\n // 1. Calculate pro-rating (if applicable)\n const proRating = calculateProRatingForSalary(\n employee.hireDate,\n employee.terminationDate || null,\n period.startDate,\n period.endDate,\n options,\n employee.workSchedule\n );\n\n // 2. Apply pro-rating to base salary\n let baseAmount = originalBaseAmount;\n if (proRating.isProRated && config.allowProRating && !options.skipProration) {\n baseAmount = prorateAmount(baseAmount, proRating.ratio);\n }\n\n // 3. Filter allowances by effective date\n const effectiveAllowances = (comp.allowances || [])\n .filter((a) => isEffectiveForPeriod(a, period.startDate, period.endDate));\n\n // 4. Filter deductions by effective date\n const effectiveDeductions = (comp.deductions || [])\n .filter((d) => isEffectiveForPeriod(d, period.startDate, period.endDate))\n .filter((d) => d.auto || d.recurring);\n\n // 5. Calculate allowances (handle percentages and pro-rating)\n const allowances = processAllowances(effectiveAllowances, originalBaseAmount, proRating, config);\n\n // 6. Calculate deductions (handle percentages and pro-rating)\n const deductions = processDeductions(effectiveDeductions, originalBaseAmount, proRating, config);\n\n // 7. Calculate attendance deduction\n if (!options.skipAttendance && config.attendanceIntegration && attendance) {\n const attendanceDeductionResult = calculateAttendanceDeductionFromData(\n attendance,\n baseAmount,\n proRating.effectiveWorkingDays\n );\n\n if (attendanceDeductionResult.hasDeduction) {\n deductions.push({\n type: 'absence',\n amount: attendanceDeductionResult.deductionAmount,\n description: `Unpaid leave deduction (${attendanceDeductionResult.absentDays} days)`,\n });\n }\n }\n\n // 8. Calculate gross salary\n const grossSalary = calculateGross(baseAmount, allowances);\n\n // 9. Calculate taxable amount (only taxable allowances)\n const taxableAllowances = allowances.filter((a) => a.taxable);\n const taxableAmount = baseAmount + sumAllowances(taxableAllowances);\n\n // 10. Calculate tax\n let taxAmount = 0;\n if (!options.skipTax && taxBrackets.length > 0 && config.autoDeductions) {\n // Annualize the taxable amount for tax bracket calculation\n const annualTaxable = taxableAmount * 12;\n const annualTax = applyTaxBrackets(annualTaxable, taxBrackets);\n taxAmount = roundMoney(annualTax / 12); // Monthly tax (banker's rounding)\n }\n\n // Add tax to deductions if applicable\n if (taxAmount > 0) {\n deductions.push({\n type: 'tax',\n amount: taxAmount,\n description: 'Income tax',\n });\n }\n\n // 11. Calculate net salary\n const netSalary = calculateNet(grossSalary, deductions);\n\n // 12. Build final breakdown\n return {\n baseAmount,\n allowances,\n deductions,\n grossSalary,\n netSalary,\n taxableAmount,\n taxAmount,\n workingDays: proRating.periodWorkingDays,\n actualDays: proRating.effectiveWorkingDays,\n proRatedAmount: (proRating.isProRated && !options.skipProration) ? baseAmount : 0,\n attendanceDeduction: attendance\n ? deductions.find((d) => d.type === 'absence')?.amount || 0\n : 0,\n };\n}\n\n// ============================================================================\n// Helper Functions\n// ============================================================================\n\n/**\n * Calculate pro-rating for salary calculation\n */\nfunction calculateProRatingForSalary(\n hireDate: Date,\n terminationDate: Date | null,\n periodStart: Date,\n periodEnd: Date,\n options: SalaryCalculationInput['options'],\n employeeWorkSchedule?: { workingDays?: number[] }\n): ProRatingResult {\n // Work schedule: prefer operation override, then employee schedule, then Mon-Fri default\n const workingDays =\n options?.workSchedule?.workingDays ||\n employeeWorkSchedule?.workingDays ||\n [1, 2, 3, 4, 5];\n\n const holidays = options?.holidays || [];\n\n return calculateProRating({\n hireDate,\n terminationDate,\n periodStart,\n periodEnd,\n workingDays,\n holidays,\n });\n}\n\n/**\n * Process allowances (handle percentages and pro-rating)\n */\nfunction processAllowances(\n allowances: Allowance[],\n originalBaseAmount: number,\n proRating: ProRatingResult,\n config: SalaryCalculationInput['config']\n): ProcessedAllowance[] {\n return allowances.map((a) => {\n // Calculate from original base (percentage) or use fixed amount\n let amount = a.isPercentage && a.value !== undefined\n ? percentageOf(originalBaseAmount, a.value)\n : a.amount;\n\n const originalAmount = amount;\n\n // Apply pro-rating ONCE if needed\n if (proRating.isProRated && config.allowProRating) {\n amount = prorateAmount(amount, proRating.ratio);\n }\n\n return {\n type: a.type,\n amount,\n taxable: a.taxable ?? true,\n originalAmount,\n isPercentage: a.isPercentage,\n value: a.value,\n };\n });\n}\n\n/**\n * Process deductions (handle percentages and pro-rating)\n */\nfunction processDeductions(\n deductions: Deduction[],\n originalBaseAmount: number,\n proRating: ProRatingResult,\n config: SalaryCalculationInput['config']\n): ProcessedDeduction[] {\n return deductions.map((d) => {\n // Calculate from original base (percentage) or use fixed amount\n let amount = d.isPercentage && d.value !== undefined\n ? percentageOf(originalBaseAmount, d.value)\n : d.amount;\n\n const originalAmount = amount;\n\n // Apply pro-rating ONCE if needed\n if (proRating.isProRated && config.allowProRating) {\n amount = prorateAmount(amount, proRating.ratio);\n }\n\n return {\n type: d.type,\n amount,\n description: d.description,\n originalAmount,\n isPercentage: d.isPercentage,\n value: d.value,\n };\n });\n}\n\n/**\n * Calculate attendance deduction from attendance data\n */\nfunction calculateAttendanceDeductionFromData(\n attendance: { expectedDays?: number; actualDays?: number },\n baseAmount: number,\n effectiveWorkingDays: number\n): {\n hasDeduction: boolean;\n deductionAmount: number;\n absentDays: number;\n} {\n const expectedDays = attendance.expectedDays ?? effectiveWorkingDays;\n const actualDays = attendance.actualDays;\n\n if (actualDays === undefined) {\n return { hasDeduction: false, deductionAmount: 0, absentDays: 0 };\n }\n\n // Daily rate based on expected working days for THIS employee in THIS period\n const dailyRate = calculateDailyRate(baseAmount, expectedDays);\n\n const result = calculateAttendanceDeduction({\n expectedWorkingDays: expectedDays,\n actualWorkingDays: actualDays,\n dailyRate,\n });\n\n return {\n hasDeduction: result.hasDeduction,\n deductionAmount: result.deductionAmount,\n absentDays: result.absentDays,\n };\n}\n\n"]}
1
+ {"version":3,"sources":["../../src/utils/money.ts","../../src/utils/calculation.ts","../../src/utils/date.ts","../../src/core/config.ts","../../src/config.ts","../../src/calculators/prorating.calculator.ts","../../src/calculators/attendance.calculator.ts","../../src/calculators/salary.calculator.ts"],"names":["periodWorkingDays"],"mappings":"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","file":"index.js","sourcesContent":["/**\n * Money calculation utilities for payroll compliance\n *\n * Uses banker's rounding (round half to even) to prevent systematic bias\n * in financial calculations, as recommended for payroll systems.\n *\n * PRECISION: All functions default to 2 decimal places (cents/paise precision)\n * to maintain accuracy in payroll calculations. Amounts are stored as floating\n * point numbers with decimal precision (e.g., 1000.50 for $1,000.50).\n *\n * ## Design Decision: Floating Point Storage\n *\n * This module uses floating-point numbers (IEEE 754 double precision) for money\n * storage rather than integer minor units (cents) or MongoDB Decimal128.\n *\n * ### Tradeoffs:\n *\n * **Why floating point:**\n * - Simpler API (developers think in dollars, not cents)\n * - More intuitive JSON serialization (100.50 vs 10050)\n * - Sufficient precision for payroll (15 significant digits)\n * - Banker's rounding mitigates cumulative drift\n *\n * **Why NOT integer cents:**\n * - Would require breaking schema changes\n * - All consumer code needs conversion (amount / 100)\n * - More verbose for reporting/display\n *\n * **Why NOT Decimal128:**\n * - MongoDB-specific, reduces portability\n * - Requires special BSON handling in application code\n * - Overkill for typical payroll amounts (< $10M/employee/year)\n *\n * ### Mitigation:\n * - All calculations go through roundMoney() with banker's rounding\n * - Final amounts always rounded to 2 decimal places before storage\n * - Aggregations use MongoDB $round operator for consistency\n *\n * @see https://en.wikipedia.org/wiki/Banker%27s_rounding\n */\n\n/**\n * Banker's rounding (round half to even) with decimal precision\n *\n * When rounding 0.5, rounds to the nearest even number:\n * - 2.5 → 2 (even)\n * - 3.5 → 4 (even)\n * - 4.5 → 4 (even)\n *\n * This prevents systematic bias in cumulative rounding that occurs\n * with standard rounding (always up), which is critical for payroll compliance.\n *\n * @param value - The number to round\n * @param decimals - Number of decimal places (default: 2 for cent precision)\n * @returns The rounded value\n */\nexport function roundMoney(value: number, decimals = 2): number {\n const multiplier = Math.pow(10, decimals);\n const scaled = value * multiplier;\n const fraction = scaled - Math.floor(scaled);\n\n // If exactly 0.5 (within tolerance), round to nearest even\n // Use 1e-10 tolerance to handle floating-point imprecision\n if (Math.abs(fraction - 0.5) < 1e-10) {\n const floor = Math.floor(scaled);\n const rounded = floor % 2 === 0 ? floor : floor + 1;\n return rounded / multiplier;\n }\n\n // Otherwise use standard rounding\n return Math.round(scaled) / multiplier;\n}\n\n/**\n * Round money with validation for negative values\n *\n * @param value - The number to round\n * @param decimals - Number of decimal places (default: 2 for cent precision)\n * @returns The rounded value (never negative for deductions)\n */\nexport function roundMoneyPositive(value: number, decimals = 2): number {\n return Math.max(0, roundMoney(value, decimals));\n}\n\n/**\n * Calculate percentage of an amount with banker's rounding\n *\n * @param amount - Base amount\n * @param percentage - Percentage (e.g., 10 for 10%)\n * @param decimals - Decimal places to round to (default: 2 for cent precision)\n * @returns Rounded percentage amount\n */\nexport function percentageOf(amount: number, percentage: number, decimals = 2): number {\n return roundMoney((amount * percentage) / 100, decimals);\n}\n\n/**\n * Prorate an amount by a ratio with banker's rounding\n *\n * @param amount - Base amount\n * @param ratio - Proration ratio (0 to 1)\n * @param decimals - Decimal places (default: 2 for cent precision)\n * @returns Prorated amount\n */\nexport function prorateAmount(amount: number, ratio: number, decimals = 2): number {\n return roundMoney(amount * ratio, decimals);\n}\n","/**\n * @classytic/payroll - Calculation Utilities\n *\n * Pure, functional, composable financial calculations\n * No side effects, highly testable\n */\n\nimport type {\n Allowance,\n Deduction,\n Compensation,\n TaxCalculationResult,\n CompensationBreakdownResult,\n} from '../types.js';\nimport { roundMoney } from './money.js';\n\n// ============================================================================\n// Basic Math Operations\n// ============================================================================\n\n/**\n * Sum array of numbers\n */\nexport function sum(numbers: number[]): number {\n return numbers.reduce((total, n) => total + n, 0);\n}\n\n/**\n * Sum by property\n */\nexport function sumBy<T>(items: T[], getter: (item: T) => number): number {\n return items.reduce((total, item) => total + getter(item), 0);\n}\n\n/**\n * Sum allowances\n */\nexport function sumAllowances(allowances: Array<{ amount: number }>): number {\n return sumBy(allowances, (a) => a.amount);\n}\n\n/**\n * Sum deductions\n */\nexport function sumDeductions(deductions: Array<{ amount: number }>): number {\n return sumBy(deductions, (d) => d.amount);\n}\n\n/**\n * ROUNDING POLICY FOR FINANCIAL CALCULATIONS\n *\n * Monetary amounts are stored as floating point numbers in major units with\n * decimal precision (e.g., 1000.50 for $1,000.50 or ₹1,000.50).\n *\n * PRECISION: All calculations preserve 2 decimal places (cent/paise precision)\n * to maintain accuracy required for payroll compliance.\n *\n * Rounding Rules:\n * 1. Banker's Rounding (Round Half to Even): Used for fair rounding over many transactions\n * 2. All intermediate calculations maintain full precision\n * 3. Final amounts rounded to 2 decimals using banker's rounding\n * 4. Tax calculations use banker's rounding for compliance\n *\n * Example:\n * Input: 1000.50 base + 15% tax\n * Calculation: 1000.50 * 0.15 = 150.075 → rounds to 150.08 (banker's rounding to 2 decimals)\n * Result: Tax = 150.08\n *\n * @see https://en.wikipedia.org/wiki/Rounding#Round_half_to_even\n */\n\n/**\n * Banker's Rounding (Round Half to Even) - Integer precision\n *\n * Rounds to the nearest integer using banker's rounding (round half to even).\n * This prevents systematic bias in rounding over many transactions.\n *\n * Uses epsilon check for safe floating-point comparison.\n *\n * Examples:\n * 0.5 → 0 (even)\n * 1.5 → 2 (even)\n * 2.5 → 2 (even)\n * 3.5 → 4 (even)\n *\n * @param value - The number to round\n * @returns Rounded integer\n * @note For money calculations with decimal precision, use `roundMoney()` instead\n */\nexport function bankersRound(value: number): number {\n const floor = Math.floor(value);\n const fraction = value - floor;\n\n // Use epsilon check for safer floating-point comparison\n if (Math.abs(fraction - 0.5) < Number.EPSILON) {\n // If halfway, round to even\n return floor % 2 === 0 ? floor : floor + 1;\n }\n\n // Otherwise use standard rounding\n return Math.round(value);\n}\n\n/**\n * Apply percentage to amount with banker's rounding (2 decimal precision)\n *\n * @param amount - Amount in major units (e.g., dollars, rupees)\n * @param percentage - Percentage to apply (e.g., 15 for 15%)\n * @param decimals - Decimal places for precision (default: 2 for cent precision)\n * @returns Result in major units, properly rounded to 2 decimals\n * @note Uses banker's rounding (round half to even) to preserve cent precision.\n * Equivalent to percentageOf() from money.ts.\n */\nexport function applyPercentage(amount: number, percentage: number, decimals = 2): number {\n // Use centralized roundMoney for consistent banker's rounding across codebase\n const result = (amount * percentage) / 100;\n return roundMoney(result, decimals);\n}\n\n/**\n * Calculate percentage of total\n */\nexport function calculatePercentage(part: number, total: number): number {\n return total > 0 ? bankersRound((part / total) * 100) : 0;\n}\n\n/**\n * Round to decimal places using banker's rounding\n */\nexport function roundTo(value: number, decimals = 2): number {\n const factor = Math.pow(10, decimals);\n return bankersRound(value * factor) / factor;\n}\n\n// ============================================================================\n// Salary Calculations\n// ============================================================================\n\n/**\n * Calculate gross salary from base and allowances\n */\nexport function calculateGross(\n baseAmount: number,\n allowances: Array<{ amount: number }>\n): number {\n return baseAmount + sumAllowances(allowances);\n}\n\n/**\n * Calculate net salary from gross and deductions\n */\nexport function calculateNet(\n gross: number,\n deductions: Array<{ amount: number }>\n): number {\n return Math.max(0, gross - sumDeductions(deductions));\n}\n\n/**\n * Calculate total compensation\n */\nexport function calculateTotalCompensation(\n baseAmount: number,\n allowances: Array<{ amount: number }>,\n deductions: Array<{ amount: number }>\n): { gross: number; net: number; deductions: number } {\n const gross = calculateGross(baseAmount, allowances);\n const totalDeductions = sumDeductions(deductions);\n const net = calculateNet(gross, deductions);\n return { gross, net, deductions: totalDeductions };\n}\n\n// ============================================================================\n// Allowance & Deduction Calculation\n// ============================================================================\n\n/**\n * Calculate allowance amount (handles percentage-based)\n */\nexport function calculateAllowanceAmount(\n allowance: Pick<Allowance, 'amount' | 'isPercentage' | 'value'>,\n baseAmount: number\n): number {\n if (allowance.isPercentage && allowance.value !== undefined) {\n return applyPercentage(baseAmount, allowance.value);\n }\n return allowance.amount;\n}\n\n/**\n * Calculate deduction amount (handles percentage-based)\n */\nexport function calculateDeductionAmount(\n deduction: Pick<Deduction, 'amount' | 'isPercentage' | 'value'>,\n baseAmount: number\n): number {\n if (deduction.isPercentage && deduction.value !== undefined) {\n return applyPercentage(baseAmount, deduction.value);\n }\n return deduction.amount;\n}\n\n/**\n * Calculate all allowances with their actual amounts\n */\nexport function calculateAllowances(\n allowances: Allowance[],\n baseAmount: number\n): Array<Allowance & { calculatedAmount: number }> {\n return allowances.map((allowance) => ({\n ...allowance,\n calculatedAmount: calculateAllowanceAmount(allowance, baseAmount),\n }));\n}\n\n/**\n * Calculate all deductions with their actual amounts\n */\nexport function calculateDeductions(\n deductions: Deduction[],\n baseAmount: number\n): Array<Deduction & { calculatedAmount: number }> {\n return deductions.map((deduction) => ({\n ...deduction,\n calculatedAmount: calculateDeductionAmount(deduction, baseAmount),\n }));\n}\n\n// ============================================================================\n// Compensation Breakdown\n// ============================================================================\n\n/**\n * Calculate full compensation breakdown\n */\nexport function calculateCompensationBreakdown(\n compensation: Pick<Compensation, 'baseAmount' | 'allowances' | 'deductions'>\n): CompensationBreakdownResult {\n const { baseAmount, allowances = [], deductions = [] } = compensation;\n\n const calculatedAllowances = calculateAllowances(allowances, baseAmount);\n const calculatedDeductions = calculateDeductions(deductions, baseAmount);\n\n const grossAmount =\n baseAmount + sumBy(calculatedAllowances, (a) => a.calculatedAmount);\n const netAmount =\n grossAmount - sumBy(calculatedDeductions, (d) => d.calculatedAmount);\n\n return {\n baseAmount,\n allowances: calculatedAllowances,\n deductions: calculatedDeductions,\n grossAmount,\n netAmount: Math.max(0, netAmount),\n };\n}\n\n// ============================================================================\n// Tax Calculations\n// ============================================================================\n\n/**\n * Apply tax brackets to calculate tax\n *\n * Uses banker's rounding for compliance (rounds to 2 decimal places).\n * Consistent with all other money calculations in the system.\n */\nexport function applyTaxBrackets(\n amount: number,\n brackets: Array<{ min: number; max: number; rate: number }>\n): number {\n let tax = 0;\n\n for (const bracket of brackets) {\n if (amount > bracket.min) {\n const taxableAmount = Math.min(amount, bracket.max) - bracket.min;\n tax += taxableAmount * bracket.rate;\n }\n }\n\n // Use roundMoney for consistency with all other money calculations\n return roundMoney(tax);\n}\n\n/**\n * Calculate tax with result\n */\nexport function calculateTax(\n amount: number,\n brackets: Array<{ min: number; max: number; rate: number }>\n): TaxCalculationResult {\n const tax = applyTaxBrackets(amount, brackets);\n return {\n gross: amount,\n tax,\n net: amount - tax,\n };\n}\n\n// ============================================================================\n// Overtime Calculations\n// ============================================================================\n\n/**\n * Calculate overtime pay\n */\nexport function calculateOvertime(\n hourlyRate: number,\n overtimeHours: number,\n multiplier = 1.5\n): number {\n return roundMoney(hourlyRate * overtimeHours * multiplier, 2);\n}\n\n/**\n * Calculate hourly rate from monthly salary\n */\nexport function calculateHourlyRate(\n monthlySalary: number,\n hoursPerMonth = 176 // 44 hours/week * 4 weeks\n): number {\n return roundMoney(monthlySalary / hoursPerMonth, 2);\n}\n\n/**\n * Calculate daily rate from monthly salary\n */\nexport function calculateDailyRate(\n monthlySalary: number,\n daysPerMonth = 22\n): number {\n return roundMoney(monthlySalary / daysPerMonth, 2);\n}\n\n// ============================================================================\n// Default Export\n// ============================================================================\n\nexport default {\n sum,\n sumBy,\n sumAllowances,\n sumDeductions,\n applyPercentage,\n calculatePercentage,\n roundTo,\n calculateGross,\n calculateNet,\n calculateTotalCompensation,\n calculateAllowanceAmount,\n calculateDeductionAmount,\n calculateAllowances,\n calculateDeductions,\n calculateCompensationBreakdown,\n applyTaxBrackets,\n calculateTax,\n calculateOvertime,\n calculateHourlyRate,\n calculateDailyRate,\n};\n\n","/**\n * @classytic/payroll - Date Utilities\n *\n * Pure, composable, testable date operations\n * No side effects, no mutations\n */\n\nimport type { PayPeriodInfo, PaymentFrequency } from '../types.js';\n\n// ============================================================================\n// Date Arithmetic\n// ============================================================================\n\n/**\n * Add days to a date\n */\nexport function addDays(date: Date, days: number): Date {\n const result = new Date(date);\n result.setDate(result.getDate() + days);\n return result;\n}\n\n/**\n * Add months to a date\n */\nexport function addMonths(date: Date, months: number): Date {\n const result = new Date(date);\n result.setMonth(result.getMonth() + months);\n return result;\n}\n\n/**\n * Add years to a date\n */\nexport function addYears(date: Date, years: number): Date {\n const result = new Date(date);\n result.setFullYear(result.getFullYear() + years);\n return result;\n}\n\n/**\n * Subtract days from a date\n */\nexport function subDays(date: Date, days: number): Date {\n return addDays(date, -days);\n}\n\n/**\n * Subtract months from a date\n */\nexport function subMonths(date: Date, months: number): Date {\n return addMonths(date, -months);\n}\n\n// ============================================================================\n// Date Boundaries\n// ============================================================================\n\n/**\n * Get the start of a month\n */\nexport function startOfMonth(date: Date): Date {\n const result = new Date(date);\n result.setDate(1);\n result.setHours(0, 0, 0, 0);\n return result;\n}\n\n/**\n * Get the end of a month\n */\nexport function endOfMonth(date: Date): Date {\n const result = new Date(date);\n result.setMonth(result.getMonth() + 1, 0);\n result.setHours(23, 59, 59, 999);\n return result;\n}\n\n/**\n * Get the start of a year\n */\nexport function startOfYear(date: Date): Date {\n const result = new Date(date);\n result.setMonth(0, 1);\n result.setHours(0, 0, 0, 0);\n return result;\n}\n\n/**\n * Get the end of a year\n */\nexport function endOfYear(date: Date): Date {\n const result = new Date(date);\n result.setMonth(11, 31);\n result.setHours(23, 59, 59, 999);\n return result;\n}\n\n/**\n * Get the start of a day\n */\nexport function startOfDay(date: Date): Date {\n const result = new Date(date);\n result.setHours(0, 0, 0, 0);\n return result;\n}\n\n/**\n * Get the end of a day\n */\nexport function endOfDay(date: Date): Date {\n const result = new Date(date);\n result.setHours(23, 59, 59, 999);\n return result;\n}\n\n// ============================================================================\n// Date Normalization\n// ============================================================================\n\n/**\n * Convert a date to a UTC-based date string for consistent comparison.\n *\n * Unlike `Date.toDateString()` which uses the local timezone, this produces\n * a locale-independent string based on the date's year/month/day components.\n * Use this for holiday set lookups to avoid timezone-dependent mismatches.\n */\nexport function toUTCDateString(date: Date): string {\n const d = new Date(date);\n d.setHours(0, 0, 0, 0);\n return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}-${String(d.getDate()).padStart(2, '0')}`;\n}\n\n// ============================================================================\n// Date Differences\n// ============================================================================\n\n/**\n * Calculate difference in days between two dates.\n *\n * Normalizes both dates to midnight before computing to avoid\n * inconsistencies from time-of-day differences or DST transitions.\n */\nexport function diffInDays(start: Date, end: Date): number {\n const s = new Date(start);\n const e = new Date(end);\n s.setHours(0, 0, 0, 0);\n e.setHours(0, 0, 0, 0);\n return Math.ceil((e.getTime() - s.getTime()) / (1000 * 60 * 60 * 24));\n}\n\n/**\n * Calculate difference in months between two dates\n */\nexport function diffInMonths(start: Date, end: Date): number {\n const startDate = new Date(start);\n const endDate = new Date(end);\n return (\n (endDate.getFullYear() - startDate.getFullYear()) * 12 +\n (endDate.getMonth() - startDate.getMonth())\n );\n}\n\n/**\n * Calculate difference in years between two dates\n */\nexport function diffInYears(start: Date, end: Date): number {\n return Math.floor(diffInMonths(start, end) / 12);\n}\n\n// Aliases for backwards compatibility\nexport const daysBetween = diffInDays;\nexport const monthsBetween = diffInMonths;\n\n// ============================================================================\n// Day Type Checks\n// ============================================================================\n\n/**\n * Check if date is a weekday (Mon-Fri)\n */\nexport function isWeekday(date: Date): boolean {\n const day = new Date(date).getDay();\n return day >= 1 && day <= 5;\n}\n\n/**\n * Check if date is a weekend (Sat-Sun)\n */\nexport function isWeekend(date: Date): boolean {\n const day = new Date(date).getDay();\n return day === 0 || day === 6;\n}\n\n/**\n * Get day of week (0=Sunday, 6=Saturday)\n */\nexport function getDayOfWeek(date: Date): number {\n return new Date(date).getDay();\n}\n\n/**\n * Get day name\n */\nexport function getDayName(date: Date): string {\n const days = [\n 'Sunday',\n 'Monday',\n 'Tuesday',\n 'Wednesday',\n 'Thursday',\n 'Friday',\n 'Saturday',\n ];\n return days[getDayOfWeek(date)];\n}\n\n// ============================================================================\n// Pay Period Functions\n// ============================================================================\n\n/**\n * Get pay period for a given month and year (monthly periods)\n */\nexport function getPayPeriod(month: number, year: number): PayPeriodInfo {\n const startDate = new Date(year, month - 1, 1);\n return {\n month,\n year,\n startDate: startOfMonth(startDate),\n endDate: endOfMonth(startDate),\n };\n}\n\n/**\n * Get pay period based on payment frequency\n *\n * Creates the correct period boundaries based on the employee's payment frequency:\n * - monthly: full calendar month\n * - bi_weekly: 14 days ending on paymentDate\n * - weekly: 7 days ending on paymentDate\n * - daily/hourly: single day (paymentDate)\n *\n * @param frequency - Payment frequency\n * @param paymentDate - Date of payment (used as end of period for non-monthly)\n * @param month - Month (1-12) for accounting purposes\n * @param year - Year for accounting purposes\n * @returns Pay period with appropriate boundaries\n */\nexport function getPayPeriodForFrequency(\n frequency: PaymentFrequency,\n paymentDate: Date,\n month: number,\n year: number\n): PayPeriodInfo & { workingDays: number } {\n switch (frequency) {\n case 'monthly': {\n const period = getPayPeriod(month, year);\n const workingDays = getWorkingDaysInMonth(year, month);\n return { ...period, workingDays };\n }\n\n case 'bi_weekly': {\n // 14-day period ending on paymentDate\n const endDate = startOfDay(paymentDate);\n const startDate = addDays(endDate, -13); // 14 days total (0-13)\n const workingDays = countWeekdaysInRange(startDate, endDate);\n return { month, year, startDate, endDate, workingDays };\n }\n\n case 'weekly': {\n // 7-day period ending on paymentDate\n const endDate = startOfDay(paymentDate);\n const startDate = addDays(endDate, -6); // 7 days total (0-6)\n const workingDays = countWeekdaysInRange(startDate, endDate);\n return { month, year, startDate, endDate, workingDays };\n }\n\n case 'daily':\n case 'hourly': {\n // Single day period\n const date = startOfDay(paymentDate);\n const workingDays = isWeekday(date) ? 1 : 0;\n return { month, year, startDate: date, endDate: date, workingDays };\n }\n\n default:\n // Fallback to monthly\n return getPayPeriodForFrequency('monthly', paymentDate, month, year);\n }\n}\n\n/**\n * Count weekdays (Mon-Fri) in a date range (inclusive)\n */\nfunction countWeekdaysInRange(start: Date, end: Date): number {\n let count = 0;\n const current = new Date(start);\n while (current <= end) {\n if (isWeekday(current)) {\n count++;\n }\n current.setDate(current.getDate() + 1);\n }\n return count;\n}\n\n/**\n * Get current pay period\n */\nexport function getCurrentPeriod(date = new Date()): { year: number; month: number } {\n const d = new Date(date);\n return {\n year: d.getFullYear(),\n month: d.getMonth() + 1,\n };\n}\n\n/**\n * Get working days in a month\n */\nexport function getWorkingDaysInMonth(year: number, month: number): number {\n const start = new Date(year, month - 1, 1);\n const end = endOfMonth(start);\n let count = 0;\n \n const current = new Date(start);\n while (current <= end) {\n if (isWeekday(current)) {\n count++;\n }\n current.setDate(current.getDate() + 1);\n }\n \n return count;\n}\n\n/**\n * Get total days in a month\n */\nexport function getDaysInMonth(year: number, month: number): number {\n return new Date(year, month, 0).getDate();\n}\n\n// ============================================================================\n// Employment Date Functions\n// ============================================================================\n\n/**\n * Calculate probation end date\n */\nexport function calculateProbationEnd(\n hireDate: Date,\n probationMonths: number\n): Date | null {\n if (!probationMonths || probationMonths <= 0) return null;\n return addMonths(hireDate, probationMonths);\n}\n\n/**\n * Check if employee is on probation\n */\nexport function isOnProbation(\n probationEndDate: Date | null | undefined,\n now = new Date()\n): boolean {\n if (!probationEndDate) return false;\n return now < new Date(probationEndDate);\n}\n\n/**\n * Calculate years of service\n */\nexport function calculateYearsOfService(\n hireDate: Date,\n terminationDate?: Date | null\n): number {\n const end = terminationDate || new Date();\n const days = diffInDays(hireDate, end);\n return Math.max(0, Math.floor((days / 365.25) * 10) / 10);\n}\n\n// ============================================================================\n// Range Functions\n// ============================================================================\n\n/**\n * Check if a date is within a range\n */\nexport function isDateInRange(date: Date, start: Date, end: Date): boolean {\n const checkDate = new Date(date);\n return checkDate >= new Date(start) && checkDate <= new Date(end);\n}\n\n/**\n * Check if an item with effectiveFrom/effectiveTo dates is effective for a given period.\n *\n * Used for filtering allowances, deductions, and other time-bounded compensation items.\n * An item is considered effective if its date range overlaps with the period.\n *\n * @param item - Object with optional effectiveFrom and effectiveTo dates\n * @param periodStart - Start of the period to check\n * @param periodEnd - End of the period to check\n * @returns true if the item is effective during any part of the period\n *\n * @example\n * ```typescript\n * const allowance = { effectiveFrom: new Date('2024-01-01'), effectiveTo: null };\n * const periodStart = new Date('2024-03-01');\n * const periodEnd = new Date('2024-03-31');\n *\n * isEffectiveForPeriod(allowance, periodStart, periodEnd); // true\n * ```\n */\nexport function isEffectiveForPeriod(\n item: { effectiveFrom?: Date | null; effectiveTo?: Date | null },\n periodStart: Date,\n periodEnd: Date\n): boolean {\n const effectiveFrom = item.effectiveFrom ? new Date(item.effectiveFrom) : new Date(0);\n const effectiveTo = item.effectiveTo ? new Date(item.effectiveTo) : new Date('2099-12-31');\n\n // Item is effective if its range overlaps with the period\n return effectiveFrom <= periodEnd && effectiveTo >= periodStart;\n}\n\n/**\n * Get date range for a pay period\n */\nexport function getPayPeriodDateRange(\n month: number,\n year: number\n): { start: Date; end: Date } {\n const period = getPayPeriod(month, year);\n return { start: period.startDate, end: period.endDate };\n}\n\n// ============================================================================\n// Formatting Functions\n// ============================================================================\n\n/**\n * Format date for database storage\n */\nexport function formatDateForDB(date: Date): string {\n if (!date) return '';\n return new Date(date).toISOString();\n}\n\n/**\n * Parse date from database\n */\nexport function parseDBDate(dateString: string): Date | null {\n if (!dateString) return null;\n return new Date(dateString);\n}\n\n/**\n * Format period as string (e.g., \"01/2025\")\n */\nexport function formatPeriod({ month, year }: { month: number; year: number }): string {\n return `${String(month).padStart(2, '0')}/${year}`;\n}\n\n/**\n * Parse period string back to object\n */\nexport function parsePeriod(periodString: string): { month: number; year: number } {\n const [month, year] = periodString.split('/').map(Number);\n return { month, year };\n}\n\n/**\n * Format month name\n */\nexport function getMonthName(month: number): string {\n const months = [\n 'January', 'February', 'March', 'April', 'May', 'June',\n 'July', 'August', 'September', 'October', 'November', 'December',\n ];\n return months[month - 1] || '';\n}\n\n/**\n * Format short month name\n */\nexport function getShortMonthName(month: number): string {\n const months = [\n 'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun',\n 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec',\n ];\n return months[month - 1] || '';\n}\n\n// ============================================================================\n// Default Export\n// ============================================================================\n\nexport default {\n toUTCDateString,\n addDays,\n addMonths,\n addYears,\n subDays,\n subMonths,\n startOfMonth,\n endOfMonth,\n startOfYear,\n endOfYear,\n startOfDay,\n endOfDay,\n diffInDays,\n diffInMonths,\n diffInYears,\n daysBetween,\n monthsBetween,\n isWeekday,\n isWeekend,\n getDayOfWeek,\n getDayName,\n getPayPeriod,\n getCurrentPeriod,\n getWorkingDaysInMonth,\n getDaysInMonth,\n calculateProbationEnd,\n isOnProbation,\n calculateYearsOfService,\n isDateInRange,\n isEffectiveForPeriod,\n getPayPeriodDateRange,\n formatDateForDB,\n parseDBDate,\n formatPeriod,\n parsePeriod,\n getMonthName,\n getShortMonthName,\n};\n\n","/**\n * @classytic/payroll - Configuration & Calculation Utilities\n *\n * DESIGN PRINCIPLES:\n * 1. Accept data, don't manage it\n * 2. Pure functions - easy to test, no side effects\n * 3. Smart defaults that work out of the box\n * 4. Override at operation time when needed\n *\n * The payroll package CALCULATES, it doesn't STORE calendars/holidays.\n * Your app manages that data and passes it when needed.\n */\n\nimport { roundMoney } from '../utils/money.js';\nimport { toUTCDateString } from '../utils/date.js';\n\n// ============================================================================\n// Types\n// ============================================================================\n\n/** Work schedule configuration */\nexport interface WorkSchedule {\n /** Working days (0=Sun, 1=Mon, ..., 6=Sat). Default: Mon-Fri */\n workingDays: number[];\n /** Hours per work day. Default: 8 */\n hoursPerDay: number;\n}\n\n/** Options passed when processing payroll */\nexport interface PayrollProcessingOptions {\n /** Holidays in this period (from YOUR app's holiday model) */\n holidays?: Date[];\n /** Override work schedule for this operation */\n workSchedule?: Partial<WorkSchedule>;\n /** Skip tax calculation */\n skipTax?: boolean;\n /** Skip proration (pay full amount regardless of hire/termination date) */\n skipProration?: boolean;\n /** Skip attendance deduction */\n skipAttendance?: boolean;\n}\n\n/** Working days calculation result */\nexport interface WorkingDaysResult {\n /** Total calendar days in period */\n totalDays: number;\n /** Working days (excluding weekends and holidays) */\n workingDays: number;\n /** Weekend days */\n weekends: number;\n /** Holiday count */\n holidays: number;\n}\n\n/** Proration calculation result */\nexport interface ProrationResult {\n /** Proration ratio (0-1) */\n ratio: number;\n /**\n * Reason for proration:\n * - 'full': Employee worked the entire period (ratio = 1)\n * - 'new_hire': Employee was hired during the period\n * - 'termination': Employee was terminated during the period\n * - 'both': Both hired and terminated within the period\n * - 'not_active': Employee was not active at all during the period (ratio = 0)\n */\n reason: 'full' | 'new_hire' | 'termination' | 'both' | 'not_active';\n /** Whether salary should be prorated */\n isProrated: boolean;\n}\n\n/** Attendance data (from YOUR attendance system) */\nexport interface AttendanceInput {\n /**\n * Expected work days in period.\n * If not provided, derived from employee's workSchedule and period dates.\n */\n expectedDays?: number;\n /** Actual days worked */\n actualDays: number;\n}\n\n// ============================================================================\n// Default Configuration\n// ============================================================================\n\nexport const DEFAULT_WORK_SCHEDULE: WorkSchedule = {\n workingDays: [1, 2, 3, 4, 5], // Monday to Friday\n hoursPerDay: 8,\n};\n\n// ============================================================================\n// Pure Calculation Functions\n// ============================================================================\n\n/**\n * Count working days in a date range\n *\n * @example\n * const result = countWorkingDays(\n * new Date('2024-03-01'),\n * new Date('2024-03-31'),\n * { workingDays: [1,2,3,4,5], holidays: companyHolidays }\n * );\n */\nexport function countWorkingDays(\n startDate: Date,\n endDate: Date,\n options: {\n workingDays?: number[];\n holidays?: Date[];\n } = {}\n): WorkingDaysResult {\n const workDays = options.workingDays || DEFAULT_WORK_SCHEDULE.workingDays;\n const holidaySet = new Set(\n (options.holidays || []).map(d => toUTCDateString(d))\n );\n\n let totalDays = 0;\n let workingDays = 0;\n let holidays = 0;\n let weekends = 0;\n\n const current = new Date(startDate);\n current.setHours(0, 0, 0, 0);\n const end = new Date(endDate);\n end.setHours(0, 0, 0, 0);\n\n while (current <= end) {\n totalDays++;\n const isHoliday = holidaySet.has(toUTCDateString(current));\n const isWorkDay = workDays.includes(current.getDay());\n\n if (isHoliday) {\n holidays++;\n } else if (isWorkDay) {\n workingDays++;\n } else {\n weekends++;\n }\n\n current.setDate(current.getDate() + 1);\n }\n\n return { totalDays, workingDays, weekends, holidays };\n}\n\n/**\n * Calculate proration ratio for partial months\n *\n * @example\n * const proration = calculateProration(\n * employee.hireDate,\n * employee.terminationDate,\n * periodStart,\n * periodEnd\n * );\n */\nexport function calculateProration(\n hireDate: Date,\n terminationDate: Date | null | undefined,\n periodStart: Date,\n periodEnd: Date\n): ProrationResult {\n const hire = new Date(hireDate);\n hire.setHours(0, 0, 0, 0);\n const term = terminationDate ? new Date(terminationDate) : null;\n if (term) term.setHours(0, 0, 0, 0);\n const start = new Date(periodStart);\n start.setHours(0, 0, 0, 0);\n const end = new Date(periodEnd);\n end.setHours(0, 0, 0, 0);\n\n // Employee not active in this period\n if (hire > end || (term && term < start)) {\n return { ratio: 0, reason: 'not_active', isProrated: true };\n }\n\n // Effective dates within the period\n const effectiveStart = hire > start ? hire : start;\n const effectiveEnd = term && term < end ? term : end;\n\n // Calculate days\n const totalDays = Math.ceil((end.getTime() - start.getTime()) / 86400000) + 1;\n const actualDays = Math.ceil((effectiveEnd.getTime() - effectiveStart.getTime()) / 86400000) + 1;\n const ratio = Math.min(1, Math.max(0, actualDays / totalDays));\n\n // Determine reason\n const isNewHire = hire > start;\n const isTermination = term !== null && term < end;\n \n let reason: ProrationResult['reason'] = 'full';\n if (isNewHire && isTermination) {\n reason = 'both';\n } else if (isNewHire) {\n reason = 'new_hire';\n } else if (isTermination) {\n reason = 'termination';\n }\n\n return { ratio, reason, isProrated: ratio < 1 };\n}\n\n// NOTE: calculateAttendanceDeduction has been moved to calculators/attendance.calculator.ts\n// The calculator version uses banker's rounding and returns a detailed result object.\n// Re-export from core/index.ts for backward compatibility.\n\n/**\n * Get pay period dates for a given month\n *\n * @example\n * const period = getPayPeriod(3, 2024); // March 2024\n */\nexport function getPayPeriod(\n month: number,\n year: number,\n payDay = 28\n): { startDate: Date; endDate: Date; payDate: Date } {\n const startDate = new Date(year, month - 1, 1);\n const endDate = new Date(year, month, 0); // Last day of month\n const payDate = new Date(year, month - 1, Math.min(payDay, endDate.getDate()));\n return { startDate, endDate, payDate };\n}\n\n","/**\n * @classytic/payroll - Configuration\n *\n * Centralized configuration with type safety\n * Configurable defaults for different use cases\n */\n\nimport type {\n HRMConfig,\n TaxBracket,\n SalaryBandRange,\n RoleMappingConfig,\n OrgRole,\n SalaryBand,\n Department,\n EmploymentType,\n PaymentFrequency,\n DeepPartial,\n EmployeeIdentityMode,\n} from './types.js';\nimport { roundMoney } from './utils/money.js';\n\n// ============================================================================\n// Default Configuration\n// ============================================================================\n\nexport const HRM_CONFIG: HRMConfig = {\n dataRetention: {\n /**\n * Default retention period for payroll records in seconds\n *\n * STANDARD APPROACH: expireAt field + configurable TTL index\n *\n * ## How It Works:\n * 1. Set expireAt date on each payroll record\n * 2. Call PayrollRecord.configureRetention() at app startup\n * 3. MongoDB deletes documents when expireAt is reached\n *\n * ## Usage:\n *\n * @example Configure at initialization\n * ```typescript\n * await payroll.init({ ... });\n * await PayrollRecord.configureRetention(0); // 0 = delete when expireAt reached\n * ```\n *\n * @example Set expireAt per record\n * ```typescript\n * const expireAt = PayrollRecord.calculateExpireAt(7); // 7 years\n * await PayrollRecord.updateOne({ _id }, { expireAt });\n * ```\n *\n * ## Jurisdiction Requirements:\n * - USA: 7 years → 220752000 seconds\n * - EU/UK: 6 years → 189216000 seconds\n * - Germany: 10 years → 315360000 seconds\n * - India: 8 years → 252288000 seconds\n *\n * Set to 0 to disable TTL\n */\n payrollRecordsTTL: 63072000, // 2 years - adjust per jurisdiction\n exportWarningDays: 30,\n archiveBeforeDeletion: true,\n },\n\n payroll: {\n defaultCurrency: 'USD',\n allowProRating: true,\n attendanceIntegration: true,\n autoDeductions: true,\n overtimeEnabled: false,\n overtimeMultiplier: 1.5,\n },\n\n salary: {\n minimumWage: 0,\n maximumAllowances: 10,\n maximumDeductions: 10,\n defaultFrequency: 'monthly',\n },\n\n employment: {\n defaultProbationMonths: 3,\n maxProbationMonths: 6,\n allowReHiring: true,\n trackEmploymentHistory: true,\n },\n\n validation: {\n requireBankDetails: false,\n requireUserId: false, // Modern: Allow guest employees by default\n identityMode: 'employeeId', // Modern: Use human-readable IDs as primary\n identityFallbacks: ['email', 'userId'], // Smart fallback chain\n },\n};\n\n// ============================================================================\n// Salary Bands Configuration\n// ============================================================================\n\nexport const SALARY_BANDS: Record<Exclude<SalaryBand, 'custom'>, SalaryBandRange> = {\n intern: { min: 10000, max: 20000 },\n junior: { min: 20000, max: 40000 },\n mid: { min: 40000, max: 70000 },\n senior: { min: 70000, max: 120000 },\n lead: { min: 100000, max: 200000 },\n executive: { min: 150000, max: 500000 },\n};\n\n// ============================================================================\n// Tax Brackets Configuration\n//\n// These brackets represent progressive tax rates AFTER the tax-free threshold.\n// The threshold is handled separately via jurisdictionTaxConfig.standardDeduction\n// or thresholdsByCategory. Do NOT include a 0% bracket for the tax-free amount —\n// that would cause a double deduction when used with standardDeduction.\n// ============================================================================\n\nexport const TAX_BRACKETS: Record<string, TaxBracket[]> = {\n // Bangladesh FY 2024-25 rates (after tax-free threshold)\n BDT: [\n { min: 0, max: 100000, rate: 0.05 },\n { min: 100000, max: 400000, rate: 0.10 },\n { min: 400000, max: 700000, rate: 0.15 },\n { min: 700000, max: 1100000, rate: 0.20 },\n { min: 1100000, max: Infinity, rate: 0.25 },\n ],\n USD: [\n { min: 0, max: 10000, rate: 0.10 },\n { min: 10000, max: 40000, rate: 0.12 },\n { min: 40000, max: 85000, rate: 0.22 },\n { min: 85000, max: 165000, rate: 0.24 },\n { min: 165000, max: 215000, rate: 0.32 },\n { min: 215000, max: 540000, rate: 0.35 },\n { min: 540000, max: Infinity, rate: 0.37 },\n ],\n};\n\n// ============================================================================\n// Organization Roles Configuration\n// ============================================================================\n\nexport interface OrgRoleDefinition {\n key: OrgRole;\n label: string;\n description: string;\n}\n\nexport const ORG_ROLES: Record<Uppercase<OrgRole>, OrgRoleDefinition> = {\n OWNER: {\n key: 'owner',\n label: 'Owner',\n description: 'Full organization access (set by Organization model)',\n },\n MANAGER: {\n key: 'manager',\n label: 'Manager',\n description: 'Management and administrative features',\n },\n TRAINER: {\n key: 'trainer',\n label: 'Trainer',\n description: 'Training and coaching features',\n },\n STAFF: {\n key: 'staff',\n label: 'Staff',\n description: 'General staff access to basic features',\n },\n INTERN: {\n key: 'intern',\n label: 'Intern',\n description: 'Limited access for interns',\n },\n CONSULTANT: {\n key: 'consultant',\n label: 'Consultant',\n description: 'Project-based consultant access',\n },\n};\n\nexport const ORG_ROLE_KEYS = Object.values(ORG_ROLES).map((role) => role.key);\n\n// ============================================================================\n// Role Mapping Configuration\n// ============================================================================\n\nexport const ROLE_MAPPING: RoleMappingConfig = {\n byDepartment: {\n management: 'manager',\n training: 'trainer',\n sales: 'staff',\n operations: 'staff',\n finance: 'staff',\n hr: 'staff',\n marketing: 'staff',\n it: 'staff',\n support: 'staff',\n maintenance: 'staff',\n },\n\n byEmploymentType: {\n full_time: 'staff',\n part_time: 'staff',\n contract: 'consultant',\n intern: 'intern',\n consultant: 'consultant',\n },\n\n default: 'staff',\n};\n\n// ============================================================================\n// Configuration Functions\n// ============================================================================\n\n/**\n * Calculate tax based on annual income\n *\n * Uses banker's rounding to prevent systematic bias in tax calculations.\n * Tax is calculated progressively across brackets and rounded once at the end.\n *\n * @param annualIncome - Annual income in major units (dollars/rupees/taka)\n * @param currency - Currency code (default: 'USD')\n * @returns Tax amount in major units, properly rounded with banker's rounding\n */\nexport function calculateTax(annualIncome: number, currency = 'USD'): number {\n const brackets = TAX_BRACKETS[currency];\n if (!brackets) return 0;\n\n let tax = 0;\n for (const bracket of brackets) {\n if (annualIncome > bracket.min) {\n const taxableAmount = Math.min(annualIncome, bracket.max) - bracket.min;\n tax += taxableAmount * bracket.rate;\n }\n }\n\n // Use roundMoney for consistency with all other money calculations\n // This ensures identical rounding behavior across all tax calculations\n return roundMoney(tax);\n}\n\n/**\n * Get salary band for a given amount\n */\nexport function getSalaryBand(amount: number): SalaryBand {\n for (const [band, range] of Object.entries(SALARY_BANDS)) {\n if (amount >= range.min && amount <= range.max) {\n return band as SalaryBand;\n }\n }\n return 'custom';\n}\n\n/**\n * Determine the appropriate organization role for an employee\n */\nexport function determineOrgRole(employmentData: {\n department?: Department | string;\n type?: EmploymentType | string;\n position?: string;\n}): OrgRole {\n const { department, type: employmentType } = employmentData;\n\n // Priority 1: Department-based mapping\n if (department && department in ROLE_MAPPING.byDepartment) {\n return ROLE_MAPPING.byDepartment[department as keyof typeof ROLE_MAPPING.byDepartment];\n }\n\n // Priority 2: Employment type mapping\n if (employmentType && employmentType in ROLE_MAPPING.byEmploymentType) {\n return ROLE_MAPPING.byEmploymentType[employmentType as keyof typeof ROLE_MAPPING.byEmploymentType];\n }\n\n // Priority 3: Default role\n return ROLE_MAPPING.default;\n}\n\n/**\n * Get pay periods per year based on frequency\n */\nexport function getPayPeriodsPerYear(frequency: PaymentFrequency): number {\n const periodsMap: Record<PaymentFrequency, number> = {\n monthly: 12,\n bi_weekly: 26,\n weekly: 52,\n daily: 365,\n hourly: 2080, // Assuming 40 hours/week * 52 weeks\n };\n return periodsMap[frequency];\n}\n\n/**\n * Calculate monthly equivalent from any frequency\n */\nexport function toMonthlyAmount(amount: number, frequency: PaymentFrequency): number {\n const periodsPerYear = getPayPeriodsPerYear(frequency);\n return roundMoney((amount * periodsPerYear) / 12, 2);\n}\n\n/**\n * Calculate annual equivalent from any frequency\n */\nexport function toAnnualAmount(amount: number, frequency: PaymentFrequency): number {\n const periodsPerYear = getPayPeriodsPerYear(frequency);\n return roundMoney(amount * periodsPerYear, 2);\n}\n\n/**\n * Merge configuration with defaults\n */\nexport function mergeConfig(\n customConfig: Partial<HRMConfig> | DeepPartial<HRMConfig> | undefined\n): HRMConfig {\n if (!customConfig) return HRM_CONFIG;\n\n return {\n dataRetention: { ...HRM_CONFIG.dataRetention, ...customConfig.dataRetention },\n payroll: { ...HRM_CONFIG.payroll, ...customConfig.payroll },\n salary: { ...HRM_CONFIG.salary, ...customConfig.salary },\n employment: { ...HRM_CONFIG.employment, ...customConfig.employment },\n validation: {\n ...HRM_CONFIG.validation,\n ...customConfig.validation,\n // Ensure fallbacks is always EmployeeIdentityMode[]\n identityFallbacks: (customConfig.validation?.identityFallbacks ?? HRM_CONFIG.validation.identityFallbacks) as EmployeeIdentityMode[]\n },\n };\n}\n\n// ============================================================================\n// Default Export\n// ============================================================================\n\nexport default {\n HRM_CONFIG,\n SALARY_BANDS,\n TAX_BRACKETS,\n ORG_ROLES,\n ORG_ROLE_KEYS,\n ROLE_MAPPING,\n calculateTax,\n getSalaryBand,\n determineOrgRole,\n getPayPeriodsPerYear,\n toMonthlyAmount,\n toAnnualAmount,\n mergeConfig,\n};\n\n","/**\n * @classytic/payroll - Pro-Rating Calculator\n *\n * Pure functions for salary pro-rating calculations.\n * No database dependencies - can be used client-side!\n *\n * Handles:\n * - Mid-period hires\n * - Mid-period terminations\n * - Working days (not calendar days)\n * - Holidays exclusion\n *\n * @packageDocumentation\n */\n\nimport { countWorkingDays } from '../core/config.js';\nimport { roundMoney } from '../utils/money.js';\n\n// ============================================================================\n// Types\n// ============================================================================\n\n/**\n * Input for pro-rating calculation\n */\nexport interface ProRatingInput {\n /**\n * Employee hire date\n */\n hireDate: Date;\n\n /**\n * Employee termination date (null if still employed)\n */\n terminationDate: Date | null;\n\n /**\n * Start of the salary period\n */\n periodStart: Date;\n\n /**\n * End of the salary period\n */\n periodEnd: Date;\n\n /**\n * Working days of the week using Date.getDay() convention (0=Sunday, 1=Monday, ..., 6=Saturday)\n * @default [1, 2, 3, 4, 5] (Monday-Friday)\n */\n workingDays: number[];\n\n /**\n * Public holidays to exclude from working days\n * @default []\n */\n holidays?: Date[];\n}\n\n/**\n * Result of pro-rating calculation\n */\nexport interface ProRatingResult {\n /**\n * Whether the salary needs to be pro-rated\n */\n isProRated: boolean;\n\n /**\n * Pro-rating ratio (0-1)\n * 1 = full salary, 0.5 = half salary, etc.\n */\n ratio: number;\n\n /**\n * Total working days in the period\n */\n periodWorkingDays: number;\n\n /**\n * Working days the employee was actually employed\n */\n effectiveWorkingDays: number;\n\n /**\n * Effective start date (max of hire date and period start)\n */\n effectiveStart: Date;\n\n /**\n * Effective end date (min of termination date and period end)\n */\n effectiveEnd: Date;\n}\n\n// ============================================================================\n// Pure Functions\n// ============================================================================\n\n/**\n * Calculate pro-rating for mid-period hires/terminations\n *\n * This function uses WORKING DAYS (not calendar days) for accurate pro-rating.\n *\n * @example\n * ```typescript\n * // Employee hired on March 15th, process March salary\n * const result = calculateProRating({\n * hireDate: new Date('2024-03-15'),\n * terminationDate: null,\n * periodStart: new Date('2024-03-01'),\n * periodEnd: new Date('2024-03-31'),\n * workingDays: [1, 2, 3, 4, 5], // Mon-Fri\n * });\n * \n * console.log(result);\n * // {\n * // isProRated: true,\n * // ratio: 0.64, // Worked 14 out of 22 working days\n * // periodWorkingDays: 22,\n * // effectiveWorkingDays: 14\n * // }\n * ```\n *\n * @param input - Pro-rating calculation parameters\n * @returns Pro-rating result with ratio and working days breakdown\n *\n * @pure This function has no side effects and doesn't access external state\n */\nexport function calculateProRating(input: ProRatingInput): ProRatingResult {\n const { hireDate, terminationDate, periodStart, periodEnd, workingDays, holidays = [] } = input;\n\n const hire = new Date(hireDate);\n const termination = terminationDate ? new Date(terminationDate) : null;\n\n // Determine the actual start and end dates for this employee in this period\n const effectiveStart = hire > periodStart ? hire : periodStart;\n const effectiveEnd = termination && termination < periodEnd ? termination : periodEnd;\n\n // If employee wasn't active during this period at all\n if (effectiveStart > periodEnd || (termination && termination < periodStart)) {\n const periodWorkingDays = countWorkingDays(periodStart, periodEnd, { workingDays, holidays }).workingDays;\n return {\n isProRated: true,\n ratio: 0,\n periodWorkingDays,\n effectiveWorkingDays: 0,\n effectiveStart: periodStart,\n effectiveEnd: periodStart, // Effectively zero days\n };\n }\n\n // Calculate working days for the full period\n const periodWorkingDays = countWorkingDays(periodStart, periodEnd, { workingDays, holidays }).workingDays;\n\n // Calculate working days the employee was actually employed\n const effectiveWorkingDays = countWorkingDays(effectiveStart, effectiveEnd, { workingDays, holidays }).workingDays;\n\n // Calculate ratio\n const ratio = periodWorkingDays > 0 \n ? Math.min(1, Math.max(0, effectiveWorkingDays / periodWorkingDays)) \n : 0;\n\n // Is pro-rated if ratio is less than 1\n const isProRated = ratio < 1;\n\n return {\n isProRated,\n ratio,\n periodWorkingDays,\n effectiveWorkingDays,\n effectiveStart,\n effectiveEnd,\n };\n}\n\n/**\n * Calculate pro-rated amount from base amount and ratio\n *\n * @example\n * ```typescript\n * const proRatedSalary = applyProRating(100000, 0.64); // 64000\n * ```\n *\n * @param baseAmount - Original amount\n * @param ratio - Pro-rating ratio (0-1)\n * @returns Pro-rated amount (rounded)\n *\n * @pure No side effects\n */\nexport function applyProRating(baseAmount: number, ratio: number): number {\n return roundMoney(baseAmount * ratio, 2);\n}\n\n/**\n * Check if pro-rating should be applied for a given hire/termination scenario\n *\n * @param hireDate - Employee hire date\n * @param terminationDate - Employee termination date (null if active)\n * @param periodStart - Salary period start\n * @param periodEnd - Salary period end\n * @returns True if pro-rating is needed\n *\n * @pure No side effects\n */\nexport function shouldProRate(\n hireDate: Date,\n terminationDate: Date | null,\n periodStart: Date,\n periodEnd: Date\n): boolean {\n const hire = new Date(hireDate);\n const termination = terminationDate ? new Date(terminationDate) : null;\n\n // Pro-rate if hired after period start\n if (hire > periodStart) return true;\n\n // Pro-rate if terminated before period end\n if (termination && termination < periodEnd) return true;\n\n return false;\n}\n\n","/**\n * @classytic/payroll - Attendance Deduction Calculator\n *\n * Pure functions for calculating salary deductions based on attendance.\n * No database dependencies - can be used client-side!\n *\n * All monetary calculations use banker's rounding for financial accuracy.\n *\n * @packageDocumentation\n */\n\nimport { roundMoney } from '../utils/money.js';\n\n// ============================================================================\n// Types\n// ============================================================================\n\n/**\n * Input for attendance deduction calculation\n */\nexport interface AttendanceDeductionInput {\n /**\n * Expected working days in the period (for this specific employee)\n * Should account for hire/termination dates\n */\n expectedWorkingDays: number;\n\n /**\n * Actual working days the employee was present\n */\n actualWorkingDays: number;\n\n /**\n * Daily salary rate for this employee\n * Calculated as: baseAmount / expectedWorkingDays\n */\n dailyRate: number;\n}\n\n/**\n * Result of attendance deduction calculation\n */\nexport interface AttendanceDeductionResult {\n /**\n * Number of absent days\n */\n absentDays: number;\n\n /**\n * Total deduction amount\n */\n deductionAmount: number;\n\n /**\n * Daily rate used for calculation\n */\n dailyRate: number;\n\n /**\n * Whether any deduction was applied\n */\n hasDeduction: boolean;\n}\n\n// ============================================================================\n// Pure Functions\n// ============================================================================\n\n/**\n * Calculate attendance deduction based on absent days\n *\n * @example\n * ```typescript\n * const result = calculateAttendanceDeduction({\n * expectedWorkingDays: 22,\n * actualWorkingDays: 20, // 2 days absent\n * dailyRate: 4545, // 100000 / 22\n * });\n *\n * console.log(result);\n * // {\n * // absentDays: 2,\n * // deductionAmount: 9090,\n * // dailyRate: 4545,\n * // hasDeduction: true\n * // }\n * ```\n *\n * @param input - Attendance deduction parameters\n * @returns Deduction result with breakdown\n *\n * @pure This function has no side effects\n */\nexport function calculateAttendanceDeduction(input: AttendanceDeductionInput): AttendanceDeductionResult {\n const { expectedWorkingDays, actualWorkingDays, dailyRate } = input;\n\n // Guard against negative values\n const expected = Math.max(0, expectedWorkingDays);\n const actual = Math.max(0, actualWorkingDays);\n const rate = Math.max(0, dailyRate);\n\n // Calculate absent days (cannot be negative)\n const absentDays = Math.max(0, expected - actual);\n\n // Calculate deduction amount (banker's rounding to cents)\n const deductionAmount = roundMoney(absentDays * rate, 2);\n\n return {\n absentDays,\n deductionAmount,\n dailyRate: rate,\n hasDeduction: deductionAmount > 0,\n };\n}\n\n/**\n * Calculate daily rate from monthly salary and working days\n *\n * @example\n * ```typescript\n * const daily = calculateDailyRate(100000, 22); // 4545\n * ```\n *\n * @param monthlySalary - Monthly base salary\n * @param workingDays - Working days in the month\n * @returns Daily rate (rounded)\n *\n * @pure No side effects\n */\nexport function calculateDailyRate(monthlySalary: number, workingDays: number): number {\n if (workingDays <= 0) return 0;\n return roundMoney(monthlySalary / workingDays, 2);\n}\n\n/**\n * Calculate hourly rate from monthly salary\n *\n * @example\n * ```typescript\n * const hourly = calculateHourlyRate(100000, 22, 8); // 568\n * ```\n *\n * @param monthlySalary - Monthly base salary\n * @param workingDays - Working days in the month\n * @param hoursPerDay - Hours per working day (default: 8)\n * @returns Hourly rate (rounded)\n *\n * @pure No side effects\n */\nexport function calculateHourlyRate(\n monthlySalary: number,\n workingDays: number,\n hoursPerDay: number = 8\n): number {\n const dailyRate = calculateDailyRate(monthlySalary, workingDays);\n if (hoursPerDay <= 0) return 0;\n return roundMoney(dailyRate / hoursPerDay, 2);\n}\n\n/**\n * Calculate deduction for partial day absence (half-day, quarter-day, etc.)\n *\n * @example\n * ```typescript\n * // Half-day absence\n * const deduction = calculatePartialDayDeduction(4545, 0.5); // 2272\n * ```\n *\n * @param dailyRate - Daily salary rate\n * @param fractionAbsent - Fraction of day absent (0-1)\n * @returns Deduction amount (rounded)\n *\n * @pure No side effects\n */\nexport function calculatePartialDayDeduction(dailyRate: number, fractionAbsent: number): number {\n const fraction = Math.min(1, Math.max(0, fractionAbsent));\n return roundMoney(dailyRate * fraction, 2);\n}\n\n/**\n * Calculate total attendance deduction including full and partial day absences\n *\n * @example\n * ```typescript\n * const result = calculateTotalAttendanceDeduction({\n * dailyRate: 4545,\n * fullDayAbsences: 2,\n * partialDayAbsences: [0.5, 0.25], // Half-day + quarter-day\n * });\n * \n * console.log(result);\n * // {\n * // fullDayDeduction: 9090,\n * // partialDayDeduction: 3408,\n * // totalDeduction: 12498\n * // }\n * ```\n *\n * @param input - Absence breakdown\n * @returns Deduction breakdown and total\n *\n * @pure No side effects\n */\nexport function calculateTotalAttendanceDeduction(input: {\n dailyRate: number;\n fullDayAbsences?: number;\n partialDayAbsences?: number[];\n}): {\n fullDayDeduction: number;\n partialDayDeduction: number;\n totalDeduction: number;\n} {\n const { dailyRate, fullDayAbsences = 0, partialDayAbsences = [] } = input;\n\n // Full day deductions (banker's rounding to whole units)\n const fullDayDeduction = roundMoney(dailyRate * Math.max(0, fullDayAbsences), 2);\n\n // Partial day deductions\n const partialDayDeduction = partialDayAbsences.reduce(\n (sum, fraction) => sum + calculatePartialDayDeduction(dailyRate, fraction),\n 0\n );\n\n return {\n fullDayDeduction,\n partialDayDeduction,\n totalDeduction: fullDayDeduction + partialDayDeduction,\n };\n}\n\n","/**\n * @classytic/payroll - Salary Calculator\n *\n * Pure functions for complete salary breakdown calculations.\n * No database dependencies - can be used client-side!\n *\n * This is the SINGLE SOURCE OF TRUTH for all salary calculations.\n *\n * @packageDocumentation\n */\n\nimport type {\n Compensation,\n PayrollBreakdown,\n Allowance,\n Deduction,\n TaxBracket,\n TaxCalculationOptions,\n PaymentFrequency,\n} from '../types.js';\nimport { calculateGross, calculateNet, sumAllowances, sumDeductions, applyTaxBrackets } from '../utils/calculation.js';\nimport { roundMoney, percentageOf, prorateAmount } from '../utils/money.js';\nimport { isEffectiveForPeriod } from '../utils/date.js';\nimport { countWorkingDays } from '../core/config.js';\nimport { getPayPeriodsPerYear } from '../config.js';\nimport { calculateProRating, type ProRatingInput, type ProRatingResult } from './prorating.calculator.js';\nimport { calculateAttendanceDeduction, calculateDailyRate, type AttendanceDeductionInput } from './attendance.calculator.js';\n\n// ============================================================================\n// Types\n// ============================================================================\n\n/**\n * Input for salary breakdown calculation\n */\nexport interface SalaryCalculationInput {\n /**\n * Employee data (minimal subset needed for calculation)\n */\n employee: {\n hireDate: Date;\n terminationDate?: Date | null;\n compensation: Compensation;\n workSchedule?: {\n workingDays?: number[];\n hoursPerDay?: number;\n };\n };\n\n /**\n * Salary period\n */\n period: {\n month: number;\n year: number;\n startDate: Date;\n endDate: Date;\n };\n\n /**\n * Attendance data (optional)\n */\n attendance?: {\n expectedDays?: number;\n actualDays?: number;\n } | null;\n\n /**\n * Processing options\n */\n options?: {\n holidays?: Date[];\n workSchedule?: {\n workingDays?: number[];\n hoursPerDay?: number;\n };\n skipTax?: boolean;\n skipAttendance?: boolean;\n skipProration?: boolean;\n };\n\n /**\n * Configuration (minimal subset)\n */\n config: {\n allowProRating: boolean;\n autoDeductions: boolean;\n defaultCurrency: string;\n attendanceIntegration: boolean;\n };\n\n /**\n * Tax brackets for the employee's currency\n */\n taxBrackets: TaxBracket[];\n\n /**\n * Enhanced tax calculation options (optional)\n *\n * When provided, enables jurisdiction-aware tax calculation with:\n * - Standard deduction / tax-free threshold\n * - Demographic-based thresholds (senior, disabled, etc.)\n * - Pre-tax deductions handling\n * - Tax credits/rebates\n *\n * @example\n * ```typescript\n * taxOptions: {\n * applyStandardDeduction: true,\n * taxpayerCategory: 'senior',\n * preTaxDeductions: [{ type: 'provident_fund', amount: 5000 }],\n * taxCredits: [{ type: 'investment', amount: 2000 }],\n * }\n * ```\n */\n taxOptions?: TaxCalculationOptions;\n\n /**\n * Jurisdiction tax configuration (optional)\n *\n * When provided alongside taxOptions, enables lookup of:\n * - standardDeduction from jurisdiction\n * - thresholdsByCategory for taxpayer category\n * - preTaxDeductionTypes for automatic pre-tax detection\n */\n jurisdictionTaxConfig?: {\n /** Standard deduction amount (annual) */\n standardDeduction?: number;\n /** Tax-free thresholds by taxpayer category (annual) */\n thresholdsByCategory?: Record<string, number>;\n /** Recognized pre-tax deduction types */\n preTaxDeductionTypes?: string[];\n };\n}\n\n/**\n * Processed allowance with calculated amount\n */\nexport interface ProcessedAllowance {\n type: string;\n amount: number;\n taxable: boolean;\n originalAmount?: number; // Before pro-rating\n isPercentage?: boolean;\n value?: number;\n}\n\n/**\n * Processed deduction with calculated amount\n */\nexport interface ProcessedDeduction {\n type: string;\n amount: number;\n description?: string;\n originalAmount?: number; // Before pro-rating\n isPercentage?: boolean;\n value?: number;\n}\n\n// ============================================================================\n// Pure Functions\n// ============================================================================\n\n/**\n * Calculate complete salary breakdown\n *\n * This is the SINGLE SOURCE OF TRUTH for salary calculations.\n * All payroll processing uses this function.\n *\n * @example\n * ```typescript\n * const breakdown = calculateSalaryBreakdown({\n * employee: {\n * hireDate: new Date('2024-01-01'),\n * compensation: {\n * baseAmount: 100000,\n * currency: 'USD',\n * allowances: [{ type: 'housing', amount: 20000, taxable: true }],\n * deductions: [{ type: 'insurance', amount: 5000 }],\n * },\n * },\n * period: {\n * month: 3,\n * year: 2024,\n * startDate: new Date('2024-03-01'),\n * endDate: new Date('2024-03-31'),\n * },\n * attendance: {\n * expectedDays: 22,\n * actualDays: 20, // 2 days absent\n * },\n * options: {\n * holidays: [new Date('2024-03-26')],\n * },\n * config: {\n * allowProRating: true,\n * autoDeductions: true,\n * defaultCurrency: 'USD',\n * attendanceIntegration: true,\n * },\n * taxBrackets: [...],\n * });\n * ```\n *\n * @param input - Salary calculation parameters\n * @returns Complete payroll breakdown\n *\n * @pure This function has no side effects and doesn't access database\n */\nexport function calculateSalaryBreakdown(input: SalaryCalculationInput): PayrollBreakdown {\n const { employee, period, attendance, options = {}, config, taxBrackets, taxOptions, jurisdictionTaxConfig } = input;\n\n const comp = employee.compensation;\n const originalBaseAmount = comp.baseAmount;\n\n // 1. Calculate pro-rating (if applicable)\n const proRating = calculateProRatingForSalary(\n employee.hireDate,\n employee.terminationDate || null,\n period.startDate,\n period.endDate,\n options,\n employee.workSchedule\n );\n\n // 2. Apply pro-rating to base salary\n let baseAmount = originalBaseAmount;\n if (proRating.isProRated && config.allowProRating && !options.skipProration) {\n baseAmount = prorateAmount(baseAmount, proRating.ratio);\n }\n\n // 3. Filter allowances by effective date\n const effectiveAllowances = (comp.allowances || [])\n .filter((a) => isEffectiveForPeriod(a, period.startDate, period.endDate));\n\n // 4. Filter deductions by effective date\n const effectiveDeductions = (comp.deductions || [])\n .filter((d) => isEffectiveForPeriod(d, period.startDate, period.endDate))\n .filter((d) => d.auto || d.recurring);\n\n // 5. Calculate allowances (handle percentages and pro-rating)\n const allowances = processAllowances(effectiveAllowances, originalBaseAmount, proRating, config, options.skipProration);\n\n // 6. Calculate deductions (handle percentages and pro-rating)\n const deductions = processDeductions(effectiveDeductions, originalBaseAmount, proRating, config, options.skipProration);\n\n // 7. Calculate attendance deduction\n if (!options.skipAttendance && config.attendanceIntegration && attendance) {\n const attendanceDeductionResult = calculateAttendanceDeductionFromData(\n attendance,\n baseAmount,\n proRating.effectiveWorkingDays\n );\n\n if (attendanceDeductionResult.hasDeduction) {\n deductions.push({\n type: 'absence',\n amount: attendanceDeductionResult.deductionAmount,\n description: `Unpaid leave deduction (${attendanceDeductionResult.absentDays} days)`,\n });\n }\n }\n\n // 8. Calculate gross salary\n const grossSalary = calculateGross(baseAmount, allowances);\n\n // 9. Calculate taxable amount with enhanced tax options\n const taxableAllowances = allowances.filter((a) => a.taxable);\n let taxableAmount = baseAmount + sumAllowances(taxableAllowances);\n\n // 9a. Apply pre-tax deductions (reduce taxable income)\n const preTaxDeductionAmount = calculatePreTaxDeductions(\n effectiveDeductions,\n deductions,\n taxOptions,\n jurisdictionTaxConfig\n );\n taxableAmount = Math.max(0, taxableAmount - preTaxDeductionAmount);\n\n // 10. Calculate tax with enhanced options (frequency-aware)\n const frequency = employee.compensation?.frequency || 'monthly';\n let taxAmount = 0;\n if (!options.skipTax && taxBrackets.length > 0 && config.autoDeductions) {\n taxAmount = calculateEnhancedTax(\n taxableAmount,\n taxBrackets,\n taxOptions,\n jurisdictionTaxConfig,\n frequency\n );\n }\n\n // Add tax to deductions if applicable\n if (taxAmount > 0) {\n deductions.push({\n type: 'tax',\n amount: taxAmount,\n description: 'Income tax',\n });\n }\n\n // 11. Calculate net salary\n const netSalary = calculateNet(grossSalary, deductions);\n\n // 12. Build final breakdown\n return {\n baseAmount,\n allowances,\n deductions,\n grossSalary,\n netSalary,\n taxableAmount,\n taxAmount,\n workingDays: proRating.periodWorkingDays,\n actualDays: proRating.effectiveWorkingDays,\n proRatedAmount: (proRating.isProRated && !options.skipProration) ? baseAmount : 0,\n attendanceDeduction: attendance\n ? deductions.find((d) => d.type === 'absence')?.amount || 0\n : 0,\n };\n}\n\n// ============================================================================\n// Helper Functions\n// ============================================================================\n\n/**\n * Calculate pro-rating for salary calculation\n */\nfunction calculateProRatingForSalary(\n hireDate: Date,\n terminationDate: Date | null,\n periodStart: Date,\n periodEnd: Date,\n options: SalaryCalculationInput['options'],\n employeeWorkSchedule?: { workingDays?: number[] }\n): ProRatingResult {\n // Work schedule: prefer operation override, then employee schedule, then Mon-Fri default\n const workingDays =\n options?.workSchedule?.workingDays ||\n employeeWorkSchedule?.workingDays ||\n [1, 2, 3, 4, 5];\n\n const holidays = options?.holidays || [];\n\n return calculateProRating({\n hireDate,\n terminationDate,\n periodStart,\n periodEnd,\n workingDays,\n holidays,\n });\n}\n\n/**\n * Process allowances (handle percentages and pro-rating)\n */\nfunction processAllowances(\n allowances: Allowance[],\n originalBaseAmount: number,\n proRating: ProRatingResult,\n config: SalaryCalculationInput['config'],\n skipProration?: boolean\n): ProcessedAllowance[] {\n return allowances.map((a) => {\n // Calculate from original base (percentage) or use fixed amount\n let amount = a.isPercentage && a.value !== undefined\n ? percentageOf(originalBaseAmount, a.value)\n : a.amount;\n\n const originalAmount = amount;\n\n // Apply pro-rating ONCE if needed (respect skipProration flag)\n if (proRating.isProRated && config.allowProRating && !skipProration) {\n amount = prorateAmount(amount, proRating.ratio);\n }\n\n return {\n type: a.type,\n amount,\n taxable: a.taxable ?? true,\n originalAmount,\n isPercentage: a.isPercentage,\n value: a.value,\n };\n });\n}\n\n/**\n * Process deductions (handle percentages and pro-rating)\n */\nfunction processDeductions(\n deductions: Deduction[],\n originalBaseAmount: number,\n proRating: ProRatingResult,\n config: SalaryCalculationInput['config'],\n skipProration?: boolean\n): ProcessedDeduction[] {\n return deductions.map((d) => {\n // Calculate from original base (percentage) or use fixed amount\n let amount = d.isPercentage && d.value !== undefined\n ? percentageOf(originalBaseAmount, d.value)\n : d.amount;\n\n const originalAmount = amount;\n\n // Apply pro-rating ONCE if needed (respect skipProration flag)\n if (proRating.isProRated && config.allowProRating && !skipProration) {\n amount = prorateAmount(amount, proRating.ratio);\n }\n\n return {\n type: d.type,\n amount,\n description: d.description,\n originalAmount,\n isPercentage: d.isPercentage,\n value: d.value,\n };\n });\n}\n\n/**\n * Calculate attendance deduction from attendance data\n */\nfunction calculateAttendanceDeductionFromData(\n attendance: { expectedDays?: number; actualDays?: number },\n baseAmount: number,\n effectiveWorkingDays: number\n): {\n hasDeduction: boolean;\n deductionAmount: number;\n absentDays: number;\n} {\n const expectedDays = attendance.expectedDays ?? effectiveWorkingDays;\n const actualDays = attendance.actualDays;\n\n if (actualDays === undefined) {\n return { hasDeduction: false, deductionAmount: 0, absentDays: 0 };\n }\n\n // Daily rate based on expected working days for THIS employee in THIS period\n const dailyRate = calculateDailyRate(baseAmount, expectedDays);\n\n const result = calculateAttendanceDeduction({\n expectedWorkingDays: expectedDays,\n actualWorkingDays: actualDays,\n dailyRate,\n });\n\n return {\n hasDeduction: result.hasDeduction,\n deductionAmount: result.deductionAmount,\n absentDays: result.absentDays,\n };\n}\n\n/**\n * Calculate total pre-tax deductions (monthly)\n *\n * Pre-tax deductions reduce taxable income before tax brackets are applied.\n * Sources:\n * 1. Employee deductions with reducesTaxableIncome=true\n * 2. Deductions matching jurisdictionTaxConfig.preTaxDeductionTypes\n * 3. Explicit taxOptions.preTaxDeductions\n */\nfunction calculatePreTaxDeductions(\n effectiveDeductions: Deduction[],\n processedDeductions: ProcessedDeduction[],\n taxOptions?: TaxCalculationOptions,\n jurisdictionTaxConfig?: SalaryCalculationInput['jurisdictionTaxConfig']\n): number {\n let totalPreTax = 0;\n\n // NOTE: effectiveDeductions[i] and processedDeductions[i] are 1:1 aligned.\n // processDeductions() builds processedDeductions from effectiveDeductions in order.\n // Attendance deductions are appended to processedDeductions AFTER this array is built,\n // so they won't be iterated here (effectiveDeductions.length < processedDeductions.length\n // when attendance deductions exist, but we only iterate up to effectiveDeductions.length).\n\n // 1. Sum deductions marked as reducesTaxableIncome\n for (let i = 0; i < effectiveDeductions.length; i++) {\n const original = effectiveDeductions[i];\n const processed = processedDeductions[i];\n\n if (original.reducesTaxableIncome) {\n totalPreTax += processed?.amount || 0;\n }\n }\n\n // 2. Sum deductions matching jurisdiction's preTaxDeductionTypes\n if (jurisdictionTaxConfig?.preTaxDeductionTypes?.length) {\n const preTaxTypes = new Set(jurisdictionTaxConfig.preTaxDeductionTypes);\n\n for (let i = 0; i < effectiveDeductions.length; i++) {\n const original = effectiveDeductions[i];\n const processed = processedDeductions[i];\n\n // Skip if already counted via reducesTaxableIncome\n if (original.reducesTaxableIncome) continue;\n\n // Check if deduction type is in pre-tax list\n if (preTaxTypes.has(original.type)) {\n totalPreTax += processed?.amount || 0;\n }\n }\n }\n\n // 3. Add explicit pre-tax deductions from taxOptions\n if (taxOptions?.preTaxDeductions?.length) {\n for (const deduction of taxOptions.preTaxDeductions) {\n totalPreTax += deduction.amount;\n }\n }\n\n return roundMoney(totalPreTax);\n}\n\n/**\n * Calculate tax with enhanced options\n *\n * Supports:\n * - Standard deduction / tax-free threshold\n * - Demographic-based thresholds (taxpayerCategory)\n * - Tax credits/rebates\n * - Multiple payment frequencies (weekly, bi_weekly, monthly, etc.)\n *\n * @param periodTaxable - Taxable amount for the pay period (after pre-tax deductions)\n * @param taxBrackets - Tax brackets (for annual income)\n * @param taxOptions - Enhanced tax calculation options\n * @param jurisdictionTaxConfig - Jurisdiction tax configuration\n * @param frequency - Payment frequency (determines periods per year)\n * @returns Tax amount for the pay period (after credits)\n */\nfunction calculateEnhancedTax(\n periodTaxable: number,\n taxBrackets: TaxBracket[],\n taxOptions?: TaxCalculationOptions,\n jurisdictionTaxConfig?: SalaryCalculationInput['jurisdictionTaxConfig'],\n frequency: PaymentFrequency = 'monthly'\n): number {\n // Get pay periods per year based on frequency\n const periodsPerYear = getPayPeriodsPerYear(frequency);\n\n // Annualize the taxable amount\n let annualTaxable = periodTaxable * periodsPerYear;\n\n // Apply standard deduction or threshold\n const threshold = getApplicableThreshold(taxOptions, jurisdictionTaxConfig);\n if (threshold > 0) {\n annualTaxable = Math.max(0, annualTaxable - threshold);\n }\n\n // Calculate tax using brackets\n let annualTax = applyTaxBrackets(annualTaxable, taxBrackets);\n\n // Apply tax credits (reduce tax liability)\n if (taxOptions?.taxCredits?.length && annualTax > 0) {\n annualTax = applyTaxCredits(annualTax, taxOptions.taxCredits);\n }\n\n // Return period tax (banker's rounding)\n return roundMoney(annualTax / periodsPerYear);\n}\n\n/**\n * Get applicable tax-free threshold based on options\n *\n * Priority:\n * 1. taxOptions.standardDeductionOverride (explicit override)\n * 2. taxOptions.thresholdOverrides[taxpayerCategory]\n * 3. jurisdictionTaxConfig.thresholdsByCategory[taxpayerCategory]\n * 4. jurisdictionTaxConfig.standardDeduction (if applyStandardDeduction)\n */\nfunction getApplicableThreshold(\n taxOptions?: TaxCalculationOptions,\n jurisdictionTaxConfig?: SalaryCalculationInput['jurisdictionTaxConfig']\n): number {\n // 1. Explicit override takes highest priority\n if (taxOptions?.standardDeductionOverride !== undefined) {\n return taxOptions.standardDeductionOverride;\n }\n\n // 2. Check taxpayer category thresholds\n if (taxOptions?.taxpayerCategory) {\n const category = taxOptions.taxpayerCategory;\n\n // Check override thresholds first\n if (taxOptions.thresholdOverrides?.[category] !== undefined) {\n return taxOptions.thresholdOverrides[category];\n }\n\n // Check jurisdiction thresholds\n if (jurisdictionTaxConfig?.thresholdsByCategory?.[category] !== undefined) {\n return jurisdictionTaxConfig.thresholdsByCategory[category];\n }\n }\n\n // 3. Fall back to standard deduction if enabled\n if (taxOptions?.applyStandardDeduction && jurisdictionTaxConfig?.standardDeduction) {\n return jurisdictionTaxConfig.standardDeduction;\n }\n\n return 0;\n}\n\n/**\n * Apply tax credits to reduce tax liability\n *\n * Credits with maxPercent are capped at that percentage of the original tax.\n * Credits cannot reduce tax below zero.\n */\nfunction applyTaxCredits(\n annualTax: number,\n taxCredits: NonNullable<TaxCalculationOptions['taxCredits']>\n): number {\n let remainingTax = annualTax;\n\n for (const credit of taxCredits) {\n if (remainingTax <= 0) break;\n\n let creditAmount = credit.amount;\n\n // Apply maxPercent cap if specified\n if (credit.maxPercent !== undefined && credit.maxPercent > 0) {\n const maxCredit = annualTax * credit.maxPercent;\n creditAmount = Math.min(creditAmount, maxCredit);\n }\n\n // Credit cannot exceed remaining tax\n creditAmount = Math.min(creditAmount, remainingTax);\n remainingTax -= creditAmount;\n }\n\n return Math.max(0, remainingTax);\n}\n\n"]}