@classytic/payroll 1.0.2 → 2.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


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

Files changed (78) hide show
  1. package/README.md +2599 -574
  2. package/dist/calculators/index.d.ts +433 -0
  3. package/dist/calculators/index.js +283 -0
  4. package/dist/calculators/index.js.map +1 -0
  5. package/dist/core/index.d.ts +314 -0
  6. package/dist/core/index.js +1166 -0
  7. package/dist/core/index.js.map +1 -0
  8. package/dist/employee-identity-DXhgOgXE.d.ts +473 -0
  9. package/dist/employee.factory-BlZqhiCk.d.ts +189 -0
  10. package/dist/idempotency-Cw2CWicb.d.ts +52 -0
  11. package/dist/index.d.ts +902 -0
  12. package/dist/index.js +9108 -0
  13. package/dist/index.js.map +1 -0
  14. package/dist/jurisdiction/index.d.ts +660 -0
  15. package/dist/jurisdiction/index.js +533 -0
  16. package/dist/jurisdiction/index.js.map +1 -0
  17. package/dist/payroll.d.ts +429 -0
  18. package/dist/payroll.js +5192 -0
  19. package/dist/payroll.js.map +1 -0
  20. package/dist/schemas/index.d.ts +3262 -0
  21. package/dist/schemas/index.js +780 -0
  22. package/dist/schemas/index.js.map +1 -0
  23. package/dist/services/index.d.ts +582 -0
  24. package/dist/services/index.js +2172 -0
  25. package/dist/services/index.js.map +1 -0
  26. package/dist/shift-compliance/index.d.ts +1171 -0
  27. package/dist/shift-compliance/index.js +1479 -0
  28. package/dist/shift-compliance/index.js.map +1 -0
  29. package/dist/types-BN3K_Uhr.d.ts +1842 -0
  30. package/dist/utils/index.d.ts +893 -0
  31. package/dist/utils/index.js +1515 -0
  32. package/dist/utils/index.js.map +1 -0
  33. package/package.json +72 -37
  34. package/dist/types/config.d.ts +0 -162
  35. package/dist/types/core/compensation.manager.d.ts +0 -54
  36. package/dist/types/core/employment.manager.d.ts +0 -49
  37. package/dist/types/core/payroll.manager.d.ts +0 -60
  38. package/dist/types/enums.d.ts +0 -117
  39. package/dist/types/factories/compensation.factory.d.ts +0 -196
  40. package/dist/types/factories/employee.factory.d.ts +0 -149
  41. package/dist/types/factories/payroll.factory.d.ts +0 -319
  42. package/dist/types/hrm.orchestrator.d.ts +0 -47
  43. package/dist/types/index.d.ts +0 -20
  44. package/dist/types/init.d.ts +0 -30
  45. package/dist/types/models/payroll-record.model.d.ts +0 -3
  46. package/dist/types/plugins/employee.plugin.d.ts +0 -2
  47. package/dist/types/schemas/employment.schema.d.ts +0 -959
  48. package/dist/types/services/compensation.service.d.ts +0 -94
  49. package/dist/types/services/employee.service.d.ts +0 -28
  50. package/dist/types/services/payroll.service.d.ts +0 -30
  51. package/dist/types/utils/calculation.utils.d.ts +0 -26
  52. package/dist/types/utils/date.utils.d.ts +0 -35
  53. package/dist/types/utils/logger.d.ts +0 -12
  54. package/dist/types/utils/query-builders.d.ts +0 -83
  55. package/dist/types/utils/validation.utils.d.ts +0 -33
  56. package/payroll.d.ts +0 -241
  57. package/src/config.js +0 -177
  58. package/src/core/compensation.manager.js +0 -242
  59. package/src/core/employment.manager.js +0 -224
  60. package/src/core/payroll.manager.js +0 -499
  61. package/src/enums.js +0 -141
  62. package/src/factories/compensation.factory.js +0 -198
  63. package/src/factories/employee.factory.js +0 -173
  64. package/src/factories/payroll.factory.js +0 -413
  65. package/src/hrm.orchestrator.js +0 -139
  66. package/src/index.js +0 -172
  67. package/src/init.js +0 -62
  68. package/src/models/payroll-record.model.js +0 -126
  69. package/src/plugins/employee.plugin.js +0 -164
  70. package/src/schemas/employment.schema.js +0 -126
  71. package/src/services/compensation.service.js +0 -231
  72. package/src/services/employee.service.js +0 -162
  73. package/src/services/payroll.service.js +0 -213
  74. package/src/utils/calculation.utils.js +0 -91
  75. package/src/utils/date.utils.js +0 -120
  76. package/src/utils/logger.js +0 -36
  77. package/src/utils/query-builders.js +0 -185
  78. package/src/utils/validation.utils.js +0 -122
