@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.
- package/README.md +333 -323
- package/dist/attendance.calculator-BZcv2iii.d.ts +336 -0
- package/dist/calculators/index.d.ts +3 -299
- package/dist/calculators/index.js +154 -19
- package/dist/calculators/index.js.map +1 -1
- package/dist/core/index.d.ts +321 -0
- package/dist/core/index.js +1962 -0
- package/dist/core/index.js.map +1 -0
- package/dist/{employee-identity-Cq2wo9-2.d.ts → error-helpers-Bm6lMny2.d.ts} +257 -7
- package/dist/{index-DjB72l6e.d.ts → index-BKLkuSAs.d.ts} +248 -132
- package/dist/index.d.ts +418 -658
- package/dist/index.js +1179 -373
- package/dist/index.js.map +1 -1
- package/dist/payroll-states-DBt0XVm-.d.ts +598 -0
- package/dist/{prorating.calculator-C7sdFiG2.d.ts → prorating.calculator-C33fWBQf.d.ts} +2 -2
- package/dist/schemas/index.d.ts +2 -2
- package/dist/schemas/index.js +95 -75
- package/dist/schemas/index.js.map +1 -1
- package/dist/{types-BVDjiVGS.d.ts → types-bZdAJueH.d.ts} +427 -12
- package/dist/utils/index.d.ts +17 -5
- package/dist/utils/index.js +185 -25
- package/dist/utils/index.js.map +1 -1
- package/package.json +5 -1
|
@@ -45,6 +45,11 @@ function applyTaxBrackets(amount, brackets) {
|
|
|
45
45
|
}
|
|
46
46
|
|
|
47
47
|
// src/utils/date.ts
|
|
48
|
+
function toUTCDateString(date) {
|
|
49
|
+
const d = new Date(date);
|
|
50
|
+
d.setHours(0, 0, 0, 0);
|
|
51
|
+
return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, "0")}-${String(d.getDate()).padStart(2, "0")}`;
|
|
52
|
+
}
|
|
48
53
|
function isEffectiveForPeriod(item, periodStart, periodEnd) {
|
|
49
54
|
const effectiveFrom = item.effectiveFrom ? new Date(item.effectiveFrom) : /* @__PURE__ */ new Date(0);
|
|
50
55
|
const effectiveTo = item.effectiveTo ? new Date(item.effectiveTo) : /* @__PURE__ */ new Date("2099-12-31");
|
|
@@ -57,7 +62,7 @@ var DEFAULT_WORK_SCHEDULE = {
|
|
|
57
62
|
function countWorkingDays(startDate, endDate, options = {}) {
|
|
58
63
|
const workDays = options.workingDays || DEFAULT_WORK_SCHEDULE.workingDays;
|
|
59
64
|
const holidaySet = new Set(
|
|
60
|
-
(options.holidays || []).map((d) =>
|
|
65
|
+
(options.holidays || []).map((d) => toUTCDateString(d))
|
|
61
66
|
);
|
|
62
67
|
let totalDays = 0;
|
|
63
68
|
let workingDays = 0;
|
|
@@ -69,7 +74,7 @@ function countWorkingDays(startDate, endDate, options = {}) {
|
|
|
69
74
|
end.setHours(0, 0, 0, 0);
|
|
70
75
|
while (current <= end) {
|
|
71
76
|
totalDays++;
|
|
72
|
-
const isHoliday = holidaySet.has(current
|
|
77
|
+
const isHoliday = holidaySet.has(toUTCDateString(current));
|
|
73
78
|
const isWorkDay = workDays.includes(current.getDay());
|
|
74
79
|
if (isHoliday) {
|
|
75
80
|
holidays++;
|
|
@@ -83,6 +88,52 @@ function countWorkingDays(startDate, endDate, options = {}) {
|
|
|
83
88
|
return { totalDays, workingDays, weekends, holidays };
|
|
84
89
|
}
|
|
85
90
|
|
|
91
|
+
// src/config.ts
|
|
92
|
+
var ORG_ROLES = {
|
|
93
|
+
OWNER: {
|
|
94
|
+
key: "owner",
|
|
95
|
+
label: "Owner",
|
|
96
|
+
description: "Full organization access (set by Organization model)"
|
|
97
|
+
},
|
|
98
|
+
MANAGER: {
|
|
99
|
+
key: "manager",
|
|
100
|
+
label: "Manager",
|
|
101
|
+
description: "Management and administrative features"
|
|
102
|
+
},
|
|
103
|
+
TRAINER: {
|
|
104
|
+
key: "trainer",
|
|
105
|
+
label: "Trainer",
|
|
106
|
+
description: "Training and coaching features"
|
|
107
|
+
},
|
|
108
|
+
STAFF: {
|
|
109
|
+
key: "staff",
|
|
110
|
+
label: "Staff",
|
|
111
|
+
description: "General staff access to basic features"
|
|
112
|
+
},
|
|
113
|
+
INTERN: {
|
|
114
|
+
key: "intern",
|
|
115
|
+
label: "Intern",
|
|
116
|
+
description: "Limited access for interns"
|
|
117
|
+
},
|
|
118
|
+
CONSULTANT: {
|
|
119
|
+
key: "consultant",
|
|
120
|
+
label: "Consultant",
|
|
121
|
+
description: "Project-based consultant access"
|
|
122
|
+
}
|
|
123
|
+
};
|
|
124
|
+
Object.values(ORG_ROLES).map((role) => role.key);
|
|
125
|
+
function getPayPeriodsPerYear(frequency) {
|
|
126
|
+
const periodsMap = {
|
|
127
|
+
monthly: 12,
|
|
128
|
+
bi_weekly: 26,
|
|
129
|
+
weekly: 52,
|
|
130
|
+
daily: 365,
|
|
131
|
+
hourly: 2080
|
|
132
|
+
// Assuming 40 hours/week * 52 weeks
|
|
133
|
+
};
|
|
134
|
+
return periodsMap[frequency];
|
|
135
|
+
}
|
|
136
|
+
|
|
86
137
|
// src/calculators/prorating.calculator.ts
|
|
87
138
|
function calculateProRating(input) {
|
|
88
139
|
const { hireDate, terminationDate, periodStart, periodEnd, workingDays, holidays = [] } = input;
|
|
@@ -116,7 +167,7 @@ function calculateProRating(input) {
|
|
|
116
167
|
};
|
|
117
168
|
}
|
|
118
169
|
function applyProRating(baseAmount, ratio) {
|
|
119
|
-
return
|
|
170
|
+
return roundMoney(baseAmount * ratio, 2);
|
|
120
171
|
}
|
|
121
172
|
function shouldProRate(hireDate, terminationDate, periodStart, periodEnd) {
|
|
122
173
|
const hire = new Date(hireDate);
|
|
@@ -133,7 +184,7 @@ function calculateAttendanceDeduction(input) {
|
|
|
133
184
|
const actual = Math.max(0, actualWorkingDays);
|
|
134
185
|
const rate = Math.max(0, dailyRate);
|
|
135
186
|
const absentDays = Math.max(0, expected - actual);
|
|
136
|
-
const deductionAmount =
|
|
187
|
+
const deductionAmount = roundMoney(absentDays * rate, 2);
|
|
137
188
|
return {
|
|
138
189
|
absentDays,
|
|
139
190
|
deductionAmount,
|
|
@@ -143,20 +194,20 @@ function calculateAttendanceDeduction(input) {
|
|
|
143
194
|
}
|
|
144
195
|
function calculateDailyRate(monthlySalary, workingDays) {
|
|
145
196
|
if (workingDays <= 0) return 0;
|
|
146
|
-
return
|
|
197
|
+
return roundMoney(monthlySalary / workingDays, 2);
|
|
147
198
|
}
|
|
148
199
|
function calculateHourlyRate(monthlySalary, workingDays, hoursPerDay = 8) {
|
|
149
200
|
const dailyRate = calculateDailyRate(monthlySalary, workingDays);
|
|
150
201
|
if (hoursPerDay <= 0) return 0;
|
|
151
|
-
return
|
|
202
|
+
return roundMoney(dailyRate / hoursPerDay, 2);
|
|
152
203
|
}
|
|
153
204
|
function calculatePartialDayDeduction(dailyRate, fractionAbsent) {
|
|
154
205
|
const fraction = Math.min(1, Math.max(0, fractionAbsent));
|
|
155
|
-
return
|
|
206
|
+
return roundMoney(dailyRate * fraction, 2);
|
|
156
207
|
}
|
|
157
208
|
function calculateTotalAttendanceDeduction(input) {
|
|
158
209
|
const { dailyRate, fullDayAbsences = 0, partialDayAbsences = [] } = input;
|
|
159
|
-
const fullDayDeduction =
|
|
210
|
+
const fullDayDeduction = roundMoney(dailyRate * Math.max(0, fullDayAbsences), 2);
|
|
160
211
|
const partialDayDeduction = partialDayAbsences.reduce(
|
|
161
212
|
(sum, fraction) => sum + calculatePartialDayDeduction(dailyRate, fraction),
|
|
162
213
|
0
|
|
@@ -170,7 +221,7 @@ function calculateTotalAttendanceDeduction(input) {
|
|
|
170
221
|
|
|
171
222
|
// src/calculators/salary.calculator.ts
|
|
172
223
|
function calculateSalaryBreakdown(input) {
|
|
173
|
-
const { employee, period, attendance, options = {}, config, taxBrackets } = input;
|
|
224
|
+
const { employee, period, attendance, options = {}, config, taxBrackets, taxOptions, jurisdictionTaxConfig } = input;
|
|
174
225
|
const comp = employee.compensation;
|
|
175
226
|
const originalBaseAmount = comp.baseAmount;
|
|
176
227
|
const proRating = calculateProRatingForSalary(
|
|
@@ -187,8 +238,8 @@ function calculateSalaryBreakdown(input) {
|
|
|
187
238
|
}
|
|
188
239
|
const effectiveAllowances = (comp.allowances || []).filter((a) => isEffectiveForPeriod(a, period.startDate, period.endDate));
|
|
189
240
|
const effectiveDeductions = (comp.deductions || []).filter((d) => isEffectiveForPeriod(d, period.startDate, period.endDate)).filter((d) => d.auto || d.recurring);
|
|
190
|
-
const allowances = processAllowances(effectiveAllowances, originalBaseAmount, proRating, config);
|
|
191
|
-
const deductions = processDeductions(effectiveDeductions, originalBaseAmount, proRating, config);
|
|
241
|
+
const allowances = processAllowances(effectiveAllowances, originalBaseAmount, proRating, config, options.skipProration);
|
|
242
|
+
const deductions = processDeductions(effectiveDeductions, originalBaseAmount, proRating, config, options.skipProration);
|
|
192
243
|
if (!options.skipAttendance && config.attendanceIntegration && attendance) {
|
|
193
244
|
const attendanceDeductionResult = calculateAttendanceDeductionFromData(
|
|
194
245
|
attendance,
|
|
@@ -205,12 +256,24 @@ function calculateSalaryBreakdown(input) {
|
|
|
205
256
|
}
|
|
206
257
|
const grossSalary = calculateGross(baseAmount, allowances);
|
|
207
258
|
const taxableAllowances = allowances.filter((a) => a.taxable);
|
|
208
|
-
|
|
259
|
+
let taxableAmount = baseAmount + sumAllowances(taxableAllowances);
|
|
260
|
+
const preTaxDeductionAmount = calculatePreTaxDeductions(
|
|
261
|
+
effectiveDeductions,
|
|
262
|
+
deductions,
|
|
263
|
+
taxOptions,
|
|
264
|
+
jurisdictionTaxConfig
|
|
265
|
+
);
|
|
266
|
+
taxableAmount = Math.max(0, taxableAmount - preTaxDeductionAmount);
|
|
267
|
+
const frequency = employee.compensation?.frequency || "monthly";
|
|
209
268
|
let taxAmount = 0;
|
|
210
269
|
if (!options.skipTax && taxBrackets.length > 0 && config.autoDeductions) {
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
270
|
+
taxAmount = calculateEnhancedTax(
|
|
271
|
+
taxableAmount,
|
|
272
|
+
taxBrackets,
|
|
273
|
+
taxOptions,
|
|
274
|
+
jurisdictionTaxConfig,
|
|
275
|
+
frequency
|
|
276
|
+
);
|
|
214
277
|
}
|
|
215
278
|
if (taxAmount > 0) {
|
|
216
279
|
deductions.push({
|
|
@@ -246,11 +309,11 @@ function calculateProRatingForSalary(hireDate, terminationDate, periodStart, per
|
|
|
246
309
|
holidays
|
|
247
310
|
});
|
|
248
311
|
}
|
|
249
|
-
function processAllowances(allowances, originalBaseAmount, proRating, config) {
|
|
312
|
+
function processAllowances(allowances, originalBaseAmount, proRating, config, skipProration) {
|
|
250
313
|
return allowances.map((a) => {
|
|
251
314
|
let amount = a.isPercentage && a.value !== void 0 ? percentageOf(originalBaseAmount, a.value) : a.amount;
|
|
252
315
|
const originalAmount = amount;
|
|
253
|
-
if (proRating.isProRated && config.allowProRating) {
|
|
316
|
+
if (proRating.isProRated && config.allowProRating && !skipProration) {
|
|
254
317
|
amount = prorateAmount(amount, proRating.ratio);
|
|
255
318
|
}
|
|
256
319
|
return {
|
|
@@ -263,11 +326,11 @@ function processAllowances(allowances, originalBaseAmount, proRating, config) {
|
|
|
263
326
|
};
|
|
264
327
|
});
|
|
265
328
|
}
|
|
266
|
-
function processDeductions(deductions, originalBaseAmount, proRating, config) {
|
|
329
|
+
function processDeductions(deductions, originalBaseAmount, proRating, config, skipProration) {
|
|
267
330
|
return deductions.map((d) => {
|
|
268
331
|
let amount = d.isPercentage && d.value !== void 0 ? percentageOf(originalBaseAmount, d.value) : d.amount;
|
|
269
332
|
const originalAmount = amount;
|
|
270
|
-
if (proRating.isProRated && config.allowProRating) {
|
|
333
|
+
if (proRating.isProRated && config.allowProRating && !skipProration) {
|
|
271
334
|
amount = prorateAmount(amount, proRating.ratio);
|
|
272
335
|
}
|
|
273
336
|
return {
|
|
@@ -298,6 +361,78 @@ function calculateAttendanceDeductionFromData(attendance, baseAmount, effectiveW
|
|
|
298
361
|
absentDays: result.absentDays
|
|
299
362
|
};
|
|
300
363
|
}
|
|
364
|
+
function calculatePreTaxDeductions(effectiveDeductions, processedDeductions, taxOptions, jurisdictionTaxConfig) {
|
|
365
|
+
let totalPreTax = 0;
|
|
366
|
+
for (let i = 0; i < effectiveDeductions.length; i++) {
|
|
367
|
+
const original = effectiveDeductions[i];
|
|
368
|
+
const processed = processedDeductions[i];
|
|
369
|
+
if (original.reducesTaxableIncome) {
|
|
370
|
+
totalPreTax += processed?.amount || 0;
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
if (jurisdictionTaxConfig?.preTaxDeductionTypes?.length) {
|
|
374
|
+
const preTaxTypes = new Set(jurisdictionTaxConfig.preTaxDeductionTypes);
|
|
375
|
+
for (let i = 0; i < effectiveDeductions.length; i++) {
|
|
376
|
+
const original = effectiveDeductions[i];
|
|
377
|
+
const processed = processedDeductions[i];
|
|
378
|
+
if (original.reducesTaxableIncome) continue;
|
|
379
|
+
if (preTaxTypes.has(original.type)) {
|
|
380
|
+
totalPreTax += processed?.amount || 0;
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
if (taxOptions?.preTaxDeductions?.length) {
|
|
385
|
+
for (const deduction of taxOptions.preTaxDeductions) {
|
|
386
|
+
totalPreTax += deduction.amount;
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
return roundMoney(totalPreTax);
|
|
390
|
+
}
|
|
391
|
+
function calculateEnhancedTax(periodTaxable, taxBrackets, taxOptions, jurisdictionTaxConfig, frequency = "monthly") {
|
|
392
|
+
const periodsPerYear = getPayPeriodsPerYear(frequency);
|
|
393
|
+
let annualTaxable = periodTaxable * periodsPerYear;
|
|
394
|
+
const threshold = getApplicableThreshold(taxOptions, jurisdictionTaxConfig);
|
|
395
|
+
if (threshold > 0) {
|
|
396
|
+
annualTaxable = Math.max(0, annualTaxable - threshold);
|
|
397
|
+
}
|
|
398
|
+
let annualTax = applyTaxBrackets(annualTaxable, taxBrackets);
|
|
399
|
+
if (taxOptions?.taxCredits?.length && annualTax > 0) {
|
|
400
|
+
annualTax = applyTaxCredits(annualTax, taxOptions.taxCredits);
|
|
401
|
+
}
|
|
402
|
+
return roundMoney(annualTax / periodsPerYear);
|
|
403
|
+
}
|
|
404
|
+
function getApplicableThreshold(taxOptions, jurisdictionTaxConfig) {
|
|
405
|
+
if (taxOptions?.standardDeductionOverride !== void 0) {
|
|
406
|
+
return taxOptions.standardDeductionOverride;
|
|
407
|
+
}
|
|
408
|
+
if (taxOptions?.taxpayerCategory) {
|
|
409
|
+
const category = taxOptions.taxpayerCategory;
|
|
410
|
+
if (taxOptions.thresholdOverrides?.[category] !== void 0) {
|
|
411
|
+
return taxOptions.thresholdOverrides[category];
|
|
412
|
+
}
|
|
413
|
+
if (jurisdictionTaxConfig?.thresholdsByCategory?.[category] !== void 0) {
|
|
414
|
+
return jurisdictionTaxConfig.thresholdsByCategory[category];
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
if (taxOptions?.applyStandardDeduction && jurisdictionTaxConfig?.standardDeduction) {
|
|
418
|
+
return jurisdictionTaxConfig.standardDeduction;
|
|
419
|
+
}
|
|
420
|
+
return 0;
|
|
421
|
+
}
|
|
422
|
+
function applyTaxCredits(annualTax, taxCredits) {
|
|
423
|
+
let remainingTax = annualTax;
|
|
424
|
+
for (const credit of taxCredits) {
|
|
425
|
+
if (remainingTax <= 0) break;
|
|
426
|
+
let creditAmount = credit.amount;
|
|
427
|
+
if (credit.maxPercent !== void 0 && credit.maxPercent > 0) {
|
|
428
|
+
const maxCredit = annualTax * credit.maxPercent;
|
|
429
|
+
creditAmount = Math.min(creditAmount, maxCredit);
|
|
430
|
+
}
|
|
431
|
+
creditAmount = Math.min(creditAmount, remainingTax);
|
|
432
|
+
remainingTax -= creditAmount;
|
|
433
|
+
}
|
|
434
|
+
return Math.max(0, remainingTax);
|
|
435
|
+
}
|
|
301
436
|
|
|
302
437
|
export { applyProRating, calculateAttendanceDeduction, calculateDailyRate, calculateHourlyRate, calculatePartialDayDeduction, calculateProRating, calculateSalaryBreakdown, calculateTotalAttendanceDeduction, shouldProRate };
|
|
303
438
|
//# sourceMappingURL=index.js.map
|