@classytic/payroll 2.0.0 → 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.
- package/README.md +2599 -253
- package/dist/calculators/index.d.ts +433 -0
- package/dist/calculators/index.js +283 -0
- package/dist/calculators/index.js.map +1 -0
- package/dist/core/index.d.ts +85 -251
- package/dist/core/index.js +286 -91
- package/dist/core/index.js.map +1 -1
- package/dist/employee-identity-DXhgOgXE.d.ts +473 -0
- package/dist/employee.factory-BlZqhiCk.d.ts +189 -0
- package/dist/idempotency-Cw2CWicb.d.ts +52 -0
- package/dist/index.d.ts +618 -683
- package/dist/index.js +8336 -3580
- package/dist/index.js.map +1 -1
- package/dist/jurisdiction/index.d.ts +660 -0
- package/dist/jurisdiction/index.js +533 -0
- package/dist/jurisdiction/index.js.map +1 -0
- package/dist/payroll.d.ts +261 -65
- package/dist/payroll.js +4164 -1075
- package/dist/payroll.js.map +1 -1
- package/dist/schemas/index.d.ts +1176 -783
- package/dist/schemas/index.js +368 -28
- package/dist/schemas/index.js.map +1 -1
- package/dist/services/index.d.ts +582 -3
- package/dist/services/index.js +572 -96
- package/dist/services/index.js.map +1 -1
- package/dist/shift-compliance/index.d.ts +1171 -0
- package/dist/shift-compliance/index.js +1479 -0
- package/dist/shift-compliance/index.js.map +1 -0
- package/dist/types-BN3K_Uhr.d.ts +1842 -0
- package/dist/utils/index.d.ts +22 -2
- package/dist/utils/index.js +470 -1
- package/dist/utils/index.js.map +1 -1
- package/package.json +24 -6
- package/dist/index-CTjHlCzz.d.ts +0 -721
- package/dist/plugin-D9mOr3_d.d.ts +0 -333
- package/dist/types-BSYyX2KJ.d.ts +0 -671
|
@@ -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
|