@classytic/payroll 1.0.2 → 2.3.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.
Files changed (78) hide show
  1. package/README.md +2599 -574
  2. package/dist/calculators/index.d.ts +433 -0
  3. package/dist/calculators/index.js +283 -0
  4. package/dist/calculators/index.js.map +1 -0
  5. package/dist/core/index.d.ts +314 -0
  6. package/dist/core/index.js +1166 -0
  7. package/dist/core/index.js.map +1 -0
  8. package/dist/employee-identity-DXhgOgXE.d.ts +473 -0
  9. package/dist/employee.factory-BlZqhiCk.d.ts +189 -0
  10. package/dist/idempotency-Cw2CWicb.d.ts +52 -0
  11. package/dist/index.d.ts +902 -0
  12. package/dist/index.js +9108 -0
  13. package/dist/index.js.map +1 -0
  14. package/dist/jurisdiction/index.d.ts +660 -0
  15. package/dist/jurisdiction/index.js +533 -0
  16. package/dist/jurisdiction/index.js.map +1 -0
  17. package/dist/payroll.d.ts +429 -0
  18. package/dist/payroll.js +5192 -0
  19. package/dist/payroll.js.map +1 -0
  20. package/dist/schemas/index.d.ts +3262 -0
  21. package/dist/schemas/index.js +780 -0
  22. package/dist/schemas/index.js.map +1 -0
  23. package/dist/services/index.d.ts +582 -0
  24. package/dist/services/index.js +2172 -0
  25. package/dist/services/index.js.map +1 -0
  26. package/dist/shift-compliance/index.d.ts +1171 -0
  27. package/dist/shift-compliance/index.js +1479 -0
  28. package/dist/shift-compliance/index.js.map +1 -0
  29. package/dist/types-BN3K_Uhr.d.ts +1842 -0
  30. package/dist/utils/index.d.ts +893 -0
  31. package/dist/utils/index.js +1515 -0
  32. package/dist/utils/index.js.map +1 -0
  33. package/package.json +72 -37
  34. package/dist/types/config.d.ts +0 -162
  35. package/dist/types/core/compensation.manager.d.ts +0 -54
  36. package/dist/types/core/employment.manager.d.ts +0 -49
  37. package/dist/types/core/payroll.manager.d.ts +0 -60
  38. package/dist/types/enums.d.ts +0 -117
  39. package/dist/types/factories/compensation.factory.d.ts +0 -196
  40. package/dist/types/factories/employee.factory.d.ts +0 -149
  41. package/dist/types/factories/payroll.factory.d.ts +0 -319
  42. package/dist/types/hrm.orchestrator.d.ts +0 -47
  43. package/dist/types/index.d.ts +0 -20
  44. package/dist/types/init.d.ts +0 -30
  45. package/dist/types/models/payroll-record.model.d.ts +0 -3
  46. package/dist/types/plugins/employee.plugin.d.ts +0 -2
  47. package/dist/types/schemas/employment.schema.d.ts +0 -959
  48. package/dist/types/services/compensation.service.d.ts +0 -94
  49. package/dist/types/services/employee.service.d.ts +0 -28
  50. package/dist/types/services/payroll.service.d.ts +0 -30
  51. package/dist/types/utils/calculation.utils.d.ts +0 -26
  52. package/dist/types/utils/date.utils.d.ts +0 -35
  53. package/dist/types/utils/logger.d.ts +0 -12
  54. package/dist/types/utils/query-builders.d.ts +0 -83
  55. package/dist/types/utils/validation.utils.d.ts +0 -33
  56. package/payroll.d.ts +0 -241
  57. package/src/config.js +0 -177
  58. package/src/core/compensation.manager.js +0 -242
  59. package/src/core/employment.manager.js +0 -224
  60. package/src/core/payroll.manager.js +0 -499
  61. package/src/enums.js +0 -141
  62. package/src/factories/compensation.factory.js +0 -198
  63. package/src/factories/employee.factory.js +0 -173
  64. package/src/factories/payroll.factory.js +0 -413
  65. package/src/hrm.orchestrator.js +0 -139
  66. package/src/index.js +0 -172
  67. package/src/init.js +0 -62
  68. package/src/models/payroll-record.model.js +0 -126
  69. package/src/plugins/employee.plugin.js +0 -164
  70. package/src/schemas/employment.schema.js +0 -126
  71. package/src/services/compensation.service.js +0 -231
  72. package/src/services/employee.service.js +0 -162
  73. package/src/services/payroll.service.js +0 -213
  74. package/src/utils/calculation.utils.js +0 -91
  75. package/src/utils/date.utils.js +0 -120
  76. package/src/utils/logger.js +0 -36
  77. package/src/utils/query-builders.js +0 -185
  78. package/src/utils/validation.utils.js +0 -122
