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