@classytic/payroll 1.0.0 → 2.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +535 -574
- package/dist/attendance.calculator-BZcv2iii.d.ts +336 -0
- package/dist/calculators/index.d.ts +4 -0
- package/dist/calculators/index.js +439 -0
- package/dist/calculators/index.js.map +1 -0
- package/dist/core/index.d.ts +321 -0
- package/dist/core/index.js +1962 -0
- package/dist/core/index.js.map +1 -0
- package/dist/error-helpers-Bm6lMny2.d.ts +740 -0
- package/dist/index-BKLkuSAs.d.ts +3858 -0
- package/dist/index.d.ts +2684 -0
- package/dist/index.js +11454 -0
- package/dist/index.js.map +1 -0
- package/dist/payroll-states-DBt0XVm-.d.ts +598 -0
- package/dist/prorating.calculator-C33fWBQf.d.ts +135 -0
- package/dist/schemas/index.d.ts +4 -0
- package/dist/schemas/index.js +1472 -0
- package/dist/schemas/index.js.map +1 -0
- package/dist/types-bZdAJueH.d.ts +2271 -0
- package/dist/utils/index.d.ts +1007 -0
- package/dist/utils/index.js +1789 -0
- package/dist/utils/index.js.map +1 -0
- package/package.json +81 -24
- 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 -247
- package/src/hrm.orchestrator.js +0 -139
- package/src/index.js +0 -172
- package/src/init.js +0 -41
- package/src/models/payroll-record.model.js +0 -126
- package/src/plugins/employee.plugin.js +0 -157
- 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
package/README.md
CHANGED
|
@@ -1,574 +1,535 @@
|
|
|
1
|
-
#
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
##
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
//
|
|
150
|
-
const
|
|
151
|
-
|
|
152
|
-
month:
|
|
153
|
-
year:
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
})
|
|
158
|
-
|
|
159
|
-
//
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
```
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
//
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
//
|
|
194
|
-
await
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
}
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
{
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
{
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
//
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
//
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
//
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
//
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
```
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
```javascript
|
|
537
|
-
// TTL index on PayrollRecord
|
|
538
|
-
payrollRecordSchema.index(
|
|
539
|
-
{ createdAt: 1 },
|
|
540
|
-
{
|
|
541
|
-
expireAfterSeconds: 63072000, // 2 years
|
|
542
|
-
partialFilterExpression: { exported: true } // Only if exported
|
|
543
|
-
}
|
|
544
|
-
);
|
|
545
|
-
|
|
546
|
-
// Export before deletion
|
|
547
|
-
const records = await hrm.exportPayroll({
|
|
548
|
-
organizationId,
|
|
549
|
-
startDate: new Date('2023-01-01'),
|
|
550
|
-
endDate: new Date('2023-12-31')
|
|
551
|
-
});
|
|
552
|
-
// Marks records as exported, making them eligible for deletion
|
|
553
|
-
```
|
|
554
|
-
|
|
555
|
-
## 🎯 Design Philosophy
|
|
556
|
-
|
|
557
|
-
- **Stripe/Passport.js inspired**: Clean DSL, dependency injection, reusable components
|
|
558
|
-
- **Lightweight**: Not a complex ERP, gym-focused features only
|
|
559
|
-
- **Multi-tenant**: Same user can work at multiple organizations
|
|
560
|
-
- **Smart defaults**: Pro-rating, attendance integration, automatic calculations
|
|
561
|
-
- **Production-ready**: Transaction integration, data retention, comprehensive error handling
|
|
562
|
-
|
|
563
|
-
## ✅ Next Steps
|
|
564
|
-
|
|
565
|
-
1. Test bootstrap initialization
|
|
566
|
-
2. Create Fastify routes in `modules/employee/`
|
|
567
|
-
3. Add API handlers
|
|
568
|
-
4. Migrate existing staff from organization module
|
|
569
|
-
5. Deploy and monitor
|
|
570
|
-
|
|
571
|
-
---
|
|
572
|
-
|
|
573
|
-
**Built with ❤️ following world-class architecture patterns**
|
|
574
|
-
**Ready for multi-tenant gym management**
|
|
1
|
+
# @classytic/payroll
|
|
2
|
+
|
|
3
|
+
HRM & Payroll for MongoDB. Multi-tenant, event-driven, type-safe.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install @classytic/payroll mongoose @classytic/mongokit
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Quick Start
|
|
12
|
+
|
|
13
|
+
```typescript
|
|
14
|
+
import { createPayrollInstance } from '@classytic/payroll';
|
|
15
|
+
|
|
16
|
+
const payroll = createPayrollInstance()
|
|
17
|
+
.withModels({ EmployeeModel, PayrollRecordModel, TransactionModel })
|
|
18
|
+
.build();
|
|
19
|
+
|
|
20
|
+
// Hire
|
|
21
|
+
await payroll.hire({
|
|
22
|
+
organizationId,
|
|
23
|
+
employment: { email: 'dev@example.com', position: 'Engineer', hireDate: new Date() },
|
|
24
|
+
compensation: { baseAmount: 80000, currency: 'USD', frequency: 'monthly' },
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
// Process salary
|
|
28
|
+
await payroll.processSalary({
|
|
29
|
+
organizationId,
|
|
30
|
+
employeeId,
|
|
31
|
+
month: 1,
|
|
32
|
+
year: 2024,
|
|
33
|
+
});
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## Package Exports
|
|
37
|
+
|
|
38
|
+
| Entry Point | Description |
|
|
39
|
+
|-------------|-------------|
|
|
40
|
+
| `@classytic/payroll` | Main API: Payroll class, types, schemas, errors |
|
|
41
|
+
| `@classytic/payroll/calculators` | Pure calculation functions (no DB required) |
|
|
42
|
+
| `@classytic/payroll/utils` | Date, money, validation utilities |
|
|
43
|
+
| `@classytic/payroll/schemas` | Mongoose schema factories |
|
|
44
|
+
|
|
45
|
+
---
|
|
46
|
+
|
|
47
|
+
## Employee Operations
|
|
48
|
+
|
|
49
|
+
```typescript
|
|
50
|
+
// Hire
|
|
51
|
+
await payroll.hire({
|
|
52
|
+
organizationId,
|
|
53
|
+
employment: { email, employeeId, position, department, hireDate },
|
|
54
|
+
compensation: { baseAmount, currency, frequency },
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
// Get employee
|
|
58
|
+
const emp = await payroll.getEmployee({ employeeId, organizationId });
|
|
59
|
+
|
|
60
|
+
// Update employment
|
|
61
|
+
await payroll.updateEmployment({
|
|
62
|
+
employeeId,
|
|
63
|
+
organizationId,
|
|
64
|
+
updates: { position: 'Senior Engineer', department: 'engineering' },
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
// Terminate
|
|
68
|
+
await payroll.terminate({
|
|
69
|
+
employeeId,
|
|
70
|
+
organizationId,
|
|
71
|
+
terminationDate: new Date(),
|
|
72
|
+
reason: 'resignation',
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
// Re-hire
|
|
76
|
+
await payroll.reHire({ employeeId, organizationId, hireDate: new Date() });
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
## Compensation
|
|
80
|
+
|
|
81
|
+
```typescript
|
|
82
|
+
// Update salary
|
|
83
|
+
await payroll.updateSalary({
|
|
84
|
+
employeeId,
|
|
85
|
+
organizationId,
|
|
86
|
+
compensation: { baseAmount: 90000 },
|
|
87
|
+
effectiveFrom: new Date(),
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
// Add allowance
|
|
91
|
+
await payroll.addAllowance({
|
|
92
|
+
employeeId,
|
|
93
|
+
organizationId,
|
|
94
|
+
allowance: {
|
|
95
|
+
type: 'housing', // 'housing' | 'transport' | 'meal' | 'mobile' | 'medical' | 'bonus' | 'other'
|
|
96
|
+
amount: 2000,
|
|
97
|
+
taxable: true,
|
|
98
|
+
},
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
// Add deduction
|
|
102
|
+
await payroll.addDeduction({
|
|
103
|
+
employeeId,
|
|
104
|
+
organizationId,
|
|
105
|
+
deduction: {
|
|
106
|
+
type: 'provident_fund', // 'tax' | 'loan' | 'advance' | 'provident_fund' | 'insurance'
|
|
107
|
+
amount: 500,
|
|
108
|
+
auto: true,
|
|
109
|
+
},
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
// Update bank details
|
|
113
|
+
await payroll.updateBankDetails({
|
|
114
|
+
employeeId,
|
|
115
|
+
organizationId,
|
|
116
|
+
bankDetails: { accountNumber, bankName, routingNumber },
|
|
117
|
+
});
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
### Payment Frequencies
|
|
121
|
+
|
|
122
|
+
Supports multiple payment frequencies with automatic tax annualization:
|
|
123
|
+
|
|
124
|
+
| Frequency | baseAmount | Periods/Year | Example ($104k/year) |
|
|
125
|
+
|-----------|------------|--------------|----------------------|
|
|
126
|
+
| `monthly` | Monthly salary | 12 | $8,666.67/month |
|
|
127
|
+
| `bi_weekly` | Bi-weekly wage | 26 | $4,000/bi-week |
|
|
128
|
+
| `weekly` | Weekly wage | 52 | $2,000/week |
|
|
129
|
+
| `daily` | Daily rate | 365 | $285/day |
|
|
130
|
+
| `hourly` | Hourly rate | 2080 | $50/hour |
|
|
131
|
+
|
|
132
|
+
Tax is calculated consistently: same annual income = same annual tax, regardless of frequency.
|
|
133
|
+
|
|
134
|
+
## Payroll Processing
|
|
135
|
+
|
|
136
|
+
```typescript
|
|
137
|
+
// Single employee
|
|
138
|
+
const result = await payroll.processSalary({
|
|
139
|
+
organizationId,
|
|
140
|
+
employeeId,
|
|
141
|
+
month: 1,
|
|
142
|
+
year: 2024,
|
|
143
|
+
paymentDate: new Date(),
|
|
144
|
+
paymentMethod: 'bank',
|
|
145
|
+
payrollRunType: 'regular', // 'regular' | 'supplemental' | 'retroactive' | 'off-cycle'
|
|
146
|
+
});
|
|
147
|
+
// Returns: { employee, payrollRecord, transaction }
|
|
148
|
+
|
|
149
|
+
// Bulk processing
|
|
150
|
+
const bulk = await payroll.processBulkPayroll({
|
|
151
|
+
organizationId, // Optional in single-tenant mode or with context.organizationId
|
|
152
|
+
month: 1,
|
|
153
|
+
year: 2024,
|
|
154
|
+
employeeIds: [], // Optional: specific employees (default: all active + on_leave)
|
|
155
|
+
batchSize: 50,
|
|
156
|
+
concurrency: 5,
|
|
157
|
+
onProgress: (p) => console.log(`${p.percentage}%`),
|
|
158
|
+
});
|
|
159
|
+
// Returns: { successCount, failCount, totalAmount, successful[], failed[] }
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
### Duplicate Protection
|
|
163
|
+
|
|
164
|
+
The package provides database-level duplicate protection via a unique compound index:
|
|
165
|
+
|
|
166
|
+
```typescript
|
|
167
|
+
// Unique index on: (organizationId, employeeId, period.month, period.year, payrollRunType)
|
|
168
|
+
// With partial filter: { isVoided: { $eq: false } }
|
|
169
|
+
|
|
170
|
+
// This allows:
|
|
171
|
+
// - One active record per employee per period per run type
|
|
172
|
+
// - Multiple run types in same period (regular + supplemental)
|
|
173
|
+
// - Re-processing after voiding (requires restorePayroll() first)
|
|
174
|
+
// - Re-processing after reversing
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
**Important**: Voided records require `restorePayroll()` before re-processing. Voided is a terminal state that preserves audit trail.
|
|
178
|
+
|
|
179
|
+
## Two-Phase Export
|
|
180
|
+
|
|
181
|
+
Safe export that only marks records after downstream confirms receipt:
|
|
182
|
+
|
|
183
|
+
```typescript
|
|
184
|
+
// Phase 1: Prepare (records NOT marked)
|
|
185
|
+
const { records, exportId } = await payroll.prepareExport({
|
|
186
|
+
organizationId,
|
|
187
|
+
startDate: new Date('2024-01-01'),
|
|
188
|
+
endDate: new Date('2024-01-31'),
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
// Send to external system...
|
|
192
|
+
|
|
193
|
+
// Phase 2a: Confirm success (marks records)
|
|
194
|
+
await payroll.confirmExport({ organizationId, exportId });
|
|
195
|
+
|
|
196
|
+
// Phase 2b: Cancel if failed (records stay unmarked)
|
|
197
|
+
await payroll.cancelExport({ organizationId, exportId, reason: 'API error' });
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
## Void / Reverse / Restore
|
|
201
|
+
|
|
202
|
+
```typescript
|
|
203
|
+
// Void unpaid payroll (pending, processing, failed)
|
|
204
|
+
await payroll.voidPayroll({
|
|
205
|
+
organizationId,
|
|
206
|
+
payrollRecordId,
|
|
207
|
+
reason: 'Test payroll',
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
// Reverse paid payroll (creates reversal transaction)
|
|
211
|
+
await payroll.reversePayroll({
|
|
212
|
+
organizationId,
|
|
213
|
+
payrollRecordId,
|
|
214
|
+
reason: 'Duplicate payment',
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
// Restore voided payroll (blocked if replacement exists)
|
|
218
|
+
await payroll.restorePayroll({
|
|
219
|
+
organizationId,
|
|
220
|
+
payrollRecordId,
|
|
221
|
+
reason: 'Voided in error',
|
|
222
|
+
});
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
**Status Flow:**
|
|
226
|
+
```
|
|
227
|
+
PENDING → PROCESSING → PAID → REVERSED
|
|
228
|
+
↓ ↓
|
|
229
|
+
└──→ VOIDED ←── FAILED
|
|
230
|
+
↓
|
|
231
|
+
PENDING (restore)
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
## Leave Management
|
|
235
|
+
|
|
236
|
+
```typescript
|
|
237
|
+
// Request leave
|
|
238
|
+
await payroll.requestLeave({
|
|
239
|
+
employeeId,
|
|
240
|
+
organizationId,
|
|
241
|
+
leaveType: 'annual', // 'annual' | 'sick' | 'unpaid' | 'maternity' | 'paternity'
|
|
242
|
+
startDate: new Date('2024-03-01'),
|
|
243
|
+
endDate: new Date('2024-03-05'),
|
|
244
|
+
reason: 'Vacation',
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
// Approve
|
|
248
|
+
await payroll.approveLeave({ leaveRequestId, organizationId, approverId: managerId });
|
|
249
|
+
|
|
250
|
+
// Reject
|
|
251
|
+
await payroll.rejectLeave({
|
|
252
|
+
leaveRequestId,
|
|
253
|
+
organizationId,
|
|
254
|
+
rejectedBy: managerId,
|
|
255
|
+
rejectionReason: 'Insufficient leave balance',
|
|
256
|
+
});
|
|
257
|
+
|
|
258
|
+
// Get balance
|
|
259
|
+
const balance = await payroll.getLeaveBalance({ employeeId, organizationId });
|
|
260
|
+
// { annual: { total: 20, used: 5, remaining: 15 }, sick: {...}, ... }
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
---
|
|
264
|
+
|
|
265
|
+
## Pure Calculators (No DB Required)
|
|
266
|
+
|
|
267
|
+
Import from `@classytic/payroll/calculators` for client-side or serverless:
|
|
268
|
+
|
|
269
|
+
```typescript
|
|
270
|
+
import {
|
|
271
|
+
calculateSalaryBreakdown,
|
|
272
|
+
calculateProRating,
|
|
273
|
+
calculateAttendanceDeduction,
|
|
274
|
+
} from '@classytic/payroll/calculators';
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
### Salary Breakdown
|
|
278
|
+
|
|
279
|
+
```typescript
|
|
280
|
+
const breakdown = calculateSalaryBreakdown({
|
|
281
|
+
employee: {
|
|
282
|
+
hireDate: new Date('2024-01-01'),
|
|
283
|
+
terminationDate: null,
|
|
284
|
+
compensation: {
|
|
285
|
+
baseAmount: 100000,
|
|
286
|
+
frequency: 'monthly',
|
|
287
|
+
currency: 'USD',
|
|
288
|
+
allowances: [
|
|
289
|
+
{ type: 'housing', amount: 20000, taxable: true },
|
|
290
|
+
{ type: 'transport', amount: 5000, taxable: true },
|
|
291
|
+
],
|
|
292
|
+
deductions: [
|
|
293
|
+
{ type: 'provident_fund', amount: 5000, auto: true },
|
|
294
|
+
],
|
|
295
|
+
},
|
|
296
|
+
},
|
|
297
|
+
period: {
|
|
298
|
+
month: 3,
|
|
299
|
+
year: 2024,
|
|
300
|
+
startDate: new Date('2024-03-01'),
|
|
301
|
+
endDate: new Date('2024-03-31'),
|
|
302
|
+
},
|
|
303
|
+
attendance: {
|
|
304
|
+
expectedDays: 22,
|
|
305
|
+
actualDays: 20,
|
|
306
|
+
},
|
|
307
|
+
config: {
|
|
308
|
+
allowProRating: true,
|
|
309
|
+
autoDeductions: true,
|
|
310
|
+
defaultCurrency: 'USD',
|
|
311
|
+
attendanceIntegration: true,
|
|
312
|
+
},
|
|
313
|
+
taxBrackets: [
|
|
314
|
+
{ min: 0, max: 600000, rate: 0 },
|
|
315
|
+
{ min: 600000, max: 1200000, rate: 0.1 },
|
|
316
|
+
{ min: 1200000, max: Infinity, rate: 0.2 },
|
|
317
|
+
],
|
|
318
|
+
});
|
|
319
|
+
|
|
320
|
+
// Returns PayrollBreakdown
|
|
321
|
+
{
|
|
322
|
+
baseAmount: number,
|
|
323
|
+
allowances: Array<{ type, amount, taxable }>,
|
|
324
|
+
deductions: Array<{ type, amount, description }>,
|
|
325
|
+
grossSalary: number,
|
|
326
|
+
netSalary: number,
|
|
327
|
+
taxableAmount: number,
|
|
328
|
+
taxAmount: number,
|
|
329
|
+
workingDays: number,
|
|
330
|
+
actualDays: number,
|
|
331
|
+
proRatedAmount: number,
|
|
332
|
+
attendanceDeduction: number,
|
|
333
|
+
}
|
|
334
|
+
```
|
|
335
|
+
|
|
336
|
+
### Pro-Rating
|
|
337
|
+
|
|
338
|
+
```typescript
|
|
339
|
+
import { calculateProRating } from '@classytic/payroll/calculators';
|
|
340
|
+
|
|
341
|
+
const result = calculateProRating({
|
|
342
|
+
hireDate: new Date('2024-03-15'),
|
|
343
|
+
terminationDate: null,
|
|
344
|
+
periodStart: new Date('2024-03-01'),
|
|
345
|
+
periodEnd: new Date('2024-03-31'),
|
|
346
|
+
workingDays: [1, 2, 3, 4, 5],
|
|
347
|
+
holidays: [],
|
|
348
|
+
});
|
|
349
|
+
|
|
350
|
+
// Returns ProRatingResult
|
|
351
|
+
{
|
|
352
|
+
isProRated: true,
|
|
353
|
+
ratio: 0.545,
|
|
354
|
+
periodWorkingDays: 22,
|
|
355
|
+
effectiveWorkingDays: 12,
|
|
356
|
+
reason: 'new_hire',
|
|
357
|
+
}
|
|
358
|
+
```
|
|
359
|
+
|
|
360
|
+
---
|
|
361
|
+
|
|
362
|
+
## Events
|
|
363
|
+
|
|
364
|
+
```typescript
|
|
365
|
+
payroll.on('employee:hired', (payload) => { /* { employee, organizationId } */ });
|
|
366
|
+
payroll.on('employee:terminated', (payload) => { /* { employee, reason } */ });
|
|
367
|
+
payroll.on('salary:processed', (payload) => { /* { payrollRecord, transaction } */ });
|
|
368
|
+
payroll.on('payroll:completed', (payload) => { /* { summary, period } */ });
|
|
369
|
+
payroll.on('payroll:exported', (payload) => { /* { exportId, recordCount } */ });
|
|
370
|
+
```
|
|
371
|
+
|
|
372
|
+
## Webhooks
|
|
373
|
+
|
|
374
|
+
```typescript
|
|
375
|
+
// Register webhook
|
|
376
|
+
payroll.registerWebhook({
|
|
377
|
+
url: 'https://api.example.com/webhooks',
|
|
378
|
+
events: ['salary:processed', 'employee:hired'],
|
|
379
|
+
secret: 'your-secret',
|
|
380
|
+
});
|
|
381
|
+
|
|
382
|
+
// Verify signature in handler
|
|
383
|
+
const signature = req.headers['x-payroll-signature'];
|
|
384
|
+
const timestamp = req.headers['x-payroll-timestamp'];
|
|
385
|
+
const signedPayload = `${timestamp}.${JSON.stringify(req.body)}`;
|
|
386
|
+
const expected = crypto.createHmac('sha256', secret).update(signedPayload).digest('hex');
|
|
387
|
+
```
|
|
388
|
+
|
|
389
|
+
---
|
|
390
|
+
|
|
391
|
+
## Configuration
|
|
392
|
+
|
|
393
|
+
### Multi-Tenant (Default)
|
|
394
|
+
|
|
395
|
+
```typescript
|
|
396
|
+
const payroll = createPayrollInstance()
|
|
397
|
+
.withModels({ EmployeeModel, PayrollRecordModel, TransactionModel })
|
|
398
|
+
.withConfig({
|
|
399
|
+
payroll: {
|
|
400
|
+
defaultCurrency: 'USD',
|
|
401
|
+
attendanceIntegration: true,
|
|
402
|
+
allowProRating: true,
|
|
403
|
+
autoDeductions: true,
|
|
404
|
+
},
|
|
405
|
+
})
|
|
406
|
+
.build();
|
|
407
|
+
|
|
408
|
+
// organizationId required on all operations
|
|
409
|
+
await payroll.hire({ organizationId, employment, compensation });
|
|
410
|
+
```
|
|
411
|
+
|
|
412
|
+
### Single-Tenant
|
|
413
|
+
|
|
414
|
+
```typescript
|
|
415
|
+
const payroll = createPayrollInstance()
|
|
416
|
+
.withModels({ EmployeeModel, PayrollRecordModel, TransactionModel })
|
|
417
|
+
.forSingleTenant({ organizationId: YOUR_ORG_ID, autoInject: true })
|
|
418
|
+
.build();
|
|
419
|
+
|
|
420
|
+
// organizationId auto-injected
|
|
421
|
+
await payroll.hire({ employment, compensation });
|
|
422
|
+
```
|
|
423
|
+
|
|
424
|
+
---
|
|
425
|
+
|
|
426
|
+
## Key Types
|
|
427
|
+
|
|
428
|
+
```typescript
|
|
429
|
+
import type {
|
|
430
|
+
// Documents
|
|
431
|
+
EmployeeDocument,
|
|
432
|
+
PayrollRecordDocument,
|
|
433
|
+
LeaveRequestDocument,
|
|
434
|
+
|
|
435
|
+
// Core types
|
|
436
|
+
Compensation,
|
|
437
|
+
Allowance,
|
|
438
|
+
Deduction,
|
|
439
|
+
PayrollBreakdown,
|
|
440
|
+
TaxBracket,
|
|
441
|
+
BankDetails,
|
|
442
|
+
|
|
443
|
+
// Params
|
|
444
|
+
HireEmployeeParams,
|
|
445
|
+
ProcessSalaryParams,
|
|
446
|
+
ProcessBulkPayrollParams,
|
|
447
|
+
ExportPayrollParams,
|
|
448
|
+
|
|
449
|
+
// Results
|
|
450
|
+
ProcessSalaryResult,
|
|
451
|
+
BulkPayrollResult,
|
|
452
|
+
|
|
453
|
+
// Enums
|
|
454
|
+
EmployeeStatus, // 'active' | 'on_leave' | 'suspended' | 'terminated'
|
|
455
|
+
PayrollStatus, // 'pending' | 'processing' | 'paid' | 'failed' | 'voided' | 'reversed'
|
|
456
|
+
PayrollRunType, // 'regular' | 'supplemental' | 'retroactive' | 'off-cycle'
|
|
457
|
+
LeaveType, // 'annual' | 'sick' | 'unpaid' | 'maternity' | 'paternity'
|
|
458
|
+
AllowanceType, // 'housing' | 'transport' | 'meal' | 'mobile' | 'medical' | 'bonus' | 'other'
|
|
459
|
+
DeductionType, // 'tax' | 'loan' | 'advance' | 'provident_fund' | 'insurance'
|
|
460
|
+
PaymentFrequency, // 'monthly' | 'bi_weekly' | 'weekly' | 'daily' | 'hourly'
|
|
461
|
+
PaymentMethod, // 'bank' | 'cash' | 'check'
|
|
462
|
+
} from '@classytic/payroll';
|
|
463
|
+
```
|
|
464
|
+
|
|
465
|
+
---
|
|
466
|
+
|
|
467
|
+
## Schemas
|
|
468
|
+
|
|
469
|
+
```typescript
|
|
470
|
+
import {
|
|
471
|
+
createEmployeeSchema,
|
|
472
|
+
createPayrollRecordSchema,
|
|
473
|
+
employeeIndexes,
|
|
474
|
+
payrollRecordIndexes,
|
|
475
|
+
} from '@classytic/payroll/schemas';
|
|
476
|
+
|
|
477
|
+
// Create with custom fields
|
|
478
|
+
const employeeSchema = createEmployeeSchema({
|
|
479
|
+
skills: [String],
|
|
480
|
+
certifications: [{ name: String, date: Date }],
|
|
481
|
+
});
|
|
482
|
+
|
|
483
|
+
// Apply indexes
|
|
484
|
+
employeeIndexes.forEach(idx => employeeSchema.index(idx.fields, idx.options));
|
|
485
|
+
```
|
|
486
|
+
|
|
487
|
+
---
|
|
488
|
+
|
|
489
|
+
## Utilities
|
|
490
|
+
|
|
491
|
+
```typescript
|
|
492
|
+
import {
|
|
493
|
+
// Date
|
|
494
|
+
addDays, addMonths, diffInDays, startOfMonth, endOfMonth,
|
|
495
|
+
getPayPeriod, getWorkingDaysInMonth,
|
|
496
|
+
|
|
497
|
+
// Money (banker's rounding)
|
|
498
|
+
roundMoney, percentageOf, prorateAmount,
|
|
499
|
+
|
|
500
|
+
// Query builders
|
|
501
|
+
toObjectId, isValidObjectId,
|
|
502
|
+
} from '@classytic/payroll/utils';
|
|
503
|
+
```
|
|
504
|
+
|
|
505
|
+
---
|
|
506
|
+
|
|
507
|
+
## Error Handling
|
|
508
|
+
|
|
509
|
+
```typescript
|
|
510
|
+
import {
|
|
511
|
+
PayrollError,
|
|
512
|
+
DuplicatePayrollError,
|
|
513
|
+
EmployeeNotFoundError,
|
|
514
|
+
NotEligibleError,
|
|
515
|
+
ValidationError,
|
|
516
|
+
} from '@classytic/payroll';
|
|
517
|
+
|
|
518
|
+
try {
|
|
519
|
+
await payroll.processSalary({ organizationId, employeeId, month, year });
|
|
520
|
+
} catch (error) {
|
|
521
|
+
if (error instanceof DuplicatePayrollError) {
|
|
522
|
+
// Already processed for this period + run type
|
|
523
|
+
} else if (error instanceof EmployeeNotFoundError) {
|
|
524
|
+
// Employee doesn't exist
|
|
525
|
+
} else if (error instanceof NotEligibleError) {
|
|
526
|
+
// Employee not eligible (terminated, etc.)
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
```
|
|
530
|
+
|
|
531
|
+
---
|
|
532
|
+
|
|
533
|
+
## License
|
|
534
|
+
|
|
535
|
+
MIT
|