@@ -0,0 +1,1166 @@
1
+ import { LRUCache } from 'lru-cache';
2
+
3
+ // src/core/result.ts
4
+ function ok(value) {
5
+ return { ok: true, value };
6
+ }
7
+ function err(error) {
8
+ return { ok: false, error };
9
+ }
10
+ function isOk(result) {
11
+ return result.ok === true;
12
+ }
13
+ function isErr(result) {
14
+ return result.ok === false;
15
+ }
16
+ function unwrap(result) {
17
+ if (isOk(result)) {
18
+ return result.value;
19
+ }
20
+ throw result.error;
21
+ }
22
+ function unwrapOr(result, defaultValue) {
23
+ if (isOk(result)) {
24
+ return result.value;
25
+ }
26
+ return defaultValue;
27
+ }
28
+ function unwrapOrElse(result, fn) {
29
+ if (isOk(result)) {
30
+ return result.value;
31
+ }
32
+ return fn(result.error);
33
+ }
34
+ function map(result, fn) {
35
+ if (isOk(result)) {
36
+ return ok(fn(result.value));
37
+ }
38
+ return result;
39
+ }
40
+ function mapErr(result, fn) {
41
+ if (isErr(result)) {
42
+ return err(fn(result.error));
43
+ }
44
+ return result;
45
+ }
46
+ function flatMap(result, fn) {
47
+ if (isOk(result)) {
48
+ return fn(result.value);
49
+ }
50
+ return result;
51
+ }
52
+ async function tryCatch(fn, errorTransform) {
53
+ try {
54
+ const value = await fn();
55
+ return ok(value);
56
+ } catch (error) {
57
+ if (errorTransform) {
58
+ return err(errorTransform(error));
59
+ }
60
+ return err(error);
61
+ }
62
+ }
63
+ function tryCatchSync(fn, errorTransform) {
64
+ try {
65
+ const value = fn();
66
+ return ok(value);
67
+ } catch (error) {
68
+ if (errorTransform) {
69
+ return err(errorTransform(error));
70
+ }
71
+ return err(error);
72
+ }
73
+ }
74
+ function all(results) {
75
+ const values = [];
76
+ for (const result of results) {
77
+ if (isErr(result)) {
78
+ return result;
79
+ }
80
+ values.push(result.value);
81
+ }
82
+ return ok(values);
83
+ }
84
+ function match(result, handlers) {
85
+ if (isOk(result)) {
86
+ return handlers.ok(result.value);
87
+ }
88
+ return handlers.err(result.error);
89
+ }
90
+ async function fromPromise(promise, errorTransform) {
91
+ return tryCatch(() => promise, errorTransform);
92
+ }
93
+ function fromNullable(value, error) {
94
+ if (value === null || value === void 0) {
95
+ return err(error);
96
+ }
97
+ return ok(value);
98
+ }
99
+ var ResultClass = class _ResultClass {
100
+ constructor(result) {
101
+ this.result = result;
102
+ }
103
+ static ok(value) {
104
+ return new _ResultClass(ok(value));
105
+ }
106
+ static err(error) {
107
+ return new _ResultClass(err(error));
108
+ }
109
+ static async fromAsync(fn, errorTransform) {
110
+ const result = await tryCatch(fn, errorTransform);
111
+ return new _ResultClass(result);
112
+ }
113
+ isOk() {
114
+ return isOk(this.result);
115
+ }
116
+ isErr() {
117
+ return isErr(this.result);
118
+ }
119
+ unwrap() {
120
+ return unwrap(this.result);
121
+ }
122
+ unwrapOr(defaultValue) {
123
+ return unwrapOr(this.result, defaultValue);
124
+ }
125
+ map(fn) {
126
+ return new _ResultClass(map(this.result, fn));
127
+ }
128
+ mapErr(fn) {
129
+ return new _ResultClass(mapErr(this.result, fn));
130
+ }
131
+ flatMap(fn) {
132
+ return new _ResultClass(flatMap(this.result, fn));
133
+ }
134
+ match(handlers) {
135
+ return match(this.result, handlers);
136
+ }
137
+ toResult() {
138
+ return this.result;
139
+ }
140
+ };
141
+ var Result = {
142
+ ok,
143
+ err,
144
+ isOk,
145
+ isErr,
146
+ unwrap,
147
+ unwrapOr,
148
+ unwrapOrElse,
149
+ map,
150
+ mapErr,
151
+ flatMap,
152
+ tryCatch,
153
+ tryCatchSync,
154
+ all,
155
+ match,
156
+ fromPromise,
157
+ fromNullable
158
+ };
159
+
160
+ // src/core/events.ts
161
+ var EventBus = class {
162
+ handlers = /* @__PURE__ */ new Map();
163
+ /**
164
+ * Register an event handler
165
+ */
166
+ on(event, handler) {
167
+ if (!this.handlers.has(event)) {
168
+ this.handlers.set(event, /* @__PURE__ */ new Set());
169
+ }
170
+ this.handlers.get(event).add(handler);
171
+ return () => this.off(event, handler);
172
+ }
173
+ /**
174
+ * Register a one-time event handler
175
+ */
176
+ once(event, handler) {
177
+ const wrappedHandler = async (payload) => {
178
+ this.off(event, wrappedHandler);
179
+ await handler(payload);
180
+ };
181
+ return this.on(event, wrappedHandler);
182
+ }
183
+ /**
184
+ * Remove an event handler
185
+ */
186
+ off(event, handler) {
187
+ const eventHandlers = this.handlers.get(event);
188
+ if (eventHandlers) {
189
+ eventHandlers.delete(handler);
190
+ }
191
+ }
192
+ /**
193
+ * Emit an event
194
+ */
195
+ async emit(event, payload) {
196
+ const eventHandlers = this.handlers.get(event);
197
+ if (!eventHandlers || eventHandlers.size === 0) {
198
+ return;
199
+ }
200
+ const handlers = Array.from(eventHandlers);
201
+ await Promise.all(
202
+ handlers.map(async (handler) => {
203
+ try {
204
+ await handler(payload);
205
+ } catch (error) {
206
+ console.error(`Event handler error for ${event}:`, error);
207
+ }
208
+ })
209
+ );
210
+ }
211
+ /**
212
+ * Emit event synchronously (fire-and-forget)
213
+ */
214
+ emitSync(event, payload) {
215
+ void this.emit(event, payload);
216
+ }
217
+ /**
218
+ * Remove all handlers for an event
219
+ */
220
+ removeAllListeners(event) {
221
+ if (event) {
222
+ this.handlers.delete(event);
223
+ } else {
224
+ this.handlers.clear();
225
+ }
226
+ }
227
+ /**
228
+ * Get listener count for an event
229
+ */
230
+ listenerCount(event) {
231
+ return this.handlers.get(event)?.size ?? 0;
232
+ }
233
+ /**
234
+ * Get all registered events
235
+ */
236
+ eventNames() {
237
+ return Array.from(this.handlers.keys());
238
+ }
239
+ };
240
+ var defaultEventBus = null;
241
+ function getEventBus() {
242
+ if (!defaultEventBus) {
243
+ defaultEventBus = new EventBus();
244
+ }
245
+ return defaultEventBus;
246
+ }
247
+ function createEventBus() {
248
+ return new EventBus();
249
+ }
250
+ function resetEventBus() {
251
+ if (defaultEventBus) {
252
+ defaultEventBus.removeAllListeners();
253
+ }
254
+ defaultEventBus = null;
255
+ }
256
+ function onEmployeeHired(handler) {
257
+ return getEventBus().on("employee:hired", handler);
258
+ }
259
+ function onSalaryProcessed(handler) {
260
+ return getEventBus().on("salary:processed", handler);
261
+ }
262
+ function onPayrollCompleted(handler) {
263
+ return getEventBus().on("payroll:completed", handler);
264
+ }
265
+ function onMilestoneAchieved(handler) {
266
+ return getEventBus().on("milestone:achieved", handler);
267
+ }
268
+ var IdempotencyManager = class {
269
+ cache;
270
+ constructor(options = {}) {
271
+ this.cache = new LRUCache({
272
+ max: options.max || 1e4,
273
+ // Store 10k keys
274
+ ttl: options.ttl || 1e3 * 60 * 60 * 24
275
+ // 24 hours default
276
+ });
277
+ }
278
+ /**
279
+ * Check if key exists and return cached result
280
+ */
281
+ get(key) {
282
+ const cached = this.cache.get(key);
283
+ if (!cached) return null;
284
+ return {
285
+ value: cached.value,
286
+ cached: true,
287
+ createdAt: cached.createdAt
288
+ };
289
+ }
290
+ /**
291
+ * Store result for idempotency key
292
+ */
293
+ set(key, value) {
294
+ this.cache.set(key, {
295
+ value,
296
+ createdAt: /* @__PURE__ */ new Date()
297
+ });
298
+ }
299
+ /**
300
+ * Execute function with idempotency protection
301
+ */
302
+ async execute(key, fn) {
303
+ const cached = this.get(key);
304
+ if (cached) {
305
+ return cached;
306
+ }
307
+ const value = await fn();
308
+ this.set(key, value);
309
+ return {
310
+ value,
311
+ cached: false,
312
+ createdAt: /* @__PURE__ */ new Date()
313
+ };
314
+ }
315
+ /**
316
+ * Clear a specific key
317
+ */
318
+ delete(key) {
319
+ this.cache.delete(key);
320
+ }
321
+ /**
322
+ * Clear all keys
323
+ */
324
+ clear() {
325
+ this.cache.clear();
326
+ }
327
+ /**
328
+ * Get cache stats
329
+ */
330
+ stats() {
331
+ return {
332
+ size: this.cache.size,
333
+ max: this.cache.max
334
+ };
335
+ }
336
+ };
337
+ function generatePayrollIdempotencyKey(organizationId, employeeId, month, year) {
338
+ return `payroll:${organizationId}:${employeeId}:${year}-${month}`;
339
+ }
340
+
341
+ // src/core/webhooks.ts
342
+ var WebhookManager = class {
343
+ webhooks = [];
344
+ deliveryLog = [];
345
+ /**
346
+ * Register a webhook
347
+ */
348
+ register(config) {
349
+ this.webhooks.push({
350
+ retries: 3,
351
+ timeout: 3e4,
352
+ ...config
353
+ });
354
+ }
355
+ /**
356
+ * Remove a webhook
357
+ */
358
+ unregister(url) {
359
+ this.webhooks = this.webhooks.filter((w) => w.url !== url);
360
+ }
361
+ /**
362
+ * Send webhook for event
363
+ */
364
+ async send(event, payload) {
365
+ const matchingWebhooks = this.webhooks.filter((w) => w.events.includes(event));
366
+ const deliveries = matchingWebhooks.map(
367
+ (webhook) => this.deliver(webhook, event, payload)
368
+ );
369
+ await Promise.allSettled(deliveries);
370
+ }
371
+ /**
372
+ * Deliver webhook with retries
373
+ */
374
+ async deliver(webhook, event, payload) {
375
+ const deliveryId = `${Date.now()}-${Math.random().toString(36)}`;
376
+ const delivery = {
377
+ id: deliveryId,
378
+ event,
379
+ url: webhook.url,
380
+ payload,
381
+ attempt: 0,
382
+ status: "pending"
383
+ };
384
+ this.deliveryLog.push(delivery);
385
+ const maxRetries = webhook.retries || 3;
386
+ for (let attempt = 1; attempt <= maxRetries; attempt++) {
387
+ delivery.attempt = attempt;
388
+ try {
389
+ const controller = new AbortController();
390
+ const timeout = setTimeout(() => controller.abort(), webhook.timeout || 3e4);
391
+ const headers = {
392
+ "Content-Type": "application/json",
393
+ "X-Payroll-Event": event,
394
+ "X-Payroll-Delivery": deliveryId,
395
+ ...webhook.headers
396
+ };
397
+ if (webhook.secret) {
398
+ headers["X-Payroll-Signature"] = this.generateSignature(payload, webhook.secret);
399
+ }
400
+ const response = await fetch(webhook.url, {
401
+ method: "POST",
402
+ headers,
403
+ body: JSON.stringify({
404
+ event,
405
+ payload,
406
+ deliveredAt: (/* @__PURE__ */ new Date()).toISOString()
407
+ }),
408
+ signal: controller.signal
409
+ });
410
+ clearTimeout(timeout);
411
+ delivery.response = {
412
+ status: response.status,
413
+ body: await response.text()
414
+ };
415
+ delivery.sentAt = /* @__PURE__ */ new Date();
416
+ if (response.ok) {
417
+ delivery.status = "sent";
418
+ return delivery;
419
+ }
420
+ if (response.status >= 500 && attempt < maxRetries) {
421
+ await this.sleep(Math.pow(2, attempt) * 1e3);
422
+ continue;
423
+ }
424
+ delivery.status = "failed";
425
+ delivery.error = `HTTP ${response.status}`;
426
+ return delivery;
427
+ } catch (error) {
428
+ delivery.error = error.message;
429
+ if (attempt < maxRetries) {
430
+ await this.sleep(Math.pow(2, attempt) * 1e3);
431
+ continue;
432
+ }
433
+ delivery.status = "failed";
434
+ return delivery;
435
+ }
436
+ }
437
+ return delivery;
438
+ }
439
+ /**
440
+ * Generate HMAC signature for webhook
441
+ */
442
+ generateSignature(payload, secret) {
443
+ const data = JSON.stringify(payload);
444
+ return Buffer.from(`${secret}:${data}`).toString("base64");
445
+ }
446
+ /**
447
+ * Sleep for ms
448
+ */
449
+ sleep(ms) {
450
+ return new Promise((resolve) => setTimeout(resolve, ms));
451
+ }
452
+ /**
453
+ * Get delivery log
454
+ */
455
+ getDeliveries(options) {
456
+ let results = this.deliveryLog;
457
+ if (options?.event) {
458
+ results = results.filter((d) => d.event === options.event);
459
+ }
460
+ if (options?.status) {
461
+ results = results.filter((d) => d.status === options.status);
462
+ }
463
+ if (options?.limit) {
464
+ results = results.slice(-options.limit);
465
+ }
466
+ return results;
467
+ }
468
+ /**
469
+ * Clear delivery log
470
+ */
471
+ clearLog() {
472
+ this.deliveryLog = [];
473
+ }
474
+ /**
475
+ * Get all registered webhooks
476
+ */
477
+ getWebhooks() {
478
+ return [...this.webhooks];
479
+ }
480
+ };
481
+
482
+ // src/core/plugin.ts
483
+ var PluginManager = class {
484
+ constructor(context) {
485
+ this.context = context;
486
+ }
487
+ plugins = /* @__PURE__ */ new Map();
488
+ hooks = /* @__PURE__ */ new Map();
489
+ /**
490
+ * Register a plugin
491
+ */
492
+ async register(plugin) {
493
+ if (this.plugins.has(plugin.name)) {
494
+ throw new Error(`Plugin "${plugin.name}" is already registered`);
495
+ }
496
+ if (plugin.hooks) {
497
+ for (const [hookName, handler] of Object.entries(plugin.hooks)) {
498
+ if (handler) {
499
+ this.addHook(hookName, handler);
500
+ }
501
+ }
502
+ }
503
+ if (plugin.init) {
504
+ await plugin.init(this.context);
505
+ }
506
+ this.plugins.set(plugin.name, plugin);
507
+ this.context.logger.debug(`Plugin "${plugin.name}" registered`);
508
+ }
509
+ /**
510
+ * Unregister a plugin
511
+ */
512
+ async unregister(name) {
513
+ const plugin = this.plugins.get(name);
514
+ if (!plugin) {
515
+ return;
516
+ }
517
+ if (plugin.destroy) {
518
+ await plugin.destroy();
519
+ }
520
+ this.plugins.delete(name);
521
+ this.context.logger.debug(`Plugin "${name}" unregistered`);
522
+ }
523
+ /**
524
+ * Add a hook handler
525
+ */
526
+ addHook(hookName, handler) {
527
+ if (!this.hooks.has(hookName)) {
528
+ this.hooks.set(hookName, []);
529
+ }
530
+ this.hooks.get(hookName).push(handler);
531
+ }
532
+ /**
533
+ * Execute hooks for a given event
534
+ */
535
+ async executeHooks(hookName, ...args) {
536
+ const handlers = this.hooks.get(hookName);
537
+ if (!handlers || handlers.length === 0) {
538
+ return;
539
+ }
540
+ for (const handler of handlers) {
541
+ try {
542
+ await handler(...args);
543
+ } catch (error) {
544
+ this.context.logger.error(`Hook "${hookName}" error:`, { error });
545
+ const errorHandlers = this.hooks.get("onError");
546
+ if (errorHandlers) {
547
+ for (const errorHandler of errorHandlers) {
548
+ try {
549
+ await errorHandler(error, hookName);
550
+ } catch {
551
+ }
552
+ }
553
+ }
554
+ }
555
+ }
556
+ }
557
+ /**
558
+ * Get registered plugin names
559
+ */
560
+ getPluginNames() {
561
+ return Array.from(this.plugins.keys());
562
+ }
563
+ /**
564
+ * Check if plugin is registered
565
+ */
566
+ hasPlugin(name) {
567
+ return this.plugins.has(name);
568
+ }
569
+ };
570
+ function definePlugin(definition) {
571
+ return definition;
572
+ }
573
+ var loggingPlugin = definePlugin({
574
+ name: "logging",
575
+ version: "1.0.0",
576
+ init: (context) => {
577
+ context.addHook("employee:hired", (payload) => {
578
+ context.logger.info("Employee hired", {
579
+ employeeId: payload.employee.employeeId,
580
+ position: payload.employee.position
581
+ });
582
+ });
583
+ context.addHook("salary:processed", (payload) => {
584
+ context.logger.info("Salary processed", {
585
+ employeeId: payload.employee.employeeId,
586
+ amount: payload.payroll.netAmount,
587
+ period: payload.payroll.period
588
+ });
589
+ });
590
+ context.addHook("employee:terminated", (payload) => {
591
+ context.logger.info("Employee terminated", {
592
+ employeeId: payload.employee.employeeId,
593
+ reason: payload.reason
594
+ });
595
+ });
596
+ },
597
+ hooks: {
598
+ onError: (error, context) => {
599
+ console.error(`[Payroll Error] ${context}:`, error.message);
600
+ }
601
+ }
602
+ });
603
+ var metricsPlugin = definePlugin({
604
+ name: "metrics",
605
+ version: "1.0.0",
606
+ init: (context) => {
607
+ const metrics = {
608
+ employeesHired: 0,
609
+ employeesTerminated: 0,
610
+ salariesProcessed: 0,
611
+ totalPaid: 0,
612
+ errors: 0
613
+ };
614
+ context.addHook("employee:hired", () => {
615
+ metrics.employeesHired++;
616
+ });
617
+ context.addHook("employee:terminated", () => {
618
+ metrics.employeesTerminated++;
619
+ });
620
+ context.addHook("salary:processed", (payload) => {
621
+ metrics.salariesProcessed++;
622
+ metrics.totalPaid += payload.payroll.netAmount;
623
+ });
624
+ context.payroll.metrics = metrics;
625
+ },
626
+ hooks: {
627
+ onError: (error, context) => {
628
+ }
629
+ }
630
+ });
631
+ function createNotificationPlugin(options) {
632
+ return definePlugin({
633
+ name: "notification",
634
+ version: "1.0.0",
635
+ init: (context) => {
636
+ if (options.onHired) {
637
+ context.addHook("employee:hired", async (payload) => {
638
+ await options.onHired({
639
+ id: payload.employee.id,
640
+ name: payload.employee.position
641
+ });
642
+ });
643
+ }
644
+ if (options.onTerminated) {
645
+ context.addHook("employee:terminated", async (payload) => {
646
+ await options.onTerminated({
647
+ id: payload.employee.id,
648
+ name: payload.employee.name
649
+ });
650
+ });
651
+ }
652
+ if (options.onSalaryProcessed) {
653
+ context.addHook("salary:processed", async (payload) => {
654
+ await options.onSalaryProcessed({
655
+ employee: {
656
+ id: payload.employee.id,
657
+ name: payload.employee.name
658
+ },
659
+ amount: payload.payroll.netAmount
660
+ });
661
+ });
662
+ }
663
+ if (options.onMilestone) {
664
+ context.addHook("milestone:achieved", async (payload) => {
665
+ await options.onMilestone({
666
+ employee: {
667
+ id: payload.employee.id,
668
+ name: payload.employee.name
669
+ },
670
+ milestone: payload.milestone.message
671
+ });
672
+ });
673
+ }
674
+ }
675
+ });
676
+ }
677
+ var notificationPlugin = createNotificationPlugin({});
678
+
679
+ // src/utils/logger.ts
680
+ var createConsoleLogger = () => ({
681
+ info: (message, meta) => {
682
+ if (meta) {
683
+ console.log(`[Payroll] INFO: ${message}`, meta);
684
+ } else {
685
+ console.log(`[Payroll] INFO: ${message}`);
686
+ }
687
+ },
688
+ error: (message, meta) => {
689
+ if (meta) {
690
+ console.error(`[Payroll] ERROR: ${message}`, meta);
691
+ } else {
692
+ console.error(`[Payroll] ERROR: ${message}`);
693
+ }
694
+ },
695
+ warn: (message, meta) => {
696
+ if (meta) {
697
+ console.warn(`[Payroll] WARN: ${message}`, meta);
698
+ } else {
699
+ console.warn(`[Payroll] WARN: ${message}`);
700
+ }
701
+ },
702
+ debug: (message, meta) => {
703
+ if (process.env.NODE_ENV !== "production") {
704
+ if (meta) {
705
+ console.log(`[Payroll] DEBUG: ${message}`, meta);
706
+ } else {
707
+ console.log(`[Payroll] DEBUG: ${message}`);
708
+ }
709
+ }
710
+ }
711
+ });
712
+ var currentLogger = createConsoleLogger();
713
+ function getLogger() {
714
+ return currentLogger;
715
+ }
716
+
717
+ // src/config.ts
718
+ var HRM_CONFIG = {
719
+ dataRetention: {
720
+ payrollRecordsTTL: 63072e3,
721
+ // 2 years in seconds
722
+ exportWarningDays: 30,
723
+ archiveBeforeDeletion: true
724
+ },
725
+ payroll: {
726
+ defaultCurrency: "BDT",
727
+ allowProRating: true,
728
+ attendanceIntegration: true,
729
+ autoDeductions: true,
730
+ overtimeEnabled: false,
731
+ overtimeMultiplier: 1.5
732
+ },
733
+ salary: {
734
+ minimumWage: 0,
735
+ maximumAllowances: 10,
736
+ maximumDeductions: 10,
737
+ defaultFrequency: "monthly"
738
+ },
739
+ employment: {
740
+ defaultProbationMonths: 3,
741
+ maxProbationMonths: 6,
742
+ allowReHiring: true,
743
+ trackEmploymentHistory: true
744
+ },
745
+ validation: {
746
+ requireBankDetails: false,
747
+ requireUserId: false,
748
+ // Modern: Allow guest employees by default
749
+ identityMode: "employeeId",
750
+ // Modern: Use human-readable IDs as primary
751
+ identityFallbacks: ["email", "userId"]
752
+ // Smart fallback chain
753
+ }
754
+ };
755
+ var ORG_ROLES = {
756
+ OWNER: {
757
+ key: "owner",
758
+ label: "Owner",
759
+ description: "Full organization access (set by Organization model)"
760
+ },
761
+ MANAGER: {
762
+ key: "manager",
763
+ label: "Manager",
764
+ description: "Management and administrative features"
765
+ },
766
+ TRAINER: {
767
+ key: "trainer",
768
+ label: "Trainer",
769
+ description: "Training and coaching features"
770
+ },
771
+ STAFF: {
772
+ key: "staff",
773
+ label: "Staff",
774
+ description: "General staff access to basic features"
775
+ },
776
+ INTERN: {
777
+ key: "intern",
778
+ label: "Intern",
779
+ description: "Limited access for interns"
780
+ },
781
+ CONSULTANT: {
782
+ key: "consultant",
783
+ label: "Consultant",
784
+ description: "Project-based consultant access"
785
+ }
786
+ };
787
+ Object.values(ORG_ROLES).map((role) => role.key);
788
+ function mergeConfig(customConfig) {
789
+ if (!customConfig) return HRM_CONFIG;
790
+ return {
791
+ dataRetention: { ...HRM_CONFIG.dataRetention, ...customConfig.dataRetention },
792
+ payroll: { ...HRM_CONFIG.payroll, ...customConfig.payroll },
793
+ salary: { ...HRM_CONFIG.salary, ...customConfig.salary },
794
+ employment: { ...HRM_CONFIG.employment, ...customConfig.employment },
795
+ validation: {
796
+ ...HRM_CONFIG.validation,
797
+ ...customConfig.validation,
798
+ // Ensure fallbacks is always EmployeeIdentityMode[]
799
+ identityFallbacks: customConfig.validation?.identityFallbacks ?? HRM_CONFIG.validation.identityFallbacks
800
+ }
801
+ };
802
+ }
803
+
804
+ // src/core/container.ts
805
+ var Container = class {
806
+ _models = null;
807
+ _config = HRM_CONFIG;
808
+ _singleTenant = null;
809
+ _logger;
810
+ _initialized = false;
811
+ constructor() {
812
+ this._logger = getLogger();
813
+ }
814
+ /**
815
+ * Initialize container with configuration
816
+ */
817
+ initialize(config) {
818
+ if (this._initialized) {
819
+ this._logger.warn("Container already initialized, re-initializing");
820
+ }
821
+ this._models = config.models;
822
+ this._config = mergeConfig(config.config);
823
+ this._singleTenant = config.singleTenant ?? null;
824
+ if (config.logger) {
825
+ this._logger = config.logger;
826
+ }
827
+ this._initialized = true;
828
+ this._logger.info("Container initialized", {
829
+ hasEmployeeModel: !!this._models.EmployeeModel,
830
+ hasPayrollRecordModel: !!this._models.PayrollRecordModel,
831
+ hasTransactionModel: !!this._models.TransactionModel,
832
+ hasAttendanceModel: !!this._models.AttendanceModel,
833
+ hasLeaveRequestModel: !!this._models.LeaveRequestModel,
834
+ hasTaxWithholdingModel: !!this._models.TaxWithholdingModel,
835
+ isSingleTenant: !!this._singleTenant
836
+ });
837
+ }
838
+ /**
839
+ * Check if container is initialized
840
+ */
841
+ isInitialized() {
842
+ return this._initialized;
843
+ }
844
+ /**
845
+ * Reset container (useful for testing)
846
+ */
847
+ reset() {
848
+ this._models = null;
849
+ this._config = HRM_CONFIG;
850
+ this._singleTenant = null;
851
+ this._initialized = false;
852
+ this._logger.info("Container reset");
853
+ }
854
+ /**
855
+ * Ensure container is initialized
856
+ */
857
+ ensureInitialized() {
858
+ if (!this._initialized || !this._models) {
859
+ throw new Error(
860
+ "Payroll not initialized. Call Payroll.initialize() first."
861
+ );
862
+ }
863
+ }
864
+ /**
865
+ * Get models container (strongly typed)
866
+ */
867
+ getModels() {
868
+ this.ensureInitialized();
869
+ return this._models;
870
+ }
871
+ /**
872
+ * Get Employee model (strongly typed)
873
+ */
874
+ getEmployeeModel() {
875
+ this.ensureInitialized();
876
+ return this._models.EmployeeModel;
877
+ }
878
+ /**
879
+ * Get PayrollRecord model (strongly typed)
880
+ */
881
+ getPayrollRecordModel() {
882
+ this.ensureInitialized();
883
+ return this._models.PayrollRecordModel;
884
+ }
885
+ /**
886
+ * Get Transaction model (strongly typed)
887
+ */
888
+ getTransactionModel() {
889
+ this.ensureInitialized();
890
+ return this._models.TransactionModel;
891
+ }
892
+ /**
893
+ * Get Attendance model (optional, strongly typed)
894
+ */
895
+ getAttendanceModel() {
896
+ this.ensureInitialized();
897
+ return this._models.AttendanceModel ?? null;
898
+ }
899
+ /**
900
+ * Get LeaveRequest model (optional, strongly typed)
901
+ */
902
+ getLeaveRequestModel() {
903
+ this.ensureInitialized();
904
+ return this._models.LeaveRequestModel ?? null;
905
+ }
906
+ /**
907
+ * Get TaxWithholding model (optional, strongly typed)
908
+ */
909
+ getTaxWithholdingModel() {
910
+ this.ensureInitialized();
911
+ return this._models.TaxWithholdingModel ?? null;
912
+ }
913
+ /**
914
+ * Get configuration
915
+ */
916
+ getConfig() {
917
+ return this._config;
918
+ }
919
+ /**
920
+ * Get specific config section
921
+ */
922
+ getConfigSection(section) {
923
+ return this._config[section];
924
+ }
925
+ /**
926
+ * Check if single-tenant mode
927
+ */
928
+ isSingleTenant() {
929
+ return !!this._singleTenant;
930
+ }
931
+ /**
932
+ * Get single-tenant config
933
+ */
934
+ getSingleTenantConfig() {
935
+ return this._singleTenant;
936
+ }
937
+ /**
938
+ * Get organization ID (for single-tenant mode)
939
+ */
940
+ getOrganizationId() {
941
+ if (!this._singleTenant || !this._singleTenant.organizationId) return null;
942
+ return typeof this._singleTenant.organizationId === "string" ? this._singleTenant.organizationId : this._singleTenant.organizationId.toString();
943
+ }
944
+ /**
945
+ * Get logger
946
+ */
947
+ getLogger() {
948
+ return this._logger;
949
+ }
950
+ /**
951
+ * Set logger
952
+ */
953
+ setLogger(logger) {
954
+ this._logger = logger;
955
+ }
956
+ /**
957
+ * Has attendance integration
958
+ */
959
+ hasAttendanceIntegration() {
960
+ return !!this._models?.AttendanceModel && this._config.payroll.attendanceIntegration;
961
+ }
962
+ /**
963
+ * Create operation context with defaults
964
+ */
965
+ createOperationContext(overrides) {
966
+ const context = {};
967
+ if (this._singleTenant?.autoInject && !overrides?.organizationId) {
968
+ context.organizationId = this.getOrganizationId();
969
+ }
970
+ return { ...context, ...overrides };
971
+ }
972
+ };
973
+ var defaultContainer = null;
974
+ function getContainer() {
975
+ if (!defaultContainer) {
976
+ defaultContainer = new Container();
977
+ }
978
+ return defaultContainer;
979
+ }
980
+ function initializeContainer(config) {
981
+ getContainer().initialize(config);
982
+ }
983
+ function isContainerInitialized() {
984
+ return defaultContainer?.isInitialized() ?? false;
985
+ }
986
+ function getModels() {
987
+ return getContainer().getModels();
988
+ }
989
+ function getConfig() {
990
+ return getContainer().getConfig();
991
+ }
992
+ function isSingleTenant() {
993
+ return getContainer().isSingleTenant();
994
+ }
995
+
996
+ // src/core/config.ts
997
+ var DEFAULT_TAX_BRACKETS = [
998
+ { min: 0, max: 1e4, rate: 0.1 },
999
+ { min: 1e4, max: 4e4, rate: 0.12 },
1000
+ { min: 4e4, max: 85e3, rate: 0.22 },
1001
+ { min: 85e3, max: 165e3, rate: 0.24 },
1002
+ { min: 165e3, max: 215e3, rate: 0.32 },
1003
+ { min: 215e3, max: 54e4, rate: 0.35 },
1004
+ { min: 54e4, max: Infinity, rate: 0.37 }
1005
+ ];
1006
+ var DEFAULT_WORK_SCHEDULE = {
1007
+ workingDays: [1, 2, 3, 4, 5],
1008
+ // Monday to Friday
1009
+ hoursPerDay: 8
1010
+ };
1011
+ function countWorkingDays(startDate, endDate, options = {}) {
1012
+ const workDays = options.workingDays || DEFAULT_WORK_SCHEDULE.workingDays;
1013
+ const holidaySet = new Set(
1014
+ (options.holidays || []).map((d) => new Date(d).toDateString())
1015
+ );
1016
+ let totalDays = 0;
1017
+ let workingDays = 0;
1018
+ let holidays = 0;
1019
+ let weekends = 0;
1020
+ const current = new Date(startDate);
1021
+ current.setHours(0, 0, 0, 0);
1022
+ const end = new Date(endDate);
1023
+ end.setHours(0, 0, 0, 0);
1024
+ while (current <= end) {
1025
+ totalDays++;
1026
+ const isHoliday = holidaySet.has(current.toDateString());
1027
+ const isWorkDay = workDays.includes(current.getDay());
1028
+ if (isHoliday) {
1029
+ holidays++;
1030
+ } else if (isWorkDay) {
1031
+ workingDays++;
1032
+ } else {
1033
+ weekends++;
1034
+ }
1035
+ current.setDate(current.getDate() + 1);
1036
+ }
1037
+ return { totalDays, workingDays, weekends, holidays };
1038
+ }
1039
+ function calculateProration(hireDate, terminationDate, periodStart, periodEnd) {
1040
+ const hire = new Date(hireDate);
1041
+ hire.setHours(0, 0, 0, 0);
1042
+ const term = terminationDate ? new Date(terminationDate) : null;
1043
+ if (term) term.setHours(0, 0, 0, 0);
1044
+ const start = new Date(periodStart);
1045
+ start.setHours(0, 0, 0, 0);
1046
+ const end = new Date(periodEnd);
1047
+ end.setHours(0, 0, 0, 0);
1048
+ if (hire > end || term && term < start) {
1049
+ return { ratio: 0, reason: "full", isProrated: true };
1050
+ }
1051
+ const effectiveStart = hire > start ? hire : start;
1052
+ const effectiveEnd = term && term < end ? term : end;
1053
+ const totalDays = Math.ceil((end.getTime() - start.getTime()) / 864e5) + 1;
1054
+ const actualDays = Math.ceil((effectiveEnd.getTime() - effectiveStart.getTime()) / 864e5) + 1;
1055
+ const ratio = Math.min(1, Math.max(0, actualDays / totalDays));
1056
+ const isNewHire = hire > start;
1057
+ const isTermination = term !== null && term < end;
1058
+ let reason = "full";
1059
+ if (isNewHire && isTermination) {
1060
+ reason = "both";
1061
+ } else if (isNewHire) {
1062
+ reason = "new_hire";
1063
+ } else if (isTermination) {
1064
+ reason = "termination";
1065
+ }
1066
+ return { ratio, reason, isProrated: ratio < 1 };
1067
+ }
1068
+ function calculateSimpleTax(monthlyIncome, brackets = DEFAULT_TAX_BRACKETS) {
1069
+ const annualIncome = monthlyIncome * 12;
1070
+ let annualTax = 0;
1071
+ for (const bracket of brackets) {
1072
+ if (annualIncome <= bracket.min) continue;
1073
+ const taxableInBracket = Math.min(annualIncome, bracket.max) - bracket.min;
1074
+ annualTax += taxableInBracket * bracket.rate;
1075
+ }
1076
+ const monthlyTax = Math.round(annualTax / 12);
1077
+ const effectiveRate = monthlyIncome > 0 ? monthlyTax / monthlyIncome : 0;
1078
+ return { amount: monthlyTax, effectiveRate };
1079
+ }
1080
+ function calculateAttendanceDeduction(expectedDays, actualDays, dailyRate, maxDeductionPercent = 100) {
1081
+ const absentDays = Math.max(0, expectedDays - actualDays);
1082
+ const deduction = Math.round(absentDays * dailyRate);
1083
+ const maxDeduction = Math.round(dailyRate * expectedDays * maxDeductionPercent / 100);
1084
+ return Math.min(deduction, maxDeduction);
1085
+ }
1086
+ function calculateSalaryBreakdown(params) {
1087
+ const {
1088
+ baseSalary,
1089
+ hireDate,
1090
+ terminationDate,
1091
+ periodStart,
1092
+ periodEnd,
1093
+ allowances = [],
1094
+ deductions = [],
1095
+ options = {},
1096
+ attendance
1097
+ } = params;
1098
+ const workSchedule = { ...DEFAULT_WORK_SCHEDULE, ...options.workSchedule };
1099
+ const workingDays = countWorkingDays(periodStart, periodEnd, {
1100
+ workingDays: workSchedule.workingDays,
1101
+ holidays: options.holidays
1102
+ });
1103
+ const proration = options.skipProration ? { ratio: 1, reason: "full", isProrated: false } : calculateProration(hireDate, terminationDate, periodStart, periodEnd);
1104
+ const proratedBase = Math.round(baseSalary * proration.ratio);
1105
+ const processedAllowances = allowances.map((a) => ({
1106
+ type: a.type,
1107
+ amount: Math.round(a.amount * proration.ratio),
1108
+ taxable: a.taxable ?? true
1109
+ }));
1110
+ const totalAllowances = processedAllowances.reduce((sum, a) => sum + a.amount, 0);
1111
+ const processedDeductions = deductions.map((d) => ({
1112
+ type: d.type,
1113
+ amount: Math.round(d.amount * proration.ratio)
1114
+ }));
1115
+ let attendanceDeduction = 0;
1116
+ if (attendance && !options.skipAttendance && workingDays.workingDays > 0) {
1117
+ const dailyRate = proratedBase / workingDays.workingDays;
1118
+ attendanceDeduction = calculateAttendanceDeduction(
1119
+ attendance.expectedDays,
1120
+ attendance.actualDays,
1121
+ dailyRate
1122
+ );
1123
+ if (attendanceDeduction > 0) {
1124
+ processedDeductions.push({ type: "attendance", amount: attendanceDeduction });
1125
+ }
1126
+ }
1127
+ const grossSalary = proratedBase + totalAllowances;
1128
+ let taxAmount = 0;
1129
+ if (!options.skipTax) {
1130
+ const taxableAllowances = processedAllowances.filter((a) => a.taxable).reduce((sum, a) => sum + a.amount, 0);
1131
+ const taxableIncome = proratedBase + taxableAllowances;
1132
+ const taxResult = calculateSimpleTax(taxableIncome);
1133
+ taxAmount = taxResult.amount;
1134
+ if (taxAmount > 0) {
1135
+ processedDeductions.push({ type: "tax", amount: taxAmount });
1136
+ }
1137
+ }
1138
+ const totalDeductions = processedDeductions.filter((d) => d.type !== "tax").reduce((sum, d) => sum + d.amount, 0);
1139
+ const netSalary = grossSalary - totalDeductions - taxAmount;
1140
+ return {
1141
+ baseSalary,
1142
+ proratedBase,
1143
+ totalAllowances,
1144
+ totalDeductions,
1145
+ attendanceDeduction,
1146
+ grossSalary,
1147
+ taxAmount,
1148
+ netSalary,
1149
+ proration,
1150
+ workingDays,
1151
+ breakdown: {
1152
+ allowances: processedAllowances,
1153
+ deductions: processedDeductions
1154
+ }
1155
+ };
1156
+ }
1157
+ function getPayPeriod(month, year, payDay = 28) {
1158
+ const startDate = new Date(year, month - 1, 1);
1159
+ const endDate = new Date(year, month, 0);
1160
+ const payDate = new Date(year, month - 1, Math.min(payDay, endDate.getDate()));
1161
+ return { startDate, endDate, payDate };
1162
+ }
1163
+
1164
+ export { Container, DEFAULT_TAX_BRACKETS, DEFAULT_WORK_SCHEDULE, EventBus, IdempotencyManager, PluginManager, Result, ResultClass, WebhookManager, all, calculateAttendanceDeduction, calculateProration, calculateSalaryBreakdown, countWorkingDays, createEventBus, createNotificationPlugin, definePlugin, err, flatMap, fromNullable, fromPromise, generatePayrollIdempotencyKey, getConfig, getContainer, getEventBus, getModels, getPayPeriod, initializeContainer, isContainerInitialized, isErr, isOk, isSingleTenant, loggingPlugin, map, mapErr, match, metricsPlugin, notificationPlugin, ok, onEmployeeHired, onMilestoneAchieved, onPayrollCompleted, onSalaryProcessed, resetEventBus, tryCatch, tryCatchSync, unwrap, unwrapOr, unwrapOrElse };
1165
+ //# sourceMappingURL=index.js.map
1166
+ //# sourceMappingURL=index.js.map