@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.

Potentially problematic release.


This version of @classytic/payroll might be problematic. Click here for more details.

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,1479 @@
1
+ import { Schema } from 'mongoose';
2
+
3
+ // src/shift-compliance/calculators/late-penalty.ts
4
+ function calculateFlatPenalty(occurrenceCount, flatAmount) {
5
+ return {
6
+ amount: occurrenceCount * flatAmount,
7
+ occurrences: occurrenceCount
8
+ };
9
+ }
10
+ function calculatePerMinutePenalty(totalMinutesLate, perMinuteRate) {
11
+ return {
12
+ amount: Math.round(totalMinutesLate * perMinuteRate),
13
+ minutes: totalMinutesLate
14
+ };
15
+ }
16
+ function calculatePercentagePenalty(occurrenceCount, percentageRate, dailyWage) {
17
+ const penaltyPerOccurrence = Math.round(dailyWage * percentageRate / 100);
18
+ return {
19
+ amount: occurrenceCount * penaltyPerOccurrence,
20
+ percentage: percentageRate
21
+ };
22
+ }
23
+ function findTier(occurrenceNumber, tiers) {
24
+ for (const tier of tiers) {
25
+ if (tier.to === void 0 && occurrenceNumber >= tier.from) {
26
+ return tier;
27
+ }
28
+ if (tier.to !== void 0 && occurrenceNumber >= tier.from && occurrenceNumber <= tier.to) {
29
+ return tier;
30
+ }
31
+ }
32
+ return null;
33
+ }
34
+ function calculateTieredPenaltyForOccurrence(occurrenceNumber, tiers) {
35
+ const tier = findTier(occurrenceNumber, tiers);
36
+ if (!tier) {
37
+ return { amount: 0, tier: null, warning: false };
38
+ }
39
+ if (tier.warning) {
40
+ return { amount: 0, tier, warning: true };
41
+ }
42
+ return { amount: tier.penalty, tier, warning: false };
43
+ }
44
+ function calculateTieredPenalty(newOccurrences, currentOccurrenceCount, tiers) {
45
+ let totalPenalty = 0;
46
+ const breakdown = [];
47
+ for (let i = 0; i < newOccurrences; i++) {
48
+ const occurrenceNumber = currentOccurrenceCount + i + 1;
49
+ const result = calculateTieredPenaltyForOccurrence(occurrenceNumber, tiers);
50
+ totalPenalty += result.amount;
51
+ breakdown.push({
52
+ occurrence: occurrenceNumber,
53
+ penalty: result.amount,
54
+ tier: result.tier ? tiers.indexOf(result.tier) : void 0,
55
+ warning: result.warning
56
+ });
57
+ }
58
+ return { amount: totalPenalty, breakdown };
59
+ }
60
+ function calculateLatePenalty(input) {
61
+ const {
62
+ policy,
63
+ occurrences = [],
64
+ lateCount = 0,
65
+ totalLateMinutes = 0,
66
+ dailyWage = 0,
67
+ currentOccurrenceCount = 0
68
+ } = input;
69
+ if (!policy.enabled) {
70
+ return {
71
+ amount: 0,
72
+ occurrences: 0,
73
+ breakdown: []
74
+ };
75
+ }
76
+ const penalizableOccurrences = occurrences.filter(
77
+ (occ) => occ.minutesLate > policy.gracePeriod
78
+ );
79
+ const penalizableCount = penalizableOccurrences.length || Math.max(0, lateCount);
80
+ const penalizableMinutes = penalizableOccurrences.reduce((sum, occ) => sum + Math.max(0, occ.minutesLate - policy.gracePeriod), 0) || totalLateMinutes;
81
+ if (penalizableCount === 0) {
82
+ return {
83
+ amount: 0,
84
+ occurrences: 0,
85
+ breakdown: occurrences.map((occ) => ({
86
+ date: occ.date,
87
+ minutesLate: occ.minutesLate,
88
+ penaltyAmount: 0,
89
+ waived: true,
90
+ reason: "Within grace period"
91
+ }))
92
+ };
93
+ }
94
+ let totalPenalty = 0;
95
+ let tierBreakdown = [];
96
+ switch (policy.mode) {
97
+ case "flat":
98
+ if (policy.flatAmount) {
99
+ const result = calculateFlatPenalty(penalizableCount, policy.flatAmount);
100
+ totalPenalty = result.amount;
101
+ }
102
+ break;
103
+ case "per-minute":
104
+ if (policy.perMinuteRate) {
105
+ const result = calculatePerMinutePenalty(penalizableMinutes, policy.perMinuteRate);
106
+ totalPenalty = result.amount;
107
+ }
108
+ break;
109
+ case "percentage":
110
+ if (policy.percentageRate && dailyWage > 0) {
111
+ const result = calculatePercentagePenalty(penalizableCount, policy.percentageRate, dailyWage);
112
+ totalPenalty = result.amount;
113
+ }
114
+ break;
115
+ case "tiered":
116
+ if (policy.tiers && policy.tiers.length > 0) {
117
+ const result = calculateTieredPenalty(penalizableCount, currentOccurrenceCount, policy.tiers);
118
+ totalPenalty = result.amount;
119
+ tierBreakdown = result.breakdown;
120
+ }
121
+ break;
122
+ }
123
+ if (policy.maxPenaltyAmount) {
124
+ totalPenalty = Math.min(totalPenalty, policy.maxPenaltyAmount.amount);
125
+ }
126
+ const breakdown = penalizableOccurrences.map((occ, index) => {
127
+ const tierInfo = tierBreakdown[index];
128
+ const penaltyPerOccurrence = policy.mode === "tiered" && tierInfo ? tierInfo.penalty : totalPenalty / penalizableCount;
129
+ return {
130
+ date: occ.date,
131
+ minutesLate: occ.minutesLate,
132
+ penaltyAmount: Math.round(penaltyPerOccurrence),
133
+ tier: tierInfo?.tier,
134
+ waived: false
135
+ };
136
+ });
137
+ return {
138
+ amount: Math.round(totalPenalty),
139
+ occurrences: penalizableCount,
140
+ breakdown
141
+ };
142
+ }
143
+
144
+ // src/shift-compliance/calculators/overtime.ts
145
+ function calculateDailyOvertime(hoursWorked, threshold, multiplier, hourlyRate) {
146
+ const overtimeHours = Math.max(0, hoursWorked - threshold);
147
+ if (overtimeHours === 0) {
148
+ return { amount: 0, overtimeHours: 0 };
149
+ }
150
+ const extraMultiplier = multiplier - 1;
151
+ const bonus = Math.round(overtimeHours * hourlyRate * extraMultiplier);
152
+ return { amount: bonus, overtimeHours };
153
+ }
154
+ function calculateWeeklyOvertime(hoursWorked, threshold, multiplier, hourlyRate) {
155
+ return calculateDailyOvertime(hoursWorked, threshold, multiplier, hourlyRate);
156
+ }
157
+ function calculateMonthlyOvertime(hoursWorked, threshold, multiplier, hourlyRate) {
158
+ return calculateDailyOvertime(hoursWorked, threshold, multiplier, hourlyRate);
159
+ }
160
+ function calculateWeekendPremium(hours, multiplier, hourlyRate, day) {
161
+ const extraMultiplier = multiplier - 1;
162
+ const bonus = Math.round(hours * hourlyRate * extraMultiplier);
163
+ return { amount: bonus, hours, day };
164
+ }
165
+ function calculateNightShiftDifferential(hours, multiplier, hourlyRate) {
166
+ const extraMultiplier = multiplier - 1;
167
+ const bonus = Math.round(hours * hourlyRate * extraMultiplier);
168
+ return { amount: bonus, hours };
169
+ }
170
+ function calculateOvertimeBonus(input) {
171
+ const {
172
+ policy,
173
+ occurrences = [],
174
+ overtimeHours = 0,
175
+ overtimeDays = 0,
176
+ hourlyRate
177
+ } = input;
178
+ if (!policy.enabled) {
179
+ return {
180
+ amount: 0,
181
+ hours: 0,
182
+ breakdown: []
183
+ };
184
+ }
185
+ let totalBonus = 0;
186
+ let totalHours = 0;
187
+ const breakdown = [];
188
+ if (occurrences.length > 0) {
189
+ for (const occ of occurrences) {
190
+ let bonus = 0;
191
+ const extraMultiplier = occ.multiplier - 1;
192
+ switch (occ.type) {
193
+ case "daily":
194
+ case "weekly":
195
+ case "monthly":
196
+ bonus = Math.round(occ.hours * hourlyRate * extraMultiplier);
197
+ break;
198
+ case "weekend-saturday":
199
+ case "weekend-sunday":
200
+ bonus = Math.round(occ.hours * hourlyRate * extraMultiplier);
201
+ break;
202
+ case "night-shift":
203
+ bonus = Math.round(occ.hours * hourlyRate * extraMultiplier);
204
+ break;
205
+ }
206
+ totalBonus += bonus;
207
+ totalHours += occ.hours;
208
+ breakdown.push({
209
+ date: occ.date,
210
+ type: occ.type,
211
+ hours: occ.hours,
212
+ rate: hourlyRate,
213
+ multiplier: occ.multiplier,
214
+ amount: bonus
215
+ });
216
+ }
217
+ } else {
218
+ const hours = overtimeHours || 0;
219
+ const days = overtimeDays || 0;
220
+ if (policy.mode === "daily" && policy.dailyThreshold && policy.dailyMultiplier) {
221
+ const hoursFromDays = days * policy.dailyThreshold;
222
+ const result = calculateDailyOvertime(
223
+ hoursFromDays + hours,
224
+ policy.dailyThreshold,
225
+ policy.dailyMultiplier,
226
+ hourlyRate
227
+ );
228
+ totalBonus = result.amount;
229
+ totalHours = result.overtimeHours;
230
+ } else if (policy.mode === "weekly" && policy.weeklyThreshold && policy.weeklyMultiplier) {
231
+ const result = calculateWeeklyOvertime(
232
+ hours,
233
+ policy.weeklyThreshold,
234
+ policy.weeklyMultiplier,
235
+ hourlyRate
236
+ );
237
+ totalBonus = result.amount;
238
+ totalHours = result.overtimeHours;
239
+ } else if (policy.mode === "monthly" && policy.monthlyThreshold && policy.monthlyMultiplier) {
240
+ const result = calculateMonthlyOvertime(
241
+ hours,
242
+ policy.monthlyThreshold,
243
+ policy.monthlyMultiplier,
244
+ hourlyRate
245
+ );
246
+ totalBonus = result.amount;
247
+ totalHours = result.overtimeHours;
248
+ }
249
+ if (totalBonus > 0) {
250
+ breakdown.push({
251
+ date: /* @__PURE__ */ new Date(),
252
+ type: policy.mode,
253
+ hours: totalHours,
254
+ rate: hourlyRate,
255
+ multiplier: policy.dailyMultiplier || policy.weeklyMultiplier || policy.monthlyMultiplier || 1.5,
256
+ amount: totalBonus
257
+ });
258
+ }
259
+ }
260
+ return {
261
+ amount: Math.round(totalBonus),
262
+ hours: totalHours,
263
+ breakdown
264
+ };
265
+ }
266
+
267
+ // src/shift-compliance/calculators/index.ts
268
+ function calculateShiftCompliance(input) {
269
+ const {
270
+ attendance,
271
+ policy,
272
+ dailyWage,
273
+ hourlyRate,
274
+ currentOccurrenceCount = 0
275
+ } = input;
276
+ const latePenalty = calculateLatePenalty({
277
+ policy: policy.lateArrival,
278
+ occurrences: attendance.lateOccurrences,
279
+ lateCount: attendance.lateArrivals,
280
+ totalLateMinutes: attendance.totalLateMinutes,
281
+ dailyWage,
282
+ currentOccurrenceCount
283
+ });
284
+ const earlyDeparturePenalty = calculateLatePenalty({
285
+ policy: policy.earlyDeparture,
286
+ occurrences: attendance.earlyOccurrences?.map((occ) => ({
287
+ date: occ.date,
288
+ scheduledTime: occ.scheduledTime,
289
+ actualTime: occ.actualTime,
290
+ minutesLate: occ.minutesEarly
291
+ // Reuse "late" logic with "early" minutes
292
+ })),
293
+ lateCount: attendance.earlyDepartures,
294
+ totalLateMinutes: attendance.totalEarlyMinutes,
295
+ dailyWage,
296
+ currentOccurrenceCount
297
+ });
298
+ const overtimeBonus = calculateOvertimeBonus({
299
+ policy: policy.overtime,
300
+ occurrences: attendance.overtimeOccurrences,
301
+ overtimeHours: attendance.overtimeHours,
302
+ overtimeDays: attendance.overtimeDays,
303
+ hourlyRate
304
+ });
305
+ const totalPenalties = latePenalty.amount + earlyDeparturePenalty.amount;
306
+ const totalBonuses = overtimeBonus.amount;
307
+ const netAdjustment = totalBonuses - totalPenalties;
308
+ const totalOccurrences = latePenalty.occurrences + earlyDeparturePenalty.occurrences;
309
+ const complianceScore = calculateComplianceScore(totalOccurrences);
310
+ const isAtRisk = determineRiskStatus(totalOccurrences, policy);
311
+ return {
312
+ latePenalty,
313
+ earlyDeparturePenalty,
314
+ overtimeBonus,
315
+ totalPenalties,
316
+ totalBonuses,
317
+ netAdjustment,
318
+ complianceScore,
319
+ occurrenceCount: totalOccurrences,
320
+ isAtRisk,
321
+ policyId: policy.id,
322
+ policyName: policy.name
323
+ };
324
+ }
325
+ function calculateComplianceScore(totalOccurrences, attendance) {
326
+ if (totalOccurrences === 0) {
327
+ return 100;
328
+ }
329
+ return Math.max(0, 100 - totalOccurrences * 10);
330
+ }
331
+ function determineRiskStatus(totalOccurrences, policy) {
332
+ if (policy.lateArrival.mode === "tiered" && policy.lateArrival.tiers) {
333
+ const lastTier = policy.lateArrival.tiers[policy.lateArrival.tiers.length - 1];
334
+ if (lastTier && lastTier.from && totalOccurrences >= lastTier.from) {
335
+ return true;
336
+ }
337
+ }
338
+ return totalOccurrences >= 7;
339
+ }
340
+
341
+ // src/shift-compliance/config.ts
342
+ var DEFAULT_ATTENDANCE_POLICY = {
343
+ lateArrival: {
344
+ enabled: true,
345
+ gracePeriod: 10,
346
+ // 10 minutes grace
347
+ mode: "tiered",
348
+ tiers: [
349
+ { from: 1, to: 2, penalty: 0, warning: true },
350
+ // 1st-2nd: warning
351
+ { from: 3, to: 4, penalty: 25 },
352
+ // 3rd-4th: $25
353
+ { from: 5, penalty: 50 }
354
+ // 5th+: $50
355
+ ],
356
+ maxPenaltiesPerPeriod: {
357
+ count: 5,
358
+ period: "monthly"
359
+ },
360
+ resetOccurrenceCount: "quarterly"
361
+ },
362
+ earlyDeparture: {
363
+ enabled: true,
364
+ gracePeriod: 10,
365
+ mode: "tiered",
366
+ tiers: [
367
+ { from: 1, to: 2, penalty: 0, warning: true },
368
+ { from: 3, to: 4, penalty: 30 },
369
+ { from: 5, penalty: 60 }
370
+ ],
371
+ maxPenaltiesPerPeriod: {
372
+ count: 5,
373
+ period: "monthly"
374
+ },
375
+ resetOccurrenceCount: "quarterly"
376
+ },
377
+ overtime: {
378
+ enabled: true,
379
+ mode: "daily",
380
+ dailyThreshold: 8,
381
+ dailyMultiplier: 1.5,
382
+ weeklyThreshold: 40,
383
+ weeklyMultiplier: 1.5
384
+ },
385
+ clockRounding: {
386
+ enabled: false,
387
+ roundTo: 15,
388
+ mode: "nearest"
389
+ }
390
+ };
391
+ var MANUFACTURING_POLICY = {
392
+ lateArrival: {
393
+ enabled: true,
394
+ gracePeriod: 0,
395
+ // No grace period
396
+ mode: "flat",
397
+ flatAmount: 100,
398
+ // $100 per occurrence
399
+ maxPenaltiesPerPeriod: {
400
+ count: 5,
401
+ period: "monthly"
402
+ },
403
+ resetOccurrenceCount: "quarterly"
404
+ },
405
+ earlyDeparture: {
406
+ enabled: true,
407
+ gracePeriod: 0,
408
+ mode: "flat",
409
+ flatAmount: 150,
410
+ // Higher penalty for early departure
411
+ maxPenaltiesPerPeriod: {
412
+ count: 5,
413
+ period: "monthly"
414
+ },
415
+ resetOccurrenceCount: "quarterly"
416
+ },
417
+ overtime: {
418
+ enabled: true,
419
+ mode: "daily",
420
+ dailyThreshold: 8,
421
+ dailyMultiplier: 1.5,
422
+ weeklyThreshold: 40,
423
+ weeklyMultiplier: 2
424
+ // Double time for weekly OT
425
+ },
426
+ clockRounding: {
427
+ enabled: true,
428
+ roundTo: 15,
429
+ mode: "down"
430
+ // Round down (favor employer)
431
+ }
432
+ };
433
+ var RETAIL_POLICY = {
434
+ lateArrival: {
435
+ enabled: true,
436
+ gracePeriod: 5,
437
+ mode: "flat",
438
+ flatAmount: 25,
439
+ maxPenaltiesPerPeriod: {
440
+ count: 5,
441
+ period: "monthly"
442
+ },
443
+ resetOccurrenceCount: "monthly"
444
+ },
445
+ earlyDeparture: {
446
+ enabled: true,
447
+ gracePeriod: 5,
448
+ mode: "flat",
449
+ flatAmount: 25,
450
+ maxPenaltiesPerPeriod: {
451
+ count: 5,
452
+ period: "monthly"
453
+ },
454
+ resetOccurrenceCount: "monthly"
455
+ },
456
+ overtime: {
457
+ enabled: true,
458
+ mode: "weekly",
459
+ weeklyThreshold: 40,
460
+ weeklyMultiplier: 1.5,
461
+ weekendPremium: {
462
+ saturday: 1.5,
463
+ // Time and half on Saturday
464
+ sunday: 2
465
+ // Double time on Sunday
466
+ }
467
+ },
468
+ clockRounding: {
469
+ enabled: true,
470
+ roundTo: 5,
471
+ mode: "nearest"
472
+ }
473
+ };
474
+ var OFFICE_POLICY = {
475
+ lateArrival: {
476
+ enabled: true,
477
+ gracePeriod: 15,
478
+ // 15 minutes grace
479
+ mode: "tiered",
480
+ tiers: [
481
+ { from: 1, to: 3, penalty: 0, warning: true },
482
+ // 1st-3rd: warning only
483
+ { from: 4, to: 5, penalty: 20 },
484
+ // 4th-5th: $20
485
+ { from: 6, penalty: 40 }
486
+ // 6th+: $40
487
+ ],
488
+ maxPenaltiesPerPeriod: {
489
+ count: 3,
490
+ period: "monthly"
491
+ },
492
+ resetOccurrenceCount: "quarterly"
493
+ },
494
+ earlyDeparture: {
495
+ enabled: false,
496
+ // Often not tracked in office environments
497
+ gracePeriod: 30,
498
+ mode: "flat",
499
+ flatAmount: 0
500
+ },
501
+ overtime: {
502
+ enabled: true,
503
+ mode: "weekly",
504
+ weeklyThreshold: 40,
505
+ weeklyMultiplier: 1.5
506
+ },
507
+ clockRounding: {
508
+ enabled: false,
509
+ // No rounding for office workers
510
+ roundTo: 15,
511
+ mode: "nearest"
512
+ }
513
+ };
514
+ var HEALTHCARE_POLICY = {
515
+ lateArrival: {
516
+ enabled: true,
517
+ gracePeriod: 5,
518
+ mode: "flat",
519
+ flatAmount: 50,
520
+ maxPenaltiesPerPeriod: {
521
+ count: 3,
522
+ period: "monthly"
523
+ },
524
+ resetOccurrenceCount: "quarterly"
525
+ },
526
+ earlyDeparture: {
527
+ enabled: true,
528
+ gracePeriod: 5,
529
+ mode: "flat",
530
+ flatAmount: 75,
531
+ // Higher penalty due to patient care
532
+ maxPenaltiesPerPeriod: {
533
+ count: 3,
534
+ period: "monthly"
535
+ },
536
+ resetOccurrenceCount: "quarterly"
537
+ },
538
+ overtime: {
539
+ enabled: true,
540
+ mode: "daily",
541
+ dailyThreshold: 8,
542
+ dailyMultiplier: 1.5,
543
+ weekendPremium: {
544
+ saturday: 1.5,
545
+ sunday: 2
546
+ },
547
+ nightShiftDifferential: {
548
+ startHour: 22,
549
+ // 10pm
550
+ endHour: 6,
551
+ // 6am
552
+ multiplier: 1.3
553
+ // 30% premium
554
+ }
555
+ },
556
+ clockRounding: {
557
+ enabled: true,
558
+ roundTo: 15,
559
+ mode: "nearest"
560
+ }
561
+ };
562
+ var HOSPITALITY_POLICY = {
563
+ lateArrival: {
564
+ enabled: true,
565
+ gracePeriod: 5,
566
+ mode: "percentage",
567
+ percentageRate: 1,
568
+ // 1% of daily wage per occurrence
569
+ maxPenaltiesPerPeriod: {
570
+ count: 5,
571
+ period: "monthly"
572
+ },
573
+ resetOccurrenceCount: "monthly"
574
+ },
575
+ earlyDeparture: {
576
+ enabled: true,
577
+ gracePeriod: 5,
578
+ mode: "percentage",
579
+ percentageRate: 1.5,
580
+ // 1.5% of daily wage
581
+ maxPenaltiesPerPeriod: {
582
+ count: 5,
583
+ period: "monthly"
584
+ },
585
+ resetOccurrenceCount: "monthly"
586
+ },
587
+ overtime: {
588
+ enabled: true,
589
+ mode: "weekly",
590
+ weeklyThreshold: 40,
591
+ weeklyMultiplier: 1.5,
592
+ weekendPremium: {
593
+ saturday: 1.25,
594
+ sunday: 1.5
595
+ },
596
+ nightShiftDifferential: {
597
+ startHour: 22,
598
+ endHour: 6,
599
+ multiplier: 1.2
600
+ }
601
+ },
602
+ clockRounding: {
603
+ enabled: true,
604
+ roundTo: 10,
605
+ mode: "nearest"
606
+ }
607
+ };
608
+ function createPolicyFromPreset(preset, overrides) {
609
+ let basePolicy;
610
+ switch (preset) {
611
+ case "manufacturing":
612
+ basePolicy = MANUFACTURING_POLICY;
613
+ break;
614
+ case "retail":
615
+ basePolicy = RETAIL_POLICY;
616
+ break;
617
+ case "office":
618
+ basePolicy = OFFICE_POLICY;
619
+ break;
620
+ case "healthcare":
621
+ basePolicy = HEALTHCARE_POLICY;
622
+ break;
623
+ case "hospitality":
624
+ basePolicy = HOSPITALITY_POLICY;
625
+ break;
626
+ default:
627
+ basePolicy = DEFAULT_ATTENDANCE_POLICY;
628
+ }
629
+ return {
630
+ ...basePolicy,
631
+ name: overrides?.name || `${preset.charAt(0).toUpperCase() + preset.slice(1)} Policy`,
632
+ effectiveFrom: overrides?.effectiveFrom || /* @__PURE__ */ new Date(),
633
+ active: overrides?.active ?? true,
634
+ ...overrides
635
+ };
636
+ }
637
+
638
+ // src/shift-compliance/builders.ts
639
+ var LatePolicyBuilder = class {
640
+ policy = {
641
+ enabled: true,
642
+ gracePeriod: 0
643
+ };
644
+ parent;
645
+ constructor(parent) {
646
+ this.parent = parent;
647
+ }
648
+ /**
649
+ * Enable this policy
650
+ */
651
+ enable() {
652
+ this.policy.enabled = true;
653
+ return this;
654
+ }
655
+ /**
656
+ * Disable this policy
657
+ */
658
+ disable() {
659
+ this.policy.enabled = false;
660
+ return this;
661
+ }
662
+ /**
663
+ * Set grace period in minutes (how many minutes late before penalty applies)
664
+ */
665
+ gracePeriod(minutes) {
666
+ this.policy.gracePeriod = minutes;
667
+ return this;
668
+ }
669
+ /**
670
+ * Use flat penalty mode (same penalty for each occurrence)
671
+ *
672
+ * @param amount - Penalty amount per occurrence
673
+ */
674
+ flatPenalty(amount) {
675
+ this.policy.mode = "flat";
676
+ this.policy.flatAmount = amount;
677
+ delete this.policy.perMinuteRate;
678
+ delete this.policy.percentageRate;
679
+ delete this.policy.tiers;
680
+ return this;
681
+ }
682
+ /**
683
+ * Use per-minute penalty mode (penalty based on minutes late)
684
+ *
685
+ * @param rate - Penalty per minute late
686
+ */
687
+ perMinutePenalty(rate) {
688
+ this.policy.mode = "per-minute";
689
+ this.policy.perMinuteRate = rate;
690
+ delete this.policy.flatAmount;
691
+ delete this.policy.percentageRate;
692
+ delete this.policy.tiers;
693
+ return this;
694
+ }
695
+ /**
696
+ * Use percentage penalty mode (percentage of daily wage)
697
+ *
698
+ * @param percentage - Percentage of daily wage (e.g., 2 for 2%)
699
+ */
700
+ percentagePenalty(percentage) {
701
+ this.policy.mode = "percentage";
702
+ this.policy.percentageRate = percentage;
703
+ delete this.policy.flatAmount;
704
+ delete this.policy.perMinuteRate;
705
+ delete this.policy.tiers;
706
+ return this;
707
+ }
708
+ /**
709
+ * Start building tiered penalty (progressive discipline)
710
+ *
711
+ * @example
712
+ * ```typescript
713
+ * .tieredPenalty()
714
+ * .tier(1, 2).warning()
715
+ * .tier(3, 4).penalty(25)
716
+ * .tier(5).penalty(50)
717
+ * .end()
718
+ * ```
719
+ */
720
+ tieredPenalty() {
721
+ this.policy.mode = "tiered";
722
+ this.policy.tiers = [];
723
+ delete this.policy.flatAmount;
724
+ delete this.policy.perMinuteRate;
725
+ delete this.policy.percentageRate;
726
+ return new TieredPenaltyBuilder(this);
727
+ }
728
+ /**
729
+ * Set maximum penalties per period (caps total penalties)
730
+ *
731
+ * @param count - Max number of penalties
732
+ * @param period - Period type ('daily' | 'weekly' | 'monthly' | 'quarterly' | 'yearly')
733
+ */
734
+ maxPenalties(count, period) {
735
+ this.policy.maxPenaltiesPerPeriod = { count, period };
736
+ return this;
737
+ }
738
+ /**
739
+ * Set when to reset occurrence counter
740
+ *
741
+ * @param period - Reset period ('monthly' | 'quarterly' | 'yearly')
742
+ */
743
+ resetOccurrences(period) {
744
+ this.policy.resetOccurrenceCount = period;
745
+ return this;
746
+ }
747
+ /**
748
+ * Add a custom tier (advanced usage)
749
+ */
750
+ addTier(tier) {
751
+ if (!this.policy.tiers) {
752
+ this.policy.tiers = [];
753
+ }
754
+ this.policy.tiers.push(tier);
755
+ return this;
756
+ }
757
+ /**
758
+ * Finish building this policy and return to parent builder
759
+ */
760
+ end() {
761
+ if (!this.parent) {
762
+ throw new Error("Cannot call end() without parent builder");
763
+ }
764
+ return this.parent;
765
+ }
766
+ /**
767
+ * Build the policy (for standalone usage)
768
+ */
769
+ build() {
770
+ if (this.policy.mode === void 0) {
771
+ throw new Error("Penalty mode is required. Use flatPenalty(), perMinutePenalty(), percentagePenalty(), or tieredPenalty()");
772
+ }
773
+ if (this.policy.mode === "flat" && this.policy.flatAmount === void 0) {
774
+ throw new Error("flatAmount is required for flat penalty mode");
775
+ }
776
+ if (this.policy.mode === "per-minute" && this.policy.perMinuteRate === void 0) {
777
+ throw new Error("perMinuteRate is required for per-minute penalty mode");
778
+ }
779
+ if (this.policy.mode === "percentage" && this.policy.percentageRate === void 0) {
780
+ throw new Error("percentageRate is required for percentage penalty mode");
781
+ }
782
+ if (this.policy.mode === "tiered" && (!this.policy.tiers || this.policy.tiers.length === 0)) {
783
+ throw new Error("At least one tier is required for tiered penalty mode");
784
+ }
785
+ return this.policy;
786
+ }
787
+ /** @internal */
788
+ _getPolicy() {
789
+ return this.policy;
790
+ }
791
+ };
792
+ var TieredPenaltyBuilder = class {
793
+ tiers = [];
794
+ parent;
795
+ currentTier = {};
796
+ constructor(parent) {
797
+ this.parent = parent;
798
+ }
799
+ /**
800
+ * Start a new tier
801
+ *
802
+ * @param from - Starting occurrence number (1-indexed)
803
+ * @param to - Ending occurrence number (optional, omit for open-ended tier)
804
+ */
805
+ tier(from, to) {
806
+ if (Object.keys(this.currentTier).length > 0) {
807
+ this.saveTier();
808
+ }
809
+ this.currentTier = { from, to };
810
+ return this;
811
+ }
812
+ /**
813
+ * Set this tier as warning only (no financial penalty)
814
+ */
815
+ warning() {
816
+ this.currentTier.penalty = 0;
817
+ this.currentTier.warning = true;
818
+ return this;
819
+ }
820
+ /**
821
+ * Set penalty amount for this tier
822
+ */
823
+ penalty(amount) {
824
+ this.currentTier.penalty = amount;
825
+ this.currentTier.warning = false;
826
+ return this;
827
+ }
828
+ /**
829
+ * Finish building tiers and return to parent
830
+ */
831
+ end() {
832
+ if (Object.keys(this.currentTier).length > 0) {
833
+ this.saveTier();
834
+ }
835
+ if (this.parent instanceof LatePolicyBuilder) {
836
+ this.parent.policy.tiers = this.tiers;
837
+ }
838
+ return this.parent;
839
+ }
840
+ saveTier() {
841
+ if (this.currentTier.from === void 0) {
842
+ throw new Error('Tier must have a "from" value. Use .tier(from, to?)');
843
+ }
844
+ if (this.currentTier.penalty === void 0) {
845
+ throw new Error("Tier must have a penalty or be marked as warning. Use .penalty(amount) or .warning()");
846
+ }
847
+ this.tiers.push(this.currentTier);
848
+ this.currentTier = {};
849
+ }
850
+ };
851
+ var OvertimePolicyBuilder = class {
852
+ policy = {
853
+ enabled: true
854
+ };
855
+ parent;
856
+ constructor(parent) {
857
+ this.parent = parent;
858
+ }
859
+ /**
860
+ * Enable overtime calculations
861
+ */
862
+ enable() {
863
+ this.policy.enabled = true;
864
+ return this;
865
+ }
866
+ /**
867
+ * Disable overtime calculations
868
+ */
869
+ disable() {
870
+ this.policy.enabled = false;
871
+ return this;
872
+ }
873
+ /**
874
+ * Set overtime calculation mode
875
+ */
876
+ mode(mode) {
877
+ this.policy.mode = mode;
878
+ return this;
879
+ }
880
+ /**
881
+ * Set daily overtime threshold and multiplier
882
+ *
883
+ * @param hours - Hours threshold (e.g., 8)
884
+ * @param multiplier - Overtime multiplier (e.g., 1.5 for time-and-a-half)
885
+ */
886
+ dailyThreshold(hours, multiplier) {
887
+ this.policy.dailyThreshold = hours;
888
+ this.policy.dailyMultiplier = multiplier;
889
+ return this;
890
+ }
891
+ /**
892
+ * Set weekly overtime threshold and multiplier
893
+ *
894
+ * @param hours - Hours threshold (e.g., 40)
895
+ * @param multiplier - Overtime multiplier (e.g., 1.5 for time-and-a-half)
896
+ */
897
+ weeklyThreshold(hours, multiplier) {
898
+ this.policy.weeklyThreshold = hours;
899
+ this.policy.weeklyMultiplier = multiplier;
900
+ return this;
901
+ }
902
+ /**
903
+ * Set monthly overtime threshold and multiplier
904
+ *
905
+ * @param hours - Hours threshold (e.g., 160)
906
+ * @param multiplier - Overtime multiplier (e.g., 2.0 for double time)
907
+ */
908
+ monthlyThreshold(hours, multiplier) {
909
+ this.policy.monthlyThreshold = hours;
910
+ this.policy.monthlyMultiplier = multiplier;
911
+ return this;
912
+ }
913
+ /**
914
+ * Set weekend premium rates
915
+ *
916
+ * @param saturday - Saturday multiplier (e.g., 1.5)
917
+ * @param sunday - Sunday multiplier (e.g., 2.0)
918
+ */
919
+ weekendPremium(saturday, sunday) {
920
+ this.policy.weekendPremium = { saturday, sunday };
921
+ return this;
922
+ }
923
+ /**
924
+ * Set night shift differential
925
+ *
926
+ * @param startHour - Start hour (24-hour format, e.g., 22 for 10pm)
927
+ * @param endHour - End hour (24-hour format, e.g., 6 for 6am)
928
+ * @param multiplier - Night shift multiplier (e.g., 1.3 for 30% premium)
929
+ */
930
+ nightShiftDifferential(startHour, endHour, multiplier) {
931
+ this.policy.nightShiftDifferential = { startHour, endHour, multiplier };
932
+ return this;
933
+ }
934
+ /**
935
+ * Finish building this policy and return to parent builder
936
+ */
937
+ end() {
938
+ if (!this.parent) {
939
+ throw new Error("Cannot call end() without parent builder");
940
+ }
941
+ return this.parent;
942
+ }
943
+ /**
944
+ * Build the policy (for standalone usage)
945
+ */
946
+ build() {
947
+ if (this.policy.mode === void 0) {
948
+ throw new Error('Overtime mode is required. Use .mode("daily" | "weekly" | "monthly")');
949
+ }
950
+ if (this.policy.mode === "daily" && (this.policy.dailyThreshold === void 0 || this.policy.dailyMultiplier === void 0)) {
951
+ throw new Error("dailyThreshold and dailyMultiplier are required for daily mode. Use .dailyThreshold(hours, multiplier)");
952
+ }
953
+ if (this.policy.mode === "weekly" && (this.policy.weeklyThreshold === void 0 || this.policy.weeklyMultiplier === void 0)) {
954
+ throw new Error("weeklyThreshold and weeklyMultiplier are required for weekly mode. Use .weeklyThreshold(hours, multiplier)");
955
+ }
956
+ if (this.policy.mode === "monthly" && (this.policy.monthlyThreshold === void 0 || this.policy.monthlyMultiplier === void 0)) {
957
+ throw new Error("monthlyThreshold and monthlyMultiplier are required for monthly mode. Use .monthlyThreshold(hours, multiplier)");
958
+ }
959
+ return this.policy;
960
+ }
961
+ /** @internal */
962
+ _getPolicy() {
963
+ return this.policy;
964
+ }
965
+ };
966
+ var ClockRoundingPolicyBuilder = class {
967
+ policy = {
968
+ enabled: false
969
+ };
970
+ parent;
971
+ constructor(parent) {
972
+ this.parent = parent;
973
+ }
974
+ /**
975
+ * Enable clock rounding
976
+ */
977
+ enable() {
978
+ this.policy.enabled = true;
979
+ return this;
980
+ }
981
+ /**
982
+ * Disable clock rounding
983
+ */
984
+ disable() {
985
+ this.policy.enabled = false;
986
+ return this;
987
+ }
988
+ /**
989
+ * Set rounding interval in minutes
990
+ *
991
+ * @param minutes - Round to nearest N minutes (e.g., 5, 10, 15)
992
+ */
993
+ roundTo(minutes) {
994
+ this.policy.roundTo = minutes;
995
+ return this;
996
+ }
997
+ /**
998
+ * Set rounding mode
999
+ *
1000
+ * @param mode - 'up' (favor employee) | 'down' (favor employer) | 'nearest' (neutral)
1001
+ */
1002
+ roundingMode(mode) {
1003
+ this.policy.mode = mode;
1004
+ return this;
1005
+ }
1006
+ /**
1007
+ * Finish building this policy and return to parent builder
1008
+ */
1009
+ end() {
1010
+ if (!this.parent) {
1011
+ throw new Error("Cannot call end() without parent builder");
1012
+ }
1013
+ return this.parent;
1014
+ }
1015
+ /**
1016
+ * Build the policy (for standalone usage)
1017
+ */
1018
+ build() {
1019
+ if (this.policy.enabled && (this.policy.roundTo === void 0 || this.policy.mode === void 0)) {
1020
+ throw new Error("roundTo and mode are required when clock rounding is enabled");
1021
+ }
1022
+ return this.policy;
1023
+ }
1024
+ /** @internal */
1025
+ _getPolicy() {
1026
+ return this.policy;
1027
+ }
1028
+ };
1029
+ var AttendancePolicyBuilder = class _AttendancePolicyBuilder {
1030
+ policy = {
1031
+ active: true,
1032
+ effectiveFrom: /* @__PURE__ */ new Date()
1033
+ };
1034
+ lateArrivalBuilder;
1035
+ earlyDepartureBuilder;
1036
+ overtimeBuilder;
1037
+ clockRoundingBuilder;
1038
+ /**
1039
+ * Create a new policy builder
1040
+ */
1041
+ static create() {
1042
+ return new _AttendancePolicyBuilder();
1043
+ }
1044
+ /**
1045
+ * Set policy name
1046
+ */
1047
+ named(name) {
1048
+ this.policy.name = name;
1049
+ return this;
1050
+ }
1051
+ /**
1052
+ * Set policy description
1053
+ */
1054
+ description(description) {
1055
+ this.policy.description = description;
1056
+ return this;
1057
+ }
1058
+ /**
1059
+ * Set organization ID (for multi-tenant systems)
1060
+ */
1061
+ organizationId(id) {
1062
+ this.policy.organizationId = id;
1063
+ return this;
1064
+ }
1065
+ /**
1066
+ * Set policy ID (for updates)
1067
+ */
1068
+ id(id) {
1069
+ this.policy.id = id;
1070
+ return this;
1071
+ }
1072
+ /**
1073
+ * Set effective from date
1074
+ */
1075
+ effectiveFrom(date) {
1076
+ this.policy.effectiveFrom = date;
1077
+ return this;
1078
+ }
1079
+ /**
1080
+ * Set effective to date (when policy expires)
1081
+ */
1082
+ effectiveTo(date) {
1083
+ this.policy.effectiveTo = date;
1084
+ return this;
1085
+ }
1086
+ /**
1087
+ * Set policy active status
1088
+ */
1089
+ active(active) {
1090
+ this.policy.active = active;
1091
+ return this;
1092
+ }
1093
+ /**
1094
+ * Start building late arrival policy
1095
+ */
1096
+ lateArrival() {
1097
+ if (!this.lateArrivalBuilder) {
1098
+ this.lateArrivalBuilder = new LatePolicyBuilder(this);
1099
+ }
1100
+ return this.lateArrivalBuilder;
1101
+ }
1102
+ /**
1103
+ * Start building early departure policy
1104
+ */
1105
+ earlyDeparture() {
1106
+ if (!this.earlyDepartureBuilder) {
1107
+ this.earlyDepartureBuilder = new LatePolicyBuilder(this);
1108
+ }
1109
+ return this.earlyDepartureBuilder;
1110
+ }
1111
+ /**
1112
+ * Start building overtime policy
1113
+ */
1114
+ overtime() {
1115
+ if (!this.overtimeBuilder) {
1116
+ this.overtimeBuilder = new OvertimePolicyBuilder(this);
1117
+ }
1118
+ return this.overtimeBuilder;
1119
+ }
1120
+ /**
1121
+ * Start building clock rounding policy
1122
+ */
1123
+ clockRounding() {
1124
+ if (!this.clockRoundingBuilder) {
1125
+ this.clockRoundingBuilder = new ClockRoundingPolicyBuilder(this);
1126
+ }
1127
+ return this.clockRoundingBuilder;
1128
+ }
1129
+ /**
1130
+ * Build the complete attendance policy
1131
+ */
1132
+ build() {
1133
+ if (!this.policy.name) {
1134
+ throw new Error("Policy name is required. Use .named(name)");
1135
+ }
1136
+ if (this.lateArrivalBuilder) {
1137
+ this.policy.lateArrival = this.lateArrivalBuilder.build();
1138
+ } else {
1139
+ throw new Error("Late arrival policy is required. Use .lateArrival()...end()");
1140
+ }
1141
+ if (this.earlyDepartureBuilder) {
1142
+ this.policy.earlyDeparture = this.earlyDepartureBuilder.build();
1143
+ } else {
1144
+ throw new Error("Early departure policy is required. Use .earlyDeparture()...end()");
1145
+ }
1146
+ if (this.overtimeBuilder) {
1147
+ this.policy.overtime = this.overtimeBuilder.build();
1148
+ } else {
1149
+ throw new Error("Overtime policy is required. Use .overtime()...end()");
1150
+ }
1151
+ if (this.clockRoundingBuilder) {
1152
+ this.policy.clockRounding = this.clockRoundingBuilder.build();
1153
+ }
1154
+ return this.policy;
1155
+ }
1156
+ };
1157
+ function createLatePolicyBuilder() {
1158
+ return new LatePolicyBuilder();
1159
+ }
1160
+ function createOvertimePolicyBuilder() {
1161
+ return new OvertimePolicyBuilder();
1162
+ }
1163
+ function createClockRoundingPolicyBuilder() {
1164
+ return new ClockRoundingPolicyBuilder();
1165
+ }
1166
+ var PenaltyTierSchemaDefinition = {
1167
+ from: {
1168
+ type: Number,
1169
+ required: true,
1170
+ min: 1,
1171
+ description: "Starting occurrence number (1-indexed)"
1172
+ },
1173
+ to: {
1174
+ type: Number,
1175
+ min: 1,
1176
+ description: "Ending occurrence number (optional for open-ended tier)"
1177
+ },
1178
+ penalty: {
1179
+ type: Number,
1180
+ required: true,
1181
+ min: 0,
1182
+ description: "Penalty amount (0 for warnings)"
1183
+ },
1184
+ warning: {
1185
+ type: Boolean,
1186
+ default: false,
1187
+ description: "Whether this is a warning-only tier"
1188
+ }
1189
+ };
1190
+ var PenaltyTierSchema = new Schema(PenaltyTierSchemaDefinition, {
1191
+ _id: false,
1192
+ timestamps: false
1193
+ });
1194
+ var MaxPenaltiesSchemaDefinition = {
1195
+ count: {
1196
+ type: Number,
1197
+ required: true,
1198
+ min: 1,
1199
+ description: "Maximum number of penalties allowed in period"
1200
+ },
1201
+ period: {
1202
+ type: String,
1203
+ enum: ["daily", "weekly", "monthly", "quarterly", "yearly"],
1204
+ required: true,
1205
+ description: "Period type"
1206
+ }
1207
+ };
1208
+ var MaxPenaltiesSchema = new Schema(MaxPenaltiesSchemaDefinition, {
1209
+ _id: false,
1210
+ timestamps: false
1211
+ });
1212
+ var LateArrivalPolicySchemaDefinition = {
1213
+ enabled: {
1214
+ type: Boolean,
1215
+ required: true,
1216
+ default: true,
1217
+ description: "Whether late arrival penalties are enabled"
1218
+ },
1219
+ gracePeriod: {
1220
+ type: Number,
1221
+ required: true,
1222
+ min: 0,
1223
+ max: 60,
1224
+ default: 0,
1225
+ description: "Grace period in minutes before penalty applies"
1226
+ },
1227
+ mode: {
1228
+ type: String,
1229
+ enum: ["flat", "per-minute", "percentage", "tiered"],
1230
+ required: true,
1231
+ description: "Penalty calculation mode"
1232
+ },
1233
+ flatAmount: {
1234
+ type: Number,
1235
+ min: 0,
1236
+ description: "Flat penalty amount per occurrence (for flat mode)"
1237
+ },
1238
+ perMinuteRate: {
1239
+ type: Number,
1240
+ min: 0,
1241
+ description: "Penalty per minute late (for per-minute mode)"
1242
+ },
1243
+ percentageRate: {
1244
+ type: Number,
1245
+ min: 0,
1246
+ max: 100,
1247
+ description: "Percentage of daily wage (for percentage mode)"
1248
+ },
1249
+ tiers: {
1250
+ type: [PenaltyTierSchema],
1251
+ description: "Penalty tiers for progressive discipline (for tiered mode)"
1252
+ },
1253
+ maxPenaltiesPerPeriod: {
1254
+ type: MaxPenaltiesSchema,
1255
+ description: "Maximum penalties allowed per period"
1256
+ },
1257
+ resetOccurrenceCount: {
1258
+ type: String,
1259
+ enum: ["monthly", "quarterly", "yearly"],
1260
+ description: "When to reset occurrence counter"
1261
+ }
1262
+ };
1263
+ var LateArrivalPolicySchema = new Schema(LateArrivalPolicySchemaDefinition, {
1264
+ _id: false,
1265
+ timestamps: false
1266
+ });
1267
+ var EarlyDeparturePolicySchemaDefinition = LateArrivalPolicySchemaDefinition;
1268
+ var EarlyDeparturePolicySchema = new Schema(EarlyDeparturePolicySchemaDefinition, {
1269
+ _id: false,
1270
+ timestamps: false
1271
+ });
1272
+ var WeekendPremiumSchemaDefinition = {
1273
+ saturday: {
1274
+ type: Number,
1275
+ required: true,
1276
+ min: 1,
1277
+ description: "Saturday premium multiplier (e.g., 1.5 for time-and-a-half)"
1278
+ },
1279
+ sunday: {
1280
+ type: Number,
1281
+ required: true,
1282
+ min: 1,
1283
+ description: "Sunday premium multiplier (e.g., 2.0 for double time)"
1284
+ }
1285
+ };
1286
+ var WeekendPremiumSchema = new Schema(WeekendPremiumSchemaDefinition, {
1287
+ _id: false,
1288
+ timestamps: false
1289
+ });
1290
+ var NightShiftDifferentialSchemaDefinition = {
1291
+ startHour: {
1292
+ type: Number,
1293
+ required: true,
1294
+ min: 0,
1295
+ max: 23,
1296
+ description: "Night shift start hour (24-hour format, e.g., 22 for 10pm)"
1297
+ },
1298
+ endHour: {
1299
+ type: Number,
1300
+ required: true,
1301
+ min: 0,
1302
+ max: 23,
1303
+ description: "Night shift end hour (24-hour format, e.g., 6 for 6am)"
1304
+ },
1305
+ multiplier: {
1306
+ type: Number,
1307
+ required: true,
1308
+ min: 1,
1309
+ description: "Night shift premium multiplier (e.g., 1.3 for 30% premium)"
1310
+ }
1311
+ };
1312
+ var NightShiftDifferentialSchema = new Schema(NightShiftDifferentialSchemaDefinition, {
1313
+ _id: false,
1314
+ timestamps: false
1315
+ });
1316
+ var OvertimePolicySchemaDefinition = {
1317
+ enabled: {
1318
+ type: Boolean,
1319
+ required: true,
1320
+ default: true,
1321
+ description: "Whether overtime bonuses are enabled"
1322
+ },
1323
+ mode: {
1324
+ type: String,
1325
+ enum: ["daily", "weekly", "monthly"],
1326
+ required: true,
1327
+ description: "Overtime calculation mode"
1328
+ },
1329
+ dailyThreshold: {
1330
+ type: Number,
1331
+ min: 0,
1332
+ description: "Daily overtime threshold in hours (for daily mode)"
1333
+ },
1334
+ dailyMultiplier: {
1335
+ type: Number,
1336
+ min: 1,
1337
+ description: "Daily overtime pay multiplier (e.g., 1.5 for time-and-a-half)"
1338
+ },
1339
+ weeklyThreshold: {
1340
+ type: Number,
1341
+ min: 0,
1342
+ description: "Weekly overtime threshold in hours (for weekly mode)"
1343
+ },
1344
+ weeklyMultiplier: {
1345
+ type: Number,
1346
+ min: 1,
1347
+ description: "Weekly overtime pay multiplier"
1348
+ },
1349
+ monthlyThreshold: {
1350
+ type: Number,
1351
+ min: 0,
1352
+ description: "Monthly overtime threshold in hours (for monthly mode)"
1353
+ },
1354
+ monthlyMultiplier: {
1355
+ type: Number,
1356
+ min: 1,
1357
+ description: "Monthly overtime pay multiplier"
1358
+ },
1359
+ weekendPremium: {
1360
+ type: WeekendPremiumSchema,
1361
+ description: "Weekend premium rates"
1362
+ },
1363
+ nightShiftDifferential: {
1364
+ type: NightShiftDifferentialSchema,
1365
+ description: "Night shift differential rates"
1366
+ }
1367
+ };
1368
+ var OvertimePolicySchema = new Schema(OvertimePolicySchemaDefinition, {
1369
+ _id: false,
1370
+ timestamps: false
1371
+ });
1372
+ var ClockRoundingPolicySchemaDefinition = {
1373
+ enabled: {
1374
+ type: Boolean,
1375
+ required: true,
1376
+ default: false,
1377
+ description: "Whether clock rounding is enabled"
1378
+ },
1379
+ roundTo: {
1380
+ type: Number,
1381
+ enum: [5, 10, 15],
1382
+ description: "Round to nearest N minutes"
1383
+ },
1384
+ mode: {
1385
+ type: String,
1386
+ enum: ["up", "down", "nearest"],
1387
+ description: "Rounding mode: up (favor employee), down (favor employer), nearest (neutral)"
1388
+ }
1389
+ };
1390
+ var ClockRoundingPolicySchema = new Schema(ClockRoundingPolicySchemaDefinition, {
1391
+ _id: false,
1392
+ timestamps: false
1393
+ });
1394
+ var AttendancePolicySchemaDefinition = {
1395
+ organizationId: {
1396
+ type: Schema.Types.ObjectId,
1397
+ index: true,
1398
+ description: "Organization this policy belongs to (for multi-tenant)"
1399
+ },
1400
+ name: {
1401
+ type: String,
1402
+ required: true,
1403
+ trim: true,
1404
+ maxlength: 200,
1405
+ description: "Policy name"
1406
+ },
1407
+ description: {
1408
+ type: String,
1409
+ trim: true,
1410
+ maxlength: 1e3,
1411
+ description: "Policy description"
1412
+ },
1413
+ lateArrival: {
1414
+ type: LateArrivalPolicySchema,
1415
+ required: true,
1416
+ description: "Late arrival penalty policy"
1417
+ },
1418
+ earlyDeparture: {
1419
+ type: EarlyDeparturePolicySchema,
1420
+ required: true,
1421
+ description: "Early departure penalty policy"
1422
+ },
1423
+ overtime: {
1424
+ type: OvertimePolicySchema,
1425
+ required: true,
1426
+ description: "Overtime bonus policy"
1427
+ },
1428
+ clockRounding: {
1429
+ type: ClockRoundingPolicySchema,
1430
+ description: "Clock rounding policy (optional)"
1431
+ },
1432
+ effectiveFrom: {
1433
+ type: Date,
1434
+ required: true,
1435
+ default: Date.now,
1436
+ description: "When this policy becomes effective"
1437
+ },
1438
+ effectiveTo: {
1439
+ type: Date,
1440
+ default: null,
1441
+ description: "When this policy expires (null for no expiration)"
1442
+ },
1443
+ active: {
1444
+ type: Boolean,
1445
+ required: true,
1446
+ default: true,
1447
+ index: true,
1448
+ description: "Whether this policy is currently active"
1449
+ }
1450
+ };
1451
+ var AttendancePolicySchema = new Schema(AttendancePolicySchemaDefinition, {
1452
+ timestamps: true,
1453
+ collection: "attendance_policies"
1454
+ });
1455
+ AttendancePolicySchema.index({ organizationId: 1, active: 1, effectiveFrom: -1 });
1456
+ AttendancePolicySchema.index({ effectiveFrom: 1, effectiveTo: 1 });
1457
+ AttendancePolicySchema.methods.isCurrentlyActive = function() {
1458
+ if (!this.active) return false;
1459
+ const now = /* @__PURE__ */ new Date();
1460
+ if (this.effectiveFrom > now) return false;
1461
+ if (this.effectiveTo && this.effectiveTo < now) return false;
1462
+ return true;
1463
+ };
1464
+ AttendancePolicySchema.statics.findActiveForOrganization = function(organizationId, date = /* @__PURE__ */ new Date()) {
1465
+ return this.findOne({
1466
+ organizationId,
1467
+ active: true,
1468
+ effectiveFrom: { $lte: date },
1469
+ $or: [
1470
+ { effectiveTo: null },
1471
+ { effectiveTo: { $gt: date } }
1472
+ ]
1473
+ }).sort({ effectiveFrom: -1 });
1474
+ };
1475
+ var schemas_default = AttendancePolicySchema;
1476
+
1477
+ export { AttendancePolicyBuilder, AttendancePolicySchema, AttendancePolicySchemaDefinition, ClockRoundingPolicyBuilder, ClockRoundingPolicySchema, ClockRoundingPolicySchemaDefinition, DEFAULT_ATTENDANCE_POLICY, EarlyDeparturePolicySchema, EarlyDeparturePolicySchemaDefinition, HEALTHCARE_POLICY, HOSPITALITY_POLICY, LateArrivalPolicySchema, LateArrivalPolicySchemaDefinition, LatePolicyBuilder, MANUFACTURING_POLICY, MaxPenaltiesSchema, MaxPenaltiesSchemaDefinition, NightShiftDifferentialSchema, NightShiftDifferentialSchemaDefinition, OFFICE_POLICY, OvertimePolicyBuilder, OvertimePolicySchema, OvertimePolicySchemaDefinition, PenaltyTierSchema, PenaltyTierSchemaDefinition, RETAIL_POLICY, TieredPenaltyBuilder, WeekendPremiumSchema, WeekendPremiumSchemaDefinition, calculateDailyOvertime, calculateFlatPenalty, calculateLatePenalty, calculateMonthlyOvertime, calculateNightShiftDifferential, calculateOvertimeBonus, calculatePerMinutePenalty, calculatePercentagePenalty, calculateShiftCompliance, calculateTieredPenalty, calculateWeekendPremium, calculateWeeklyOvertime, createClockRoundingPolicyBuilder, createLatePolicyBuilder, createOvertimePolicyBuilder, createPolicyFromPreset, schemas_default as default };
1478
+ //# sourceMappingURL=index.js.map
1479
+ //# sourceMappingURL=index.js.map