@@ -0,0 +1,1515 @@
1
+ import { Types } from 'mongoose';
2
+
3
+ // src/utils/logger.ts
4
+ var createConsoleLogger = () => ({
5
+ info: (message, meta) => {
6
+ if (meta) {
7
+ console.log(`[Payroll] INFO: ${message}`, meta);
8
+ } else {
9
+ console.log(`[Payroll] INFO: ${message}`);
10
+ }
11
+ },
12
+ error: (message, meta) => {
13
+ if (meta) {
14
+ console.error(`[Payroll] ERROR: ${message}`, meta);
15
+ } else {
16
+ console.error(`[Payroll] ERROR: ${message}`);
17
+ }
18
+ },
19
+ warn: (message, meta) => {
20
+ if (meta) {
21
+ console.warn(`[Payroll] WARN: ${message}`, meta);
22
+ } else {
23
+ console.warn(`[Payroll] WARN: ${message}`);
24
+ }
25
+ },
26
+ debug: (message, meta) => {
27
+ if (process.env.NODE_ENV !== "production") {
28
+ if (meta) {
29
+ console.log(`[Payroll] DEBUG: ${message}`, meta);
30
+ } else {
31
+ console.log(`[Payroll] DEBUG: ${message}`);
32
+ }
33
+ }
34
+ }
35
+ });
36
+ var currentLogger = createConsoleLogger();
37
+ var loggingEnabled = true;
38
+ function getLogger() {
39
+ return currentLogger;
40
+ }
41
+ function setLogger(logger2) {
42
+ currentLogger = logger2;
43
+ }
44
+ function resetLogger() {
45
+ currentLogger = createConsoleLogger();
46
+ }
47
+ function createChildLogger(prefix) {
48
+ const parent = currentLogger;
49
+ return {
50
+ info: (message, meta) => parent.info(`[${prefix}] ${message}`, meta),
51
+ error: (message, meta) => parent.error(`[${prefix}] ${message}`, meta),
52
+ warn: (message, meta) => parent.warn(`[${prefix}] ${message}`, meta),
53
+ debug: (message, meta) => parent.debug(`[${prefix}] ${message}`, meta)
54
+ };
55
+ }
56
+ function createSilentLogger() {
57
+ return {
58
+ info: () => {
59
+ },
60
+ error: () => {
61
+ },
62
+ warn: () => {
63
+ },
64
+ debug: () => {
65
+ }
66
+ };
67
+ }
68
+ function enableLogging() {
69
+ loggingEnabled = true;
70
+ }
71
+ function disableLogging() {
72
+ loggingEnabled = false;
73
+ }
74
+ function isLoggingEnabled() {
75
+ return loggingEnabled;
76
+ }
77
+ var logger = {
78
+ info: (message, meta) => {
79
+ if (loggingEnabled) currentLogger.info(message, meta);
80
+ },
81
+ error: (message, meta) => {
82
+ if (loggingEnabled) currentLogger.error(message, meta);
83
+ },
84
+ warn: (message, meta) => {
85
+ if (loggingEnabled) currentLogger.warn(message, meta);
86
+ },
87
+ debug: (message, meta) => {
88
+ if (loggingEnabled) currentLogger.debug(message, meta);
89
+ }
90
+ };
91
+
92
+ // src/utils/date.ts
93
+ function addDays(date, days) {
94
+ const result = new Date(date);
95
+ result.setDate(result.getDate() + days);
96
+ return result;
97
+ }
98
+ function addMonths(date, months) {
99
+ const result = new Date(date);
100
+ result.setMonth(result.getMonth() + months);
101
+ return result;
102
+ }
103
+ function addYears(date, years) {
104
+ const result = new Date(date);
105
+ result.setFullYear(result.getFullYear() + years);
106
+ return result;
107
+ }
108
+ function subDays(date, days) {
109
+ return addDays(date, -days);
110
+ }
111
+ function subMonths(date, months) {
112
+ return addMonths(date, -months);
113
+ }
114
+ function startOfMonth(date) {
115
+ const result = new Date(date);
116
+ result.setDate(1);
117
+ result.setHours(0, 0, 0, 0);
118
+ return result;
119
+ }
120
+ function endOfMonth(date) {
121
+ const result = new Date(date);
122
+ result.setMonth(result.getMonth() + 1, 0);
123
+ result.setHours(23, 59, 59, 999);
124
+ return result;
125
+ }
126
+ function startOfYear(date) {
127
+ const result = new Date(date);
128
+ result.setMonth(0, 1);
129
+ result.setHours(0, 0, 0, 0);
130
+ return result;
131
+ }
132
+ function endOfYear(date) {
133
+ const result = new Date(date);
134
+ result.setMonth(11, 31);
135
+ result.setHours(23, 59, 59, 999);
136
+ return result;
137
+ }
138
+ function startOfDay(date) {
139
+ const result = new Date(date);
140
+ result.setHours(0, 0, 0, 0);
141
+ return result;
142
+ }
143
+ function endOfDay(date) {
144
+ const result = new Date(date);
145
+ result.setHours(23, 59, 59, 999);
146
+ return result;
147
+ }
148
+ function diffInDays(start, end) {
149
+ return Math.ceil(
150
+ (new Date(end).getTime() - new Date(start).getTime()) / (1e3 * 60 * 60 * 24)
151
+ );
152
+ }
153
+ function diffInMonths(start, end) {
154
+ const startDate = new Date(start);
155
+ const endDate = new Date(end);
156
+ return (endDate.getFullYear() - startDate.getFullYear()) * 12 + (endDate.getMonth() - startDate.getMonth());
157
+ }
158
+ function diffInYears(start, end) {
159
+ return Math.floor(diffInMonths(start, end) / 12);
160
+ }
161
+ var daysBetween = diffInDays;
162
+ var monthsBetween = diffInMonths;
163
+ function isWeekday(date) {
164
+ const day = new Date(date).getDay();
165
+ return day >= 1 && day <= 5;
166
+ }
167
+ function isWeekend(date) {
168
+ const day = new Date(date).getDay();
169
+ return day === 0 || day === 6;
170
+ }
171
+ function getDayOfWeek(date) {
172
+ return new Date(date).getDay();
173
+ }
174
+ function getDayName(date) {
175
+ const days = [
176
+ "Sunday",
177
+ "Monday",
178
+ "Tuesday",
179
+ "Wednesday",
180
+ "Thursday",
181
+ "Friday",
182
+ "Saturday"
183
+ ];
184
+ return days[getDayOfWeek(date)];
185
+ }
186
+ function getPayPeriod(month, year) {
187
+ const startDate = new Date(year, month - 1, 1);
188
+ return {
189
+ month,
190
+ year,
191
+ startDate: startOfMonth(startDate),
192
+ endDate: endOfMonth(startDate)
193
+ };
194
+ }
195
+ function getCurrentPeriod(date = /* @__PURE__ */ new Date()) {
196
+ const d = new Date(date);
197
+ return {
198
+ year: d.getFullYear(),
199
+ month: d.getMonth() + 1
200
+ };
201
+ }
202
+ function getWorkingDaysInMonth(year, month) {
203
+ const start = new Date(year, month - 1, 1);
204
+ const end = endOfMonth(start);
205
+ let count = 0;
206
+ const current = new Date(start);
207
+ while (current <= end) {
208
+ if (isWeekday(current)) {
209
+ count++;
210
+ }
211
+ current.setDate(current.getDate() + 1);
212
+ }
213
+ return count;
214
+ }
215
+ function getDaysInMonth(year, month) {
216
+ return new Date(year, month, 0).getDate();
217
+ }
218
+ function calculateProbationEnd(hireDate, probationMonths) {
219
+ if (!probationMonths || probationMonths <= 0) return null;
220
+ return addMonths(hireDate, probationMonths);
221
+ }
222
+ function isOnProbation(probationEndDate, now = /* @__PURE__ */ new Date()) {
223
+ if (!probationEndDate) return false;
224
+ return now < new Date(probationEndDate);
225
+ }
226
+ function calculateYearsOfService(hireDate, terminationDate) {
227
+ const end = terminationDate || /* @__PURE__ */ new Date();
228
+ const days = diffInDays(hireDate, end);
229
+ return Math.max(0, Math.floor(days / 365.25 * 10) / 10);
230
+ }
231
+ function isDateInRange(date, start, end) {
232
+ const checkDate = new Date(date);
233
+ return checkDate >= new Date(start) && checkDate <= new Date(end);
234
+ }
235
+ function getPayPeriodDateRange(month, year) {
236
+ const period = getPayPeriod(month, year);
237
+ return { start: period.startDate, end: period.endDate };
238
+ }
239
+ function formatDateForDB(date) {
240
+ if (!date) return "";
241
+ return new Date(date).toISOString();
242
+ }
243
+ function parseDBDate(dateString) {
244
+ if (!dateString) return null;
245
+ return new Date(dateString);
246
+ }
247
+ function formatPeriod({ month, year }) {
248
+ return `${String(month).padStart(2, "0")}/${year}`;
249
+ }
250
+ function parsePeriod(periodString) {
251
+ const [month, year] = periodString.split("/").map(Number);
252
+ return { month, year };
253
+ }
254
+ function getMonthName(month) {
255
+ const months = [
256
+ "January",
257
+ "February",
258
+ "March",
259
+ "April",
260
+ "May",
261
+ "June",
262
+ "July",
263
+ "August",
264
+ "September",
265
+ "October",
266
+ "November",
267
+ "December"
268
+ ];
269
+ return months[month - 1] || "";
270
+ }
271
+ function getShortMonthName(month) {
272
+ const months = [
273
+ "Jan",
274
+ "Feb",
275
+ "Mar",
276
+ "Apr",
277
+ "May",
278
+ "Jun",
279
+ "Jul",
280
+ "Aug",
281
+ "Sep",
282
+ "Oct",
283
+ "Nov",
284
+ "Dec"
285
+ ];
286
+ return months[month - 1] || "";
287
+ }
288
+ var date_default = {
289
+ addDays,
290
+ addMonths,
291
+ addYears,
292
+ subDays,
293
+ subMonths,
294
+ startOfMonth,
295
+ endOfMonth,
296
+ startOfYear,
297
+ endOfYear,
298
+ startOfDay,
299
+ endOfDay,
300
+ diffInDays,
301
+ diffInMonths,
302
+ diffInYears,
303
+ daysBetween,
304
+ monthsBetween,
305
+ isWeekday,
306
+ isWeekend,
307
+ getDayOfWeek,
308
+ getDayName,
309
+ getPayPeriod,
310
+ getCurrentPeriod,
311
+ getWorkingDaysInMonth,
312
+ getDaysInMonth,
313
+ calculateProbationEnd,
314
+ isOnProbation,
315
+ calculateYearsOfService,
316
+ isDateInRange,
317
+ getPayPeriodDateRange,
318
+ formatDateForDB,
319
+ parseDBDate,
320
+ formatPeriod,
321
+ parsePeriod,
322
+ getMonthName,
323
+ getShortMonthName
324
+ };
325
+
326
+ // src/utils/calculation.ts
327
+ function sum(numbers) {
328
+ return numbers.reduce((total, n) => total + n, 0);
329
+ }
330
+ function sumBy(items, getter) {
331
+ return items.reduce((total, item) => total + getter(item), 0);
332
+ }
333
+ function sumAllowances(allowances) {
334
+ return sumBy(allowances, (a) => a.amount);
335
+ }
336
+ function sumDeductions(deductions) {
337
+ return sumBy(deductions, (d) => d.amount);
338
+ }
339
+ function applyPercentage(amount, percentage) {
340
+ return Math.round(amount * (percentage / 100));
341
+ }
342
+ function calculatePercentage(part, total) {
343
+ return total > 0 ? Math.round(part / total * 100) : 0;
344
+ }
345
+ function roundTo(value, decimals = 2) {
346
+ const factor = Math.pow(10, decimals);
347
+ return Math.round(value * factor) / factor;
348
+ }
349
+ function calculateGross(baseAmount, allowances) {
350
+ return baseAmount + sumAllowances(allowances);
351
+ }
352
+ function calculateNet(gross, deductions) {
353
+ return Math.max(0, gross - sumDeductions(deductions));
354
+ }
355
+ function calculateTotalCompensation(baseAmount, allowances, deductions) {
356
+ const gross = calculateGross(baseAmount, allowances);
357
+ const totalDeductions = sumDeductions(deductions);
358
+ const net = calculateNet(gross, deductions);
359
+ return { gross, net, deductions: totalDeductions };
360
+ }
361
+ function calculateAllowanceAmount(allowance, baseAmount) {
362
+ if (allowance.isPercentage && allowance.value !== void 0) {
363
+ return applyPercentage(baseAmount, allowance.value);
364
+ }
365
+ return allowance.amount;
366
+ }
367
+ function calculateDeductionAmount(deduction, baseAmount) {
368
+ if (deduction.isPercentage && deduction.value !== void 0) {
369
+ return applyPercentage(baseAmount, deduction.value);
370
+ }
371
+ return deduction.amount;
372
+ }
373
+ function calculateAllowances(allowances, baseAmount) {
374
+ return allowances.map((allowance) => ({
375
+ ...allowance,
376
+ calculatedAmount: calculateAllowanceAmount(allowance, baseAmount)
377
+ }));
378
+ }
379
+ function calculateDeductions(deductions, baseAmount) {
380
+ return deductions.map((deduction) => ({
381
+ ...deduction,
382
+ calculatedAmount: calculateDeductionAmount(deduction, baseAmount)
383
+ }));
384
+ }
385
+ function calculateCompensationBreakdown(compensation) {
386
+ const { baseAmount, allowances = [], deductions = [] } = compensation;
387
+ const calculatedAllowances = calculateAllowances(allowances, baseAmount);
388
+ const calculatedDeductions = calculateDeductions(deductions, baseAmount);
389
+ const grossAmount = baseAmount + sumBy(calculatedAllowances, (a) => a.calculatedAmount);
390
+ const netAmount = grossAmount - sumBy(calculatedDeductions, (d) => d.calculatedAmount);
391
+ return {
392
+ baseAmount,
393
+ allowances: calculatedAllowances,
394
+ deductions: calculatedDeductions,
395
+ grossAmount,
396
+ netAmount: Math.max(0, netAmount)
397
+ };
398
+ }
399
+ function calculateProRating(hireDate, periodStart, periodEnd) {
400
+ const totalDays = diffInDays(periodStart, periodEnd) + 1;
401
+ if (hireDate <= periodStart) {
402
+ return {
403
+ isProRated: false,
404
+ totalDays,
405
+ actualDays: totalDays,
406
+ ratio: 1
407
+ };
408
+ }
409
+ if (hireDate > periodStart && hireDate <= periodEnd) {
410
+ const actualDays = diffInDays(hireDate, periodEnd) + 1;
411
+ const ratio = actualDays / totalDays;
412
+ return {
413
+ isProRated: true,
414
+ totalDays,
415
+ actualDays,
416
+ ratio
417
+ };
418
+ }
419
+ return {
420
+ isProRated: false,
421
+ totalDays,
422
+ actualDays: 0,
423
+ ratio: 0
424
+ };
425
+ }
426
+ function applyProRating(amount, proRating) {
427
+ return Math.round(amount * proRating.ratio);
428
+ }
429
+ function calculateProRatedSalary(baseAmount, hireDate, period) {
430
+ const proRating = calculateProRating(hireDate, period.startDate, period.endDate);
431
+ const amount = applyProRating(baseAmount, proRating);
432
+ return { amount, proRating };
433
+ }
434
+ function applyTaxBrackets(amount, brackets) {
435
+ let tax = 0;
436
+ for (const bracket of brackets) {
437
+ if (amount > bracket.min) {
438
+ const taxableAmount = Math.min(amount, bracket.max) - bracket.min;
439
+ tax += taxableAmount * bracket.rate;
440
+ }
441
+ }
442
+ return Math.round(tax);
443
+ }
444
+ function calculateTax(amount, brackets) {
445
+ const tax = applyTaxBrackets(amount, brackets);
446
+ return {
447
+ gross: amount,
448
+ tax,
449
+ net: amount - tax
450
+ };
451
+ }
452
+ function pipe(...fns) {
453
+ return (value) => fns.reduce((acc, fn) => fn(acc), value);
454
+ }
455
+ function compose(...fns) {
456
+ return (value) => fns.reduceRight((acc, fn) => fn(acc), value);
457
+ }
458
+ function createAllowanceCalculator(allowances) {
459
+ return (baseSalary) => calculateAllowances(allowances, baseSalary);
460
+ }
461
+ function createDeductionCalculator(deductions) {
462
+ return (baseSalary) => calculateDeductions(deductions, baseSalary);
463
+ }
464
+ function calculateOvertime(hourlyRate, overtimeHours, multiplier = 1.5) {
465
+ return Math.round(hourlyRate * overtimeHours * multiplier);
466
+ }
467
+ function calculateHourlyRate(monthlySalary, hoursPerMonth = 176) {
468
+ return Math.round(monthlySalary / hoursPerMonth);
469
+ }
470
+ function calculateDailyRate(monthlySalary, daysPerMonth = 22) {
471
+ return Math.round(monthlySalary / daysPerMonth);
472
+ }
473
+ var calculation_default = {
474
+ sum,
475
+ sumBy,
476
+ sumAllowances,
477
+ sumDeductions,
478
+ applyPercentage,
479
+ calculatePercentage,
480
+ roundTo,
481
+ calculateGross,
482
+ calculateNet,
483
+ calculateTotalCompensation,
484
+ calculateAllowanceAmount,
485
+ calculateDeductionAmount,
486
+ calculateAllowances,
487
+ calculateDeductions,
488
+ calculateCompensationBreakdown,
489
+ calculateProRating,
490
+ applyProRating,
491
+ calculateProRatedSalary,
492
+ applyTaxBrackets,
493
+ calculateTax,
494
+ pipe,
495
+ compose,
496
+ createAllowanceCalculator,
497
+ createDeductionCalculator,
498
+ calculateOvertime,
499
+ calculateHourlyRate,
500
+ calculateDailyRate
501
+ };
502
+
503
+ // src/enums.ts
504
+ var EMPLOYMENT_TYPE = {
505
+ FULL_TIME: "full_time",
506
+ PART_TIME: "part_time",
507
+ CONTRACT: "contract",
508
+ INTERN: "intern",
509
+ CONSULTANT: "consultant"
510
+ };
511
+ var EMPLOYMENT_TYPE_VALUES = Object.values(EMPLOYMENT_TYPE);
512
+ var EMPLOYEE_STATUS = {
513
+ ACTIVE: "active",
514
+ ON_LEAVE: "on_leave",
515
+ SUSPENDED: "suspended",
516
+ TERMINATED: "terminated"
517
+ };
518
+ var EMPLOYEE_STATUS_VALUES = Object.values(EMPLOYEE_STATUS);
519
+
520
+ // src/utils/validation.ts
521
+ function isActive(employee2) {
522
+ return employee2?.status === "active";
523
+ }
524
+ function isOnLeave(employee2) {
525
+ return employee2?.status === "on_leave";
526
+ }
527
+ function isSuspended(employee2) {
528
+ return employee2?.status === "suspended";
529
+ }
530
+ function isTerminated(employee2) {
531
+ return employee2?.status === "terminated";
532
+ }
533
+ function isEmployed(employee2) {
534
+ return isActive(employee2) || isOnLeave(employee2) || isSuspended(employee2);
535
+ }
536
+ function canReceiveSalary(employee2) {
537
+ return (isActive(employee2) || isOnLeave(employee2)) && (employee2.compensation?.baseAmount ?? 0) > 0;
538
+ }
539
+ function canUpdateEmployment(employee2) {
540
+ return !isTerminated(employee2);
541
+ }
542
+ function hasCompensation(employee2) {
543
+ return (employee2.compensation?.baseAmount ?? 0) > 0;
544
+ }
545
+ function isValidCompensation(compensation) {
546
+ return !!(compensation?.baseAmount && compensation.baseAmount > 0 && compensation.frequency && compensation.currency);
547
+ }
548
+ function isValidBankDetails(bankDetails) {
549
+ return !!(bankDetails?.accountNumber && bankDetails.bankName && bankDetails.accountName);
550
+ }
551
+ function isInProbation(employee2, now = /* @__PURE__ */ new Date()) {
552
+ if (!employee2?.probationEndDate) return false;
553
+ return new Date(employee2.probationEndDate) > now;
554
+ }
555
+ function hasCompletedProbation(employee2, now = /* @__PURE__ */ new Date()) {
556
+ if (!employee2?.probationEndDate) return true;
557
+ return new Date(employee2.probationEndDate) <= now;
558
+ }
559
+ function isEligibleForBonus(employee2, requiredMonths = 6) {
560
+ if (!isActive(employee2) || !employee2.hireDate) return false;
561
+ const monthsEmployed = diffInMonths(employee2.hireDate, /* @__PURE__ */ new Date());
562
+ return monthsEmployed >= requiredMonths;
563
+ }
564
+ function isEligibleForPayroll(employee2) {
565
+ const reasons = [];
566
+ if (!isActive(employee2) && !isOnLeave(employee2)) {
567
+ reasons.push("Employee is not in active or on-leave status");
568
+ }
569
+ if (!hasCompensation(employee2)) {
570
+ reasons.push("Employee has no valid compensation");
571
+ }
572
+ return {
573
+ eligible: reasons.length === 0,
574
+ reasons
575
+ };
576
+ }
577
+ function required(fieldName) {
578
+ return (value) => value !== void 0 && value !== null && value !== "" ? true : `${fieldName} is required`;
579
+ }
580
+ function min(minValue2, fieldName) {
581
+ return (value) => value >= minValue2 ? true : `${fieldName} must be at least ${minValue2}`;
582
+ }
583
+ function max(maxValue2, fieldName) {
584
+ return (value) => value <= maxValue2 ? true : `${fieldName} must not exceed ${maxValue2}`;
585
+ }
586
+ function inRange(minValue2, maxValue2, fieldName) {
587
+ return (value) => value >= minValue2 && value <= maxValue2 ? true : `${fieldName} must be between ${minValue2} and ${maxValue2}`;
588
+ }
589
+ function isPositive(fieldName) {
590
+ return (value) => value > 0 ? true : `${fieldName} must be positive`;
591
+ }
592
+ function oneOf(allowedValues, fieldName) {
593
+ return (value) => allowedValues.includes(value) ? true : `${fieldName} must be one of: ${allowedValues.join(", ")}`;
594
+ }
595
+ function isValidStatus(value) {
596
+ return EMPLOYEE_STATUS_VALUES.includes(value);
597
+ }
598
+ function isValidEmploymentType(value) {
599
+ return EMPLOYMENT_TYPE_VALUES.includes(value);
600
+ }
601
+ function composeValidators(...validators) {
602
+ return (value, data) => {
603
+ for (const validator of validators) {
604
+ const result = validator(value, data);
605
+ if (result !== true) return result;
606
+ }
607
+ return true;
608
+ };
609
+ }
610
+ function createValidator(validationFns) {
611
+ return (data) => {
612
+ const errors = [];
613
+ for (const [field, validator] of Object.entries(validationFns)) {
614
+ const result = validator(data[field], data);
615
+ if (result !== true) {
616
+ errors.push(result);
617
+ }
618
+ }
619
+ return {
620
+ valid: errors.length === 0,
621
+ errors
622
+ };
623
+ };
624
+ }
625
+ function hasRequiredFields(obj, fields) {
626
+ const missing = fields.filter(
627
+ (field) => obj[field] === void 0 || obj[field] === null
628
+ );
629
+ return {
630
+ valid: missing.length === 0,
631
+ missing
632
+ };
633
+ }
634
+ var minValue = min;
635
+ var maxValue = max;
636
+ var isInRange = inRange;
637
+ var validation_default = {
638
+ // Status validators
639
+ isActive,
640
+ isOnLeave,
641
+ isSuspended,
642
+ isTerminated,
643
+ isEmployed,
644
+ canReceiveSalary,
645
+ canUpdateEmployment,
646
+ // Compensation validators
647
+ hasCompensation,
648
+ isValidCompensation,
649
+ isValidBankDetails,
650
+ // Probation validators
651
+ isInProbation,
652
+ hasCompletedProbation,
653
+ // Eligibility validators
654
+ isEligibleForBonus,
655
+ isEligibleForPayroll,
656
+ // Field validators
657
+ required,
658
+ min,
659
+ max,
660
+ inRange,
661
+ isPositive,
662
+ oneOf,
663
+ // Enum validators
664
+ isValidStatus,
665
+ isValidEmploymentType,
666
+ // Composite validators
667
+ composeValidators,
668
+ createValidator,
669
+ hasRequiredFields
670
+ };
671
+ function toObjectId(id) {
672
+ if (id instanceof Types.ObjectId) return id;
673
+ return new Types.ObjectId(id);
674
+ }
675
+ function safeToObjectId(id) {
676
+ if (id instanceof Types.ObjectId) return id;
677
+ if (typeof id === "string" && Types.ObjectId.isValid(id)) {
678
+ return new Types.ObjectId(id);
679
+ }
680
+ return null;
681
+ }
682
+ function isValidObjectId(value) {
683
+ if (value instanceof Types.ObjectId) return true;
684
+ if (typeof value === "string") return Types.ObjectId.isValid(value);
685
+ return false;
686
+ }
687
+ var QueryBuilder = class {
688
+ query;
689
+ constructor(initialQuery = {}) {
690
+ this.query = { ...initialQuery };
691
+ }
692
+ /**
693
+ * Add where condition
694
+ */
695
+ where(field, value) {
696
+ this.query[field] = value;
697
+ return this;
698
+ }
699
+ /**
700
+ * Add $in condition
701
+ */
702
+ whereIn(field, values) {
703
+ this.query[field] = { $in: values };
704
+ return this;
705
+ }
706
+ /**
707
+ * Add $nin condition
708
+ */
709
+ whereNotIn(field, values) {
710
+ this.query[field] = { $nin: values };
711
+ return this;
712
+ }
713
+ /**
714
+ * Add $gte condition
715
+ */
716
+ whereGte(field, value) {
717
+ const existing = this.query[field] || {};
718
+ this.query[field] = { ...existing, $gte: value };
719
+ return this;
720
+ }
721
+ /**
722
+ * Add $lte condition
723
+ */
724
+ whereLte(field, value) {
725
+ const existing = this.query[field] || {};
726
+ this.query[field] = { ...existing, $lte: value };
727
+ return this;
728
+ }
729
+ /**
730
+ * Add $gt condition
731
+ */
732
+ whereGt(field, value) {
733
+ const existing = this.query[field] || {};
734
+ this.query[field] = { ...existing, $gt: value };
735
+ return this;
736
+ }
737
+ /**
738
+ * Add $lt condition
739
+ */
740
+ whereLt(field, value) {
741
+ const existing = this.query[field] || {};
742
+ this.query[field] = { ...existing, $lt: value };
743
+ return this;
744
+ }
745
+ /**
746
+ * Add between condition
747
+ */
748
+ whereBetween(field, start, end) {
749
+ this.query[field] = { $gte: start, $lte: end };
750
+ return this;
751
+ }
752
+ /**
753
+ * Add $exists condition
754
+ */
755
+ whereExists(field) {
756
+ this.query[field] = { $exists: true };
757
+ return this;
758
+ }
759
+ /**
760
+ * Add $exists: false condition
761
+ */
762
+ whereNotExists(field) {
763
+ this.query[field] = { $exists: false };
764
+ return this;
765
+ }
766
+ /**
767
+ * Add $ne condition
768
+ */
769
+ whereNot(field, value) {
770
+ this.query[field] = { $ne: value };
771
+ return this;
772
+ }
773
+ /**
774
+ * Add regex condition
775
+ */
776
+ whereRegex(field, pattern, flags = "i") {
777
+ this.query[field] = { $regex: pattern, $options: flags };
778
+ return this;
779
+ }
780
+ /**
781
+ * Merge another query
782
+ */
783
+ merge(otherQuery) {
784
+ this.query = { ...this.query, ...otherQuery };
785
+ return this;
786
+ }
787
+ /**
788
+ * Build and return the query
789
+ */
790
+ build() {
791
+ return { ...this.query };
792
+ }
793
+ };
794
+ var EmployeeQueryBuilder = class extends QueryBuilder {
795
+ /**
796
+ * Filter by organization
797
+ */
798
+ forOrganization(organizationId) {
799
+ return this.where("organizationId", toObjectId(organizationId));
800
+ }
801
+ /**
802
+ * Filter by user
803
+ */
804
+ forUser(userId) {
805
+ return this.where("userId", toObjectId(userId));
806
+ }
807
+ /**
808
+ * Filter by employeeId (human-readable ID)
809
+ */
810
+ forEmployeeId(employeeId) {
811
+ return this.where("employeeId", employeeId);
812
+ }
813
+ /**
814
+ * Filter by email (for guest employees)
815
+ */
816
+ forEmail(email) {
817
+ return this.where("email", email.toLowerCase().trim());
818
+ }
819
+ /**
820
+ * Filter guest employees (no userId)
821
+ */
822
+ guestEmployees() {
823
+ return this.where("userId", null);
824
+ }
825
+ /**
826
+ * Filter user-linked employees (has userId)
827
+ */
828
+ userLinkedEmployees() {
829
+ return this.where("userId", { $ne: null });
830
+ }
831
+ /**
832
+ * Filter by status(es)
833
+ */
834
+ withStatus(...statuses) {
835
+ if (statuses.length === 1) {
836
+ return this.where("status", statuses[0]);
837
+ }
838
+ return this.whereIn("status", statuses);
839
+ }
840
+ /**
841
+ * Filter active employees
842
+ */
843
+ active() {
844
+ return this.withStatus("active");
845
+ }
846
+ /**
847
+ * Filter employed employees (not terminated)
848
+ */
849
+ employed() {
850
+ return this.whereIn("status", ["active", "on_leave", "suspended"]);
851
+ }
852
+ /**
853
+ * Filter terminated employees
854
+ */
855
+ terminated() {
856
+ return this.withStatus("terminated");
857
+ }
858
+ /**
859
+ * Filter by department
860
+ */
861
+ inDepartment(department) {
862
+ return this.where("department", department);
863
+ }
864
+ /**
865
+ * Filter by position
866
+ */
867
+ inPosition(position) {
868
+ return this.where("position", position);
869
+ }
870
+ /**
871
+ * Filter by employment type
872
+ */
873
+ withEmploymentType(type) {
874
+ return this.where("employmentType", type);
875
+ }
876
+ /**
877
+ * Filter by hire date (after)
878
+ */
879
+ hiredAfter(date) {
880
+ return this.whereGte("hireDate", date);
881
+ }
882
+ /**
883
+ * Filter by hire date (before)
884
+ */
885
+ hiredBefore(date) {
886
+ return this.whereLte("hireDate", date);
887
+ }
888
+ /**
889
+ * Filter by minimum salary
890
+ */
891
+ withMinSalary(amount) {
892
+ return this.whereGte("compensation.netSalary", amount);
893
+ }
894
+ /**
895
+ * Filter by maximum salary
896
+ */
897
+ withMaxSalary(amount) {
898
+ return this.whereLte("compensation.netSalary", amount);
899
+ }
900
+ /**
901
+ * Filter by salary range
902
+ */
903
+ withSalaryRange(min2, max2) {
904
+ return this.whereBetween("compensation.netSalary", min2, max2);
905
+ }
906
+ };
907
+ var PayrollQueryBuilder = class extends QueryBuilder {
908
+ /**
909
+ * Filter by organization
910
+ */
911
+ forOrganization(organizationId) {
912
+ return this.where("organizationId", toObjectId(organizationId));
913
+ }
914
+ /**
915
+ * Filter by employee
916
+ *
917
+ * Note: PayrollRecord.employeeId is always ObjectId _id
918
+ * If passing a string business ID, resolve to _id first
919
+ */
920
+ forEmployee(employeeId) {
921
+ return this.where("employeeId", toObjectId(employeeId));
922
+ }
923
+ /**
924
+ * Filter by period
925
+ */
926
+ forPeriod(month, year) {
927
+ if (month !== void 0) {
928
+ this.where("period.month", month);
929
+ }
930
+ if (year !== void 0) {
931
+ this.where("period.year", year);
932
+ }
933
+ return this;
934
+ }
935
+ /**
936
+ * Filter by status(es)
937
+ */
938
+ withStatus(...statuses) {
939
+ if (statuses.length === 1) {
940
+ return this.where("status", statuses[0]);
941
+ }
942
+ return this.whereIn("status", statuses);
943
+ }
944
+ /**
945
+ * Filter paid records
946
+ */
947
+ paid() {
948
+ return this.withStatus("paid");
949
+ }
950
+ /**
951
+ * Filter pending records
952
+ */
953
+ pending() {
954
+ return this.whereIn("status", ["pending", "processing"]);
955
+ }
956
+ /**
957
+ * Filter by date range
958
+ */
959
+ inDateRange(start, end) {
960
+ return this.whereBetween("period.payDate", start, end);
961
+ }
962
+ /**
963
+ * Filter exported records
964
+ */
965
+ exported() {
966
+ return this.where("exported", true);
967
+ }
968
+ /**
969
+ * Filter not exported records
970
+ */
971
+ notExported() {
972
+ return this.where("exported", false);
973
+ }
974
+ };
975
+ function employee() {
976
+ return new EmployeeQueryBuilder();
977
+ }
978
+ function payroll() {
979
+ return new PayrollQueryBuilder();
980
+ }
981
+ function createQueryBuilder(initialQuery) {
982
+ return new QueryBuilder(initialQuery);
983
+ }
984
+ function buildEmployeeQuery(options) {
985
+ const builder = employee().forOrganization(options.organizationId);
986
+ if (options.userId) {
987
+ builder.forUser(options.userId);
988
+ }
989
+ if (options.statuses) {
990
+ builder.withStatus(...options.statuses);
991
+ }
992
+ if (options.department) {
993
+ builder.inDepartment(options.department);
994
+ }
995
+ if (options.employmentType) {
996
+ builder.withEmploymentType(options.employmentType);
997
+ }
998
+ return builder.build();
999
+ }
1000
+ function buildPayrollQuery(options) {
1001
+ const builder = payroll();
1002
+ if (options.organizationId) {
1003
+ builder.forOrganization(options.organizationId);
1004
+ }
1005
+ if (options.employeeId) {
1006
+ builder.forEmployee(options.employeeId);
1007
+ }
1008
+ if (options.month || options.year) {
1009
+ builder.forPeriod(options.month, options.year);
1010
+ }
1011
+ if (options.statuses) {
1012
+ builder.withStatus(...options.statuses);
1013
+ }
1014
+ return builder.build();
1015
+ }
1016
+ function buildAggregationPipeline(...stages) {
1017
+ return stages.filter((stage) => !!stage);
1018
+ }
1019
+ function matchStage(query) {
1020
+ return { $match: query };
1021
+ }
1022
+ function groupStage(groupBy, aggregations) {
1023
+ return {
1024
+ $group: {
1025
+ _id: groupBy,
1026
+ ...aggregations
1027
+ }
1028
+ };
1029
+ }
1030
+ function sortStage(sortBy) {
1031
+ return { $sort: sortBy };
1032
+ }
1033
+ function limitStage(limit) {
1034
+ return { $limit: limit };
1035
+ }
1036
+ function skipStage(skip) {
1037
+ return { $skip: skip };
1038
+ }
1039
+ function projectStage(fields) {
1040
+ return { $project: fields };
1041
+ }
1042
+ function lookupStage(options) {
1043
+ return { $lookup: options };
1044
+ }
1045
+ function unwindStage(path, options = {}) {
1046
+ return { $unwind: { path, ...options } };
1047
+ }
1048
+ var query_builders_default = {
1049
+ toObjectId,
1050
+ safeToObjectId,
1051
+ isValidObjectId,
1052
+ QueryBuilder,
1053
+ EmployeeQueryBuilder,
1054
+ PayrollQueryBuilder,
1055
+ employee,
1056
+ payroll,
1057
+ createQueryBuilder,
1058
+ buildEmployeeQuery,
1059
+ buildPayrollQuery,
1060
+ buildAggregationPipeline,
1061
+ matchStage,
1062
+ groupStage,
1063
+ sortStage,
1064
+ limitStage,
1065
+ skipStage,
1066
+ projectStage,
1067
+ lookupStage,
1068
+ unwindStage
1069
+ };
1070
+
1071
+ // src/utils/leave.ts
1072
+ var DEFAULT_LEAVE_ALLOCATIONS = {
1073
+ annual: 20,
1074
+ sick: 10,
1075
+ unpaid: 0,
1076
+ // Unlimited
1077
+ maternity: 90,
1078
+ paternity: 10,
1079
+ bereavement: 5,
1080
+ compensatory: 0,
1081
+ other: 0
1082
+ };
1083
+ var DEFAULT_CARRY_OVER = {
1084
+ annual: 5,
1085
+ sick: 0,
1086
+ unpaid: 0,
1087
+ maternity: 0,
1088
+ paternity: 0,
1089
+ bereavement: 0,
1090
+ compensatory: 5,
1091
+ other: 0
1092
+ };
1093
+ function calculateLeaveDays(startDate, endDate, options = {}) {
1094
+ const {
1095
+ workingDays = [1, 2, 3, 4, 5],
1096
+ // Mon-Fri by default
1097
+ holidays = [],
1098
+ includeEndDate = true
1099
+ } = options;
1100
+ const holidaySet = new Set(holidays.map((d) => new Date(d).toDateString()));
1101
+ let count = 0;
1102
+ const current = new Date(startDate);
1103
+ current.setHours(0, 0, 0, 0);
1104
+ const end = new Date(endDate);
1105
+ end.setHours(0, 0, 0, 0);
1106
+ if (!includeEndDate) {
1107
+ end.setDate(end.getDate() - 1);
1108
+ }
1109
+ while (current <= end) {
1110
+ const isWorkDay = workingDays.includes(current.getDay());
1111
+ const isHoliday = holidaySet.has(current.toDateString());
1112
+ if (isWorkDay && !isHoliday) {
1113
+ count++;
1114
+ }
1115
+ current.setDate(current.getDate() + 1);
1116
+ }
1117
+ return count;
1118
+ }
1119
+ function hasLeaveBalance(employee2, type, days, year = (/* @__PURE__ */ new Date()).getFullYear()) {
1120
+ if (type === "unpaid") return true;
1121
+ const balance = getLeaveBalance(employee2, type, year);
1122
+ if (!balance) return false;
1123
+ const available = balance.allocated + balance.carriedOver - balance.used - balance.pending;
1124
+ return available >= days;
1125
+ }
1126
+ function getLeaveBalance(employee2, type, year = (/* @__PURE__ */ new Date()).getFullYear()) {
1127
+ return employee2.leaveBalances?.find((b) => b.type === type && b.year === year);
1128
+ }
1129
+ function getLeaveBalances(employee2, year = (/* @__PURE__ */ new Date()).getFullYear()) {
1130
+ return (employee2.leaveBalances || []).filter((b) => b.year === year);
1131
+ }
1132
+ function getAvailableDays(employee2, type, year = (/* @__PURE__ */ new Date()).getFullYear()) {
1133
+ if (type === "unpaid") return Infinity;
1134
+ const balance = getLeaveBalance(employee2, type, year);
1135
+ if (!balance) return 0;
1136
+ return Math.max(
1137
+ 0,
1138
+ balance.allocated + balance.carriedOver - balance.used - balance.pending
1139
+ );
1140
+ }
1141
+ function getLeaveSummary(employee2, year = (/* @__PURE__ */ new Date()).getFullYear()) {
1142
+ const balances = getLeaveBalances(employee2, year);
1143
+ const byType = {};
1144
+ let totalAllocated = 0;
1145
+ let totalUsed = 0;
1146
+ let totalPending = 0;
1147
+ for (const balance of balances) {
1148
+ const available = Math.max(
1149
+ 0,
1150
+ balance.allocated + balance.carriedOver - balance.used - balance.pending
1151
+ );
1152
+ byType[balance.type] = {
1153
+ allocated: balance.allocated + balance.carriedOver,
1154
+ used: balance.used,
1155
+ pending: balance.pending,
1156
+ available
1157
+ };
1158
+ totalAllocated += balance.allocated + balance.carriedOver;
1159
+ totalUsed += balance.used;
1160
+ totalPending += balance.pending;
1161
+ }
1162
+ return {
1163
+ year,
1164
+ balances,
1165
+ totalAllocated,
1166
+ totalUsed,
1167
+ totalPending,
1168
+ totalAvailable: Math.max(0, totalAllocated - totalUsed - totalPending),
1169
+ byType
1170
+ };
1171
+ }
1172
+ function initializeLeaveBalances(hireDate, config = {}, year = (/* @__PURE__ */ new Date()).getFullYear()) {
1173
+ const {
1174
+ defaultAllocations = DEFAULT_LEAVE_ALLOCATIONS,
1175
+ proRateNewHires = true,
1176
+ fiscalYearStartMonth = 1
1177
+ } = config;
1178
+ const fiscalYearStart = new Date(year, fiscalYearStartMonth - 1, 1);
1179
+ const fiscalYearEnd = new Date(year + 1, fiscalYearStartMonth - 1, 0);
1180
+ let prorationRatio = 1;
1181
+ if (proRateNewHires && hireDate > fiscalYearStart) {
1182
+ const totalDays = diffInDays2(fiscalYearStart, fiscalYearEnd);
1183
+ const remainingDays = diffInDays2(hireDate, fiscalYearEnd);
1184
+ prorationRatio = Math.max(0, Math.min(1, remainingDays / totalDays));
1185
+ }
1186
+ const balances = [];
1187
+ for (const [type, allocation] of Object.entries(defaultAllocations)) {
1188
+ if (allocation > 0) {
1189
+ balances.push({
1190
+ type,
1191
+ allocated: Math.round(allocation * prorationRatio),
1192
+ used: 0,
1193
+ pending: 0,
1194
+ carriedOver: 0,
1195
+ year
1196
+ });
1197
+ }
1198
+ }
1199
+ return balances;
1200
+ }
1201
+ function proRateAllocation(fullAllocation, hireDate, fiscalYearStartMonth = 1, year = (/* @__PURE__ */ new Date()).getFullYear()) {
1202
+ const fiscalYearStart = new Date(year, fiscalYearStartMonth - 1, 1);
1203
+ if (hireDate <= fiscalYearStart) {
1204
+ return fullAllocation;
1205
+ }
1206
+ const fiscalYearEnd = new Date(year + 1, fiscalYearStartMonth - 1, 0);
1207
+ const totalDays = diffInDays2(fiscalYearStart, fiscalYearEnd);
1208
+ const remainingDays = Math.max(0, diffInDays2(hireDate, fiscalYearEnd));
1209
+ return Math.round(fullAllocation * remainingDays / totalDays);
1210
+ }
1211
+ function calculateUnpaidLeaveDeduction(baseSalary, unpaidDays, workingDaysInMonth) {
1212
+ if (unpaidDays <= 0 || workingDaysInMonth <= 0) return 0;
1213
+ const dailyRate = baseSalary / workingDaysInMonth;
1214
+ return Math.round(dailyRate * unpaidDays);
1215
+ }
1216
+ function getUnpaidLeaveDays(leaveRequests, status = "approved") {
1217
+ return leaveRequests.filter((r) => r.type === "unpaid" && r.status === status).reduce((sum2, r) => sum2 + r.days, 0);
1218
+ }
1219
+ function calculateCarryOver(balances, maxCarryOver = DEFAULT_CARRY_OVER, newYearAllocations = DEFAULT_LEAVE_ALLOCATIONS) {
1220
+ if (!balances.length) return [];
1221
+ const currentYear = balances[0].year;
1222
+ const newYear = currentYear + 1;
1223
+ return balances.map((balance) => {
1224
+ const available = balance.allocated + balance.carriedOver - balance.used - balance.pending;
1225
+ const maxForType = maxCarryOver[balance.type] ?? 0;
1226
+ const carryOver = maxForType > 0 ? Math.min(Math.max(0, available), maxForType) : 0;
1227
+ return {
1228
+ type: balance.type,
1229
+ allocated: newYearAllocations[balance.type] ?? DEFAULT_LEAVE_ALLOCATIONS[balance.type] ?? 0,
1230
+ used: 0,
1231
+ pending: 0,
1232
+ carriedOver: carryOver,
1233
+ year: newYear
1234
+ };
1235
+ });
1236
+ }
1237
+ function accrueLeaveToBalance(balances, type, amount, year = (/* @__PURE__ */ new Date()).getFullYear()) {
1238
+ const existingIdx = balances.findIndex((b) => b.type === type && b.year === year);
1239
+ if (existingIdx >= 0) {
1240
+ balances[existingIdx].allocated += amount;
1241
+ } else {
1242
+ balances.push({
1243
+ type,
1244
+ allocated: amount,
1245
+ used: 0,
1246
+ pending: 0,
1247
+ carriedOver: 0,
1248
+ year
1249
+ });
1250
+ }
1251
+ return balances;
1252
+ }
1253
+ function diffInDays2(start, end) {
1254
+ const startDate = new Date(start);
1255
+ const endDate = new Date(end);
1256
+ startDate.setHours(0, 0, 0, 0);
1257
+ endDate.setHours(0, 0, 0, 0);
1258
+ return Math.ceil((endDate.getTime() - startDate.getTime()) / (1e3 * 60 * 60 * 24));
1259
+ }
1260
+ var leave_default = {
1261
+ DEFAULT_LEAVE_ALLOCATIONS,
1262
+ DEFAULT_CARRY_OVER,
1263
+ calculateLeaveDays,
1264
+ hasLeaveBalance,
1265
+ getLeaveBalance,
1266
+ getLeaveBalances,
1267
+ getAvailableDays,
1268
+ getLeaveSummary,
1269
+ initializeLeaveBalances,
1270
+ proRateAllocation,
1271
+ calculateUnpaidLeaveDeduction,
1272
+ getUnpaidLeaveDays,
1273
+ calculateCarryOver,
1274
+ accrueLeaveToBalance
1275
+ };
1276
+
1277
+ // src/errors/index.ts
1278
+ var PayrollError = class extends Error {
1279
+ code;
1280
+ status;
1281
+ context;
1282
+ timestamp;
1283
+ /**
1284
+ * Create a PayrollError.
1285
+ *
1286
+ * Supports BOTH constructor styles for backwards compatibility:
1287
+ * - new PayrollError(message, code?, status?, context?)
1288
+ * - new PayrollError(code, status, message, context?)
1289
+ */
1290
+ constructor(messageOrCode, codeOrStatus = "PAYROLL_ERROR", statusOrMessage = 500, context = {}) {
1291
+ const isLegacySignature = typeof messageOrCode === "string" && typeof codeOrStatus === "string";
1292
+ const message = isLegacySignature ? messageOrCode : statusOrMessage;
1293
+ super(message);
1294
+ this.name = this.constructor.name;
1295
+ this.code = isLegacySignature ? codeOrStatus : messageOrCode;
1296
+ this.status = isLegacySignature ? statusOrMessage : codeOrStatus;
1297
+ this.context = context ?? {};
1298
+ this.timestamp = /* @__PURE__ */ new Date();
1299
+ Error.captureStackTrace?.(this, this.constructor);
1300
+ }
1301
+ /**
1302
+ * Convert error to JSON for API responses (ClockIn-compatible shape)
1303
+ */
1304
+ toJSON() {
1305
+ return {
1306
+ error: {
1307
+ type: this.name,
1308
+ code: this.code,
1309
+ message: this.message,
1310
+ status: this.status,
1311
+ context: this.context,
1312
+ timestamp: this.timestamp.toISOString()
1313
+ }
1314
+ };
1315
+ }
1316
+ /**
1317
+ * Check if error is operational (expected) vs programmer error
1318
+ */
1319
+ isOperational() {
1320
+ return true;
1321
+ }
1322
+ };
1323
+ var EmployeeNotFoundError = class extends PayrollError {
1324
+ constructor(employeeId, context) {
1325
+ super(
1326
+ employeeId ? `Employee not found: ${employeeId}` : "Employee not found",
1327
+ "EMPLOYEE_NOT_FOUND",
1328
+ 404,
1329
+ context ?? {}
1330
+ );
1331
+ }
1332
+ };
1333
+
1334
+ // src/utils/employee-lookup.ts
1335
+ async function findEmployeeSecure(model, options) {
1336
+ const {
1337
+ organizationId,
1338
+ _id,
1339
+ employeeId,
1340
+ employeeIdMode = "auto",
1341
+ userId,
1342
+ email,
1343
+ session,
1344
+ populate
1345
+ } = options;
1346
+ const query = {};
1347
+ if (organizationId) {
1348
+ query.organizationId = toObjectId(organizationId);
1349
+ }
1350
+ if (_id) {
1351
+ query._id = toObjectId(_id);
1352
+ } else if (employeeId !== void 0) {
1353
+ const shouldTreatAsObjectId = employeeIdMode === "objectId" || employeeIdMode === "auto" && isValidObjectId(employeeId);
1354
+ const shouldTreatAsBusinessId = employeeIdMode === "businessId" || employeeIdMode === "auto" && !isValidObjectId(employeeId);
1355
+ if (shouldTreatAsObjectId) {
1356
+ query._id = toObjectId(employeeId);
1357
+ } else if (shouldTreatAsBusinessId) {
1358
+ query.employeeId = employeeId;
1359
+ }
1360
+ } else if (userId) {
1361
+ query.userId = toObjectId(userId);
1362
+ } else if (email) {
1363
+ query.email = email;
1364
+ } else {
1365
+ throw new Error(
1366
+ "findEmployeeSecure requires at least one identifier: _id, employeeId, userId, or email"
1367
+ );
1368
+ }
1369
+ let mongooseQuery = model.findOne(query);
1370
+ if (session) {
1371
+ mongooseQuery = mongooseQuery.session(session);
1372
+ }
1373
+ if (populate) {
1374
+ const fields = Array.isArray(populate) ? populate : [populate];
1375
+ for (const field of fields) {
1376
+ mongooseQuery = mongooseQuery.populate(field);
1377
+ }
1378
+ }
1379
+ const employee2 = await mongooseQuery;
1380
+ if (!employee2) {
1381
+ const identifier = _id ? `_id=${_id}` : employeeId ? `employeeId=${employeeId}` : userId ? `userId=${userId}` : `email=${email}`;
1382
+ throw new EmployeeNotFoundError(
1383
+ `Employee not found: ${identifier}${organizationId ? ` in organization ${organizationId}` : ""}`
1384
+ );
1385
+ }
1386
+ return employee2;
1387
+ }
1388
+ async function employeeExistsSecure(model, options) {
1389
+ try {
1390
+ await findEmployeeSecure(model, options);
1391
+ return true;
1392
+ } catch (error) {
1393
+ if (error instanceof EmployeeNotFoundError) {
1394
+ return false;
1395
+ }
1396
+ throw error;
1397
+ }
1398
+ }
1399
+ async function findEmployeesSecure(model, options) {
1400
+ const { organizationId, filter = {}, session, limit, skip, sort } = options;
1401
+ const query = {
1402
+ organizationId: toObjectId(organizationId),
1403
+ ...filter
1404
+ };
1405
+ let mongooseQuery = model.find(query);
1406
+ if (session) {
1407
+ mongooseQuery = mongooseQuery.session(session);
1408
+ }
1409
+ if (limit) {
1410
+ mongooseQuery = mongooseQuery.limit(limit);
1411
+ }
1412
+ if (skip) {
1413
+ mongooseQuery = mongooseQuery.skip(skip);
1414
+ }
1415
+ if (sort) {
1416
+ mongooseQuery = mongooseQuery.sort(sort);
1417
+ }
1418
+ return mongooseQuery;
1419
+ }
1420
+ function requireOrganizationId(organizationId, operation) {
1421
+ if (!organizationId) {
1422
+ throw new Error(
1423
+ `${operation} requires organizationId. In multi-tenant mode, you must explicitly provide organizationId. In single-tenant mode, ensure autoInject is enabled in configuration.`
1424
+ );
1425
+ }
1426
+ }
1427
+
1428
+ // src/utils/org-resolution.ts
1429
+ function resolveOrganizationId(params) {
1430
+ const { explicit, context, container, operation } = params;
1431
+ if (explicit) {
1432
+ return toObjectId(explicit);
1433
+ }
1434
+ if (context?.organizationId) {
1435
+ return toObjectId(context.organizationId);
1436
+ }
1437
+ if (container?.isSingleTenant()) {
1438
+ const singleTenantConfig = container.getSingleTenantConfig();
1439
+ if (singleTenantConfig?.autoInject) {
1440
+ const orgId = container.getOrganizationId();
1441
+ if (orgId) {
1442
+ return toObjectId(orgId);
1443
+ }
1444
+ }
1445
+ }
1446
+ const operationName = operation || "Operation";
1447
+ throw new Error(
1448
+ `${operationName} requires organizationId. Options:
1449
+ 1. Provide it explicitly in parameters
1450
+ 2. Pass it via context (from middleware/auth)
1451
+ 3. Enable single-tenant mode with autoInject: true
1452
+
1453
+ Example (multi-tenant):
1454
+ await payroll.${operation || "method"}({ organizationId: org._id, ... });
1455
+
1456
+ Example (single-tenant):
1457
+ const payroll = createPayrollInstance()
1458
+ .withModels({ ... })
1459
+ .forSingleTenant({ organizationId: myOrg._id, autoInject: true })
1460
+ .build();`
1461
+ );
1462
+ }
1463
+ function validateOrganizationId(organizationId, operation) {
1464
+ if (!organizationId) {
1465
+ throw new Error(
1466
+ `${operation} requires organizationId. Provide it explicitly, via context, or enable single-tenant mode with autoInject.`
1467
+ );
1468
+ }
1469
+ try {
1470
+ return toObjectId(organizationId);
1471
+ } catch (error) {
1472
+ throw new Error(
1473
+ `${operation} received invalid organizationId: ${organizationId}. Must be a valid ObjectId, ObjectId string, or ObjectId-like object.`
1474
+ );
1475
+ }
1476
+ }
1477
+ function tryResolveOrganizationId(params) {
1478
+ try {
1479
+ return resolveOrganizationId(params);
1480
+ } catch {
1481
+ return null;
1482
+ }
1483
+ }
1484
+
1485
+ // src/utils/employee-identity.ts
1486
+ function detectEmployeeIdType(employeeId) {
1487
+ if (isValidObjectId(employeeId)) {
1488
+ return "objectId";
1489
+ }
1490
+ return "string";
1491
+ }
1492
+ function normalizeEmployeeId(employeeId) {
1493
+ const idType = detectEmployeeIdType(employeeId);
1494
+ if (idType === "objectId") {
1495
+ return toObjectId(employeeId);
1496
+ }
1497
+ return employeeId;
1498
+ }
1499
+ function isStringEmployeeId(value) {
1500
+ return typeof value === "string" && !isValidObjectId(value);
1501
+ }
1502
+ function isObjectIdEmployeeId(value) {
1503
+ return isValidObjectId(value);
1504
+ }
1505
+ function formatEmployeeId(employeeId) {
1506
+ const idType = detectEmployeeIdType(employeeId);
1507
+ if (idType === "objectId") {
1508
+ return `_id=${toObjectId(employeeId).toString()}`;
1509
+ }
1510
+ return `employeeId=${employeeId}`;
1511
+ }
1512
+
1513
+ export { DEFAULT_CARRY_OVER, DEFAULT_LEAVE_ALLOCATIONS, EmployeeQueryBuilder, PayrollQueryBuilder, QueryBuilder, accrueLeaveToBalance, addDays, addMonths, addYears, applyPercentage, applyProRating, applyTaxBrackets, buildAggregationPipeline, buildEmployeeQuery, buildPayrollQuery, calculateAllowanceAmount, calculateAllowances, calculateCarryOver, calculateCompensationBreakdown, calculateDailyRate, calculateDeductionAmount, calculateDeductions, calculateGross, calculateHourlyRate, calculateLeaveDays, calculateNet, calculateOvertime, calculatePercentage, calculateProRatedSalary, calculateProRating, calculateProbationEnd, calculateTax, calculateTotalCompensation, calculateUnpaidLeaveDeduction, calculateYearsOfService, calculation_default as calculationUtils, canReceiveSalary, canUpdateEmployment, compose, composeValidators, createAllowanceCalculator, createChildLogger, createDeductionCalculator, createQueryBuilder, createSilentLogger, createValidator, date_default as dateUtils, daysBetween, detectEmployeeIdType, diffInDays, diffInMonths, diffInYears, disableLogging, employee, employeeExistsSecure, enableLogging, endOfDay, endOfMonth, endOfYear, findEmployeeSecure, findEmployeesSecure, formatDateForDB, formatEmployeeId, formatPeriod, getAvailableDays, getCurrentPeriod, getDayName, getDayOfWeek, getDaysInMonth, getLeaveBalance, getLeaveBalances, getLeaveSummary, getLogger, getMonthName, getPayPeriod, getPayPeriodDateRange, getShortMonthName, getUnpaidLeaveDays, getWorkingDaysInMonth, groupStage, hasCompensation, hasCompletedProbation, hasLeaveBalance, hasRequiredFields, inRange, initializeLeaveBalances, isActive, isDateInRange, isEligibleForBonus, isEligibleForPayroll, isEmployed, isInProbation, isInRange, isLoggingEnabled, isObjectIdEmployeeId, isOnLeave, isOnProbation, isPositive, isStringEmployeeId, isSuspended, isTerminated, isValidBankDetails, isValidCompensation, isValidEmploymentType, isValidObjectId, isValidStatus, isWeekday, isWeekend, leave_default as leaveUtils, limitStage, logger, lookupStage, matchStage, max, maxValue, min, minValue, monthsBetween, normalizeEmployeeId, oneOf, parseDBDate, parsePeriod, payroll, pipe, proRateAllocation, projectStage, query_builders_default as queryBuilders, requireOrganizationId, required, resetLogger, resolveOrganizationId, roundTo, safeToObjectId, setLogger, skipStage, sortStage, startOfDay, startOfMonth, startOfYear, subDays, subMonths, sum, sumAllowances, sumBy, sumDeductions, toObjectId, tryResolveOrganizationId, unwindStage, validateOrganizationId, validation_default as validationUtils };
1514
+ //# sourceMappingURL=index.js.map
1515
+ //# sourceMappingURL=index.js.map