@classytic/payroll 2.0.0 → 2.3.0

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

Potentially problematic release.


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

@@ -0,0 +1,660 @@
1
+ /**
2
+ * @classytic/payroll - Multi-Jurisdiction Support
3
+ *
4
+ * DESIGN:
5
+ * - Pluggable jurisdiction rules (tax, overtime, leave, compliance)
6
+ * - Pure calculation functions
7
+ * - Smart defaults per country/state/province
8
+ * - Override at runtime when needed
9
+ *
10
+ * Your app passes jurisdiction code, we handle the calculations.
11
+ */
12
+ type JurisdictionLevel = 'country' | 'state' | 'province' | 'city' | 'custom';
13
+ interface JurisdictionIdentifier {
14
+ /** ISO country code (e.g., 'US', 'GB', 'BD', 'IN') */
15
+ country: string;
16
+ /** State/province code (e.g., 'CA', 'NY', 'ON') */
17
+ state?: string;
18
+ /** City/municipality code */
19
+ city?: string;
20
+ /** Custom jurisdiction identifier */
21
+ custom?: string;
22
+ }
23
+ interface TaxBracket {
24
+ min: number;
25
+ max: number;
26
+ rate: number;
27
+ /** Fixed amount for this bracket (some jurisdictions use fixed + %) */
28
+ fixedAmount?: number;
29
+ }
30
+ interface TaxConfiguration {
31
+ /** Income tax brackets (annual income) */
32
+ incomeTax: TaxBracket[];
33
+ /** Social security/pension tax rate */
34
+ socialSecurity?: {
35
+ employeeRate: number;
36
+ employerRate: number;
37
+ ceiling?: number;
38
+ floor?: number;
39
+ };
40
+ /** Medicare/health insurance tax */
41
+ medicare?: {
42
+ employeeRate: number;
43
+ employerRate: number;
44
+ additionalRate?: number;
45
+ additionalThreshold?: number;
46
+ };
47
+ /** Unemployment insurance */
48
+ unemployment?: {
49
+ employerRate: number;
50
+ ceiling?: number;
51
+ };
52
+ /** Other mandatory contributions */
53
+ otherContributions?: Array<{
54
+ name: string;
55
+ employeeRate?: number;
56
+ employerRate?: number;
57
+ ceiling?: number;
58
+ }>;
59
+ /** Tax-free allowances (annual) */
60
+ allowances?: {
61
+ personal: number;
62
+ dependent?: number;
63
+ pension?: number;
64
+ };
65
+ /** Standard deduction (annual) */
66
+ standardDeduction?: number;
67
+ }
68
+ interface TaxCalculationInput {
69
+ annualIncome: number;
70
+ jurisdiction: JurisdictionIdentifier;
71
+ dependents?: number;
72
+ customAllowances?: number;
73
+ pensionContribution?: number;
74
+ }
75
+ interface TaxCalculationResult {
76
+ /** Total income tax (annual) */
77
+ incomeTax: number;
78
+ /** Social security employee contribution (annual) */
79
+ socialSecurityEmployee: number;
80
+ /** Social security employer contribution (annual) */
81
+ socialSecurityEmployer: number;
82
+ /** Medicare employee (annual) */
83
+ medicareEmployee: number;
84
+ /** Medicare employer (annual) */
85
+ medicareEmployer: number;
86
+ /** Unemployment employer (annual) */
87
+ unemploymentEmployer: number;
88
+ /** Other contributions */
89
+ otherContributions: Array<{
90
+ name: string;
91
+ employeeAmount: number;
92
+ employerAmount: number;
93
+ }>;
94
+ /** Total employee tax burden (annual) */
95
+ totalEmployeeTax: number;
96
+ /** Total employer tax burden (annual) */
97
+ totalEmployerTax: number;
98
+ /** Effective tax rate */
99
+ effectiveRate: number;
100
+ /** Taxable income after allowances/deductions */
101
+ taxableIncome: number;
102
+ }
103
+ type OvertimeCalculationBasis = 'daily' | 'weekly' | 'biweekly' | 'monthly';
104
+ interface OvertimeRule {
105
+ /** Hours threshold before overtime kicks in */
106
+ threshold: number;
107
+ /** Overtime multiplier (1.5 = time and a half) */
108
+ multiplier: number;
109
+ /** Basis for calculation */
110
+ basis: OvertimeCalculationBasis;
111
+ /** Maximum daily hours before double time */
112
+ doubleTimeThreshold?: number;
113
+ /** Double time multiplier */
114
+ doubleTimeMultiplier?: number;
115
+ }
116
+ interface OvertimeConfiguration {
117
+ /** Standard overtime rules */
118
+ standard: OvertimeRule;
119
+ /** Weekend overtime (if different) */
120
+ weekend?: OvertimeRule;
121
+ /** Holiday overtime */
122
+ holiday?: OvertimeRule;
123
+ /** Night shift differential */
124
+ nightShift?: {
125
+ startHour: number;
126
+ endHour: number;
127
+ multiplier: number;
128
+ };
129
+ /** Consecutive days worked bonus */
130
+ consecutiveDays?: {
131
+ threshold: number;
132
+ multiplier: number;
133
+ };
134
+ }
135
+ interface LeaveEntitlement {
136
+ /** Annual leave days per year */
137
+ annualLeave: {
138
+ days: number;
139
+ /** How it accrues */
140
+ accrual: 'monthly' | 'quarterly' | 'annual' | 'per-hour';
141
+ /** Accrual rate (days per period) */
142
+ accrualRate?: number;
143
+ /** Can it be carried forward */
144
+ carryForward: boolean;
145
+ /** Max carry forward days */
146
+ maxCarryForward?: number;
147
+ };
148
+ /** Sick leave */
149
+ sickLeave: {
150
+ days: number;
151
+ accrual: 'monthly' | 'quarterly' | 'annual' | 'unlimited';
152
+ accrualRate?: number;
153
+ /** Requires medical certificate after X days */
154
+ medicalCertificateAfter?: number;
155
+ };
156
+ /** Maternity leave */
157
+ maternityLeave?: {
158
+ days: number;
159
+ paidDays: number;
160
+ /** Percentage of salary paid */
161
+ paidPercentage: number;
162
+ };
163
+ /** Paternity leave */
164
+ paternityLeave?: {
165
+ days: number;
166
+ paidDays: number;
167
+ paidPercentage: number;
168
+ };
169
+ /** Public holidays (annual count) */
170
+ publicHolidays: number;
171
+ /** Bereavement leave */
172
+ bereavementLeave?: {
173
+ days: number;
174
+ paidDays: number;
175
+ };
176
+ /** Other statutory leaves */
177
+ otherLeaves?: Array<{
178
+ name: string;
179
+ days: number;
180
+ paidDays: number;
181
+ paidPercentage: number;
182
+ }>;
183
+ }
184
+ interface ComplianceRule {
185
+ /** Rule identifier */
186
+ id: string;
187
+ /** Rule name */
188
+ name: string;
189
+ /** Rule category */
190
+ category: 'wage' | 'hours' | 'leave' | 'termination' | 'benefits' | 'safety' | 'other';
191
+ /** Validation function */
192
+ validate: (data: EmploymentData) => ComplianceViolation[];
193
+ }
194
+ interface ComplianceViolation {
195
+ ruleId: string;
196
+ ruleName: string;
197
+ severity: 'critical' | 'high' | 'medium' | 'low';
198
+ message: string;
199
+ /** Suggested remediation */
200
+ remediation?: string;
201
+ /** Monetary penalty (if applicable) */
202
+ penalty?: number;
203
+ }
204
+ interface EmploymentData {
205
+ baseSalary: number;
206
+ currency: string;
207
+ hoursWorked: number;
208
+ overtimeHours: number;
209
+ leaveBalance: {
210
+ annual: number;
211
+ sick: number;
212
+ };
213
+ hireDate: Date;
214
+ terminationDate?: Date;
215
+ [key: string]: any;
216
+ }
217
+ interface WageConfiguration {
218
+ /** Minimum wage (hourly) */
219
+ minimumWage: {
220
+ amount: number;
221
+ effectiveDate: Date;
222
+ };
223
+ /** Living wage (recommended, not mandated) */
224
+ livingWage?: {
225
+ amount: number;
226
+ effectiveDate: Date;
227
+ };
228
+ /** Pay frequency requirements */
229
+ payFrequency: {
230
+ allowed: Array<'daily' | 'weekly' | 'biweekly' | 'semimonthly' | 'monthly'>;
231
+ default: 'weekly' | 'biweekly' | 'semimonthly' | 'monthly';
232
+ };
233
+ /** Salary payment deadline */
234
+ paymentDeadline: {
235
+ /** Days after period end */
236
+ daysAfterPeriod: number;
237
+ };
238
+ /** Probation period rules */
239
+ probation?: {
240
+ maxDays: number;
241
+ /** Can salary be lower during probation */
242
+ allowReducedSalary: boolean;
243
+ /** Termination notice during probation */
244
+ terminationNoticeDays: number;
245
+ };
246
+ }
247
+ interface WorkingHoursConfiguration {
248
+ /** Standard work week */
249
+ standardWeek: {
250
+ hours: number;
251
+ days: number;
252
+ };
253
+ /** Maximum daily hours */
254
+ maxDailyHours: number;
255
+ /** Maximum weekly hours */
256
+ maxWeeklyHours: number;
257
+ /** Required breaks */
258
+ breaks: Array<{
259
+ afterHours: number;
260
+ durationMinutes: number;
261
+ paid: boolean;
262
+ }>;
263
+ /** Rest period between shifts */
264
+ restBetweenShifts: {
265
+ hours: number;
266
+ };
267
+ /** Weekly rest day requirement */
268
+ weeklyRestDays: {
269
+ days: number;
270
+ consecutive: boolean;
271
+ };
272
+ }
273
+ interface JurisdictionDefinition {
274
+ /** Jurisdiction identifier */
275
+ id: string;
276
+ /** Display name */
277
+ name: string;
278
+ /** Level (country, state, etc) */
279
+ level: JurisdictionLevel;
280
+ /** Parent jurisdiction (e.g., US for California) */
281
+ parent?: string;
282
+ /** Currency code */
283
+ currency: string;
284
+ /** Locale code */
285
+ locale: string;
286
+ /** Effective date */
287
+ effectiveFrom: Date;
288
+ /** End date (if jurisdiction rules changed) */
289
+ effectiveTo?: Date;
290
+ tax: TaxConfiguration;
291
+ overtime: OvertimeConfiguration;
292
+ leave: LeaveEntitlement;
293
+ wage: WageConfiguration;
294
+ workingHours: WorkingHoursConfiguration;
295
+ complianceRules: ComplianceRule[];
296
+ metadata?: {
297
+ /** Government authority */
298
+ authority?: string;
299
+ /** Reference laws */
300
+ laws?: string[];
301
+ /** Last updated */
302
+ lastUpdated: Date;
303
+ /** Maintained by */
304
+ maintainer?: string;
305
+ };
306
+ }
307
+ interface PaySlipData {
308
+ employee: {
309
+ id: string;
310
+ name: string;
311
+ position: string;
312
+ department?: string;
313
+ hireDate: Date;
314
+ taxId?: string;
315
+ socialSecurityNumber?: string;
316
+ };
317
+ employer: {
318
+ name: string;
319
+ address: string;
320
+ taxId?: string;
321
+ registrationNumber?: string;
322
+ };
323
+ period: {
324
+ start: Date;
325
+ end: Date;
326
+ payDate: Date;
327
+ };
328
+ earnings: {
329
+ basic: number;
330
+ allowances: Array<{
331
+ name: string;
332
+ amount: number;
333
+ taxable: boolean;
334
+ }>;
335
+ overtime: number;
336
+ bonuses: Array<{
337
+ name: string;
338
+ amount: number;
339
+ taxable: boolean;
340
+ }>;
341
+ other: Array<{
342
+ name: string;
343
+ amount: number;
344
+ taxable: boolean;
345
+ }>;
346
+ };
347
+ deductions: {
348
+ incomeTax: number;
349
+ socialSecurity: number;
350
+ medicare?: number;
351
+ providentFund?: number;
352
+ loans: Array<{
353
+ name: string;
354
+ amount: number;
355
+ }>;
356
+ advances: number;
357
+ other: Array<{
358
+ name: string;
359
+ amount: number;
360
+ }>;
361
+ };
362
+ totals: {
363
+ grossEarnings: number;
364
+ totalDeductions: number;
365
+ netPay: number;
366
+ };
367
+ yearToDate: {
368
+ grossEarnings: number;
369
+ incomeTax: number;
370
+ socialSecurity: number;
371
+ netPay: number;
372
+ };
373
+ bankDetails?: {
374
+ accountNumber: string;
375
+ bankName: string;
376
+ routingNumber?: string;
377
+ };
378
+ }
379
+ interface PaySlipTemplate {
380
+ jurisdiction: JurisdictionIdentifier;
381
+ format: 'pdf' | 'html' | 'json' | 'custom';
382
+ /** Required fields per jurisdiction */
383
+ requiredFields: string[];
384
+ /** Optional fields */
385
+ optionalFields?: string[];
386
+ /** Custom template function */
387
+ customRenderer?: (data: PaySlipData) => any;
388
+ }
389
+ interface StatutoryFilingRequirement {
390
+ /** Filing type */
391
+ type: 'monthly' | 'quarterly' | 'annual' | 'event-based';
392
+ /** Filing name */
393
+ name: string;
394
+ /** Due date calculation */
395
+ dueDate: (period: {
396
+ start: Date;
397
+ end: Date;
398
+ }) => Date;
399
+ /** Required data */
400
+ requiredData: string[];
401
+ /** Generate filing data */
402
+ generateData: (employees: any[], period: {
403
+ start: Date;
404
+ end: Date;
405
+ }) => any;
406
+ /** Validation rules */
407
+ validate?: (data: any) => ComplianceViolation[];
408
+ }
409
+ interface FilingCalendar {
410
+ jurisdiction: JurisdictionIdentifier;
411
+ filings: StatutoryFilingRequirement[];
412
+ }
413
+
414
+ /**
415
+ * @classytic/payroll - Jurisdiction Registry
416
+ *
417
+ * Pluggable jurisdiction system. Register custom jurisdictions at runtime.
418
+ */
419
+
420
+ declare class JurisdictionRegistry {
421
+ private jurisdictions;
422
+ /**
423
+ * Register a jurisdiction
424
+ */
425
+ register(jurisdiction: JurisdictionDefinition): void;
426
+ /**
427
+ * Register multiple jurisdictions
428
+ */
429
+ registerMany(jurisdictions: JurisdictionDefinition[]): void;
430
+ /**
431
+ * Get jurisdiction by identifier
432
+ */
433
+ get(identifier: JurisdictionIdentifier): JurisdictionDefinition | undefined;
434
+ /**
435
+ * Get jurisdiction with fallback to parent
436
+ */
437
+ getWithFallback(identifier: JurisdictionIdentifier): JurisdictionDefinition | undefined;
438
+ /**
439
+ * Check if jurisdiction is registered
440
+ */
441
+ has(identifier: JurisdictionIdentifier): boolean;
442
+ /**
443
+ * Get all jurisdictions for a country
444
+ */
445
+ getByCountry(countryCode: string): JurisdictionDefinition[];
446
+ /**
447
+ * Get all registered jurisdictions
448
+ */
449
+ getAll(): JurisdictionDefinition[];
450
+ /**
451
+ * Remove a jurisdiction
452
+ */
453
+ unregister(identifier: JurisdictionIdentifier): boolean;
454
+ /**
455
+ * Clear all jurisdictions
456
+ */
457
+ clear(): void;
458
+ /**
459
+ * Get registry size
460
+ */
461
+ size(): number;
462
+ private makeKey;
463
+ private makeKeyFromId;
464
+ private makeKeyFromIdentifier;
465
+ private validate;
466
+ }
467
+ declare const jurisdictionRegistry: JurisdictionRegistry;
468
+ /**
469
+ * Register a jurisdiction
470
+ */
471
+ declare function registerJurisdiction(jurisdiction: JurisdictionDefinition): void;
472
+ /**
473
+ * Register multiple jurisdictions
474
+ */
475
+ declare function registerJurisdictions(jurisdictions: JurisdictionDefinition[]): void;
476
+ /**
477
+ * Get jurisdiction
478
+ */
479
+ declare function getJurisdiction(identifier: JurisdictionIdentifier): JurisdictionDefinition | undefined;
480
+ /**
481
+ * Get jurisdiction (throws if not found)
482
+ */
483
+ declare function requireJurisdiction(identifier: JurisdictionIdentifier): JurisdictionDefinition;
484
+ /**
485
+ * Check if jurisdiction exists
486
+ */
487
+ declare function hasJurisdiction(identifier: JurisdictionIdentifier): boolean;
488
+ /**
489
+ * Get all jurisdictions for a country
490
+ */
491
+ declare function getJurisdictionsByCountry(countryCode: string): JurisdictionDefinition[];
492
+
493
+ /**
494
+ * @classytic/payroll - Jurisdiction-Aware Tax Calculator
495
+ *
496
+ * Pure functions for calculating taxes using jurisdiction-specific rules.
497
+ */
498
+
499
+ /**
500
+ * Calculate comprehensive tax for an employee
501
+ *
502
+ * @example
503
+ * ```typescript
504
+ * const result = calculateJurisdictionTax({
505
+ * annualIncome: 100000,
506
+ * jurisdiction: { country: 'US', state: 'CA' },
507
+ * dependents: 2,
508
+ * });
509
+ *
510
+ * console.log(result.totalEmployeeTax); // Total tax burden
511
+ * console.log(result.effectiveRate); // Effective tax rate
512
+ * ```
513
+ */
514
+ declare function calculateJurisdictionTax(input: TaxCalculationInput): TaxCalculationResult;
515
+ /**
516
+ * Calculate monthly tax (convenience wrapper)
517
+ */
518
+ declare function calculateMonthlyTax(monthlyIncome: number, jurisdiction: JurisdictionIdentifier, options?: {
519
+ dependents?: number;
520
+ customAllowances?: number;
521
+ pensionContribution?: number;
522
+ }): {
523
+ monthlyIncomeTax: number;
524
+ monthlySocialSecurity: number;
525
+ monthlyMedicare: number;
526
+ monthlyTotal: number;
527
+ effectiveRate: number;
528
+ };
529
+ /**
530
+ * Compare tax burden across multiple jurisdictions
531
+ */
532
+ declare function compareTaxBurden(annualIncome: number, jurisdictions: JurisdictionIdentifier[]): Array<{
533
+ jurisdiction: JurisdictionIdentifier;
534
+ jurisdictionName: string;
535
+ result: TaxCalculationResult;
536
+ }>;
537
+
538
+ /**
539
+ * @classytic/payroll - Labor Law Compliance Checker
540
+ *
541
+ * Validates employment data against jurisdiction-specific labor laws.
542
+ */
543
+
544
+ /**
545
+ * Check compliance for an employee
546
+ *
547
+ * @example
548
+ * ```typescript
549
+ * const violations = checkCompliance({
550
+ * baseSalary: 2500,
551
+ * currency: 'USD',
552
+ * hoursWorked: 45,
553
+ * jurisdiction: { country: 'US', state: 'CA' },
554
+ * });
555
+ *
556
+ * if (violations.length > 0) {
557
+ * console.log('Compliance issues found:', violations);
558
+ * }
559
+ * ```
560
+ */
561
+ declare function checkCompliance(data: EmploymentData, jurisdiction: JurisdictionIdentifier): ComplianceViolation[];
562
+ /**
563
+ * Check multiple employees for compliance
564
+ */
565
+ declare function checkBulkCompliance(employees: Array<EmploymentData & {
566
+ jurisdiction: JurisdictionIdentifier;
567
+ }>, options?: {
568
+ severityFilter?: ComplianceViolation['severity'][];
569
+ categoryFilter?: string[];
570
+ }): Array<{
571
+ employee: EmploymentData;
572
+ violations: ComplianceViolation[];
573
+ }>;
574
+ /**
575
+ * Generate compliance report for an organization
576
+ */
577
+ declare function generateComplianceReport(employees: Array<EmploymentData & {
578
+ jurisdiction: JurisdictionIdentifier;
579
+ id: string;
580
+ name: string;
581
+ }>, options?: {
582
+ includeCompliant?: boolean;
583
+ groupByJurisdiction?: boolean;
584
+ }): {
585
+ summary: {
586
+ totalEmployees: number;
587
+ compliantEmployees: number;
588
+ violationCount: number;
589
+ criticalViolations: number;
590
+ highViolations: number;
591
+ mediumViolations: number;
592
+ lowViolations: number;
593
+ };
594
+ violations: Array<{
595
+ employeeId: string;
596
+ employeeName: string;
597
+ jurisdiction: string;
598
+ violations: ComplianceViolation[];
599
+ }>;
600
+ byJurisdiction?: Map<string, {
601
+ employeeCount: number;
602
+ violationCount: number;
603
+ violations: ComplianceViolation[];
604
+ }>;
605
+ };
606
+
607
+ /**
608
+ * Create a jurisdiction definition with type safety
609
+ *
610
+ * @example
611
+ * ```typescript
612
+ * const myJurisdiction = createJurisdictionDefinition({
613
+ * id: 'US',
614
+ * name: 'United States',
615
+ * level: 'country',
616
+ * currency: 'USD',
617
+ * locale: 'en-US',
618
+ * effectiveFrom: new Date('2024-01-01'),
619
+ * tax: { ... },
620
+ * overtime: { ... },
621
+ * leave: { ... },
622
+ * wage: { ... },
623
+ * workingHours: { ... },
624
+ * complianceRules: [],
625
+ * });
626
+ *
627
+ * registerJurisdiction(myJurisdiction);
628
+ * ```
629
+ */
630
+ declare function createJurisdictionDefinition(definition: JurisdictionDefinition): JurisdictionDefinition;
631
+ /**
632
+ * Helper to extend an existing jurisdiction (e.g., create a state from a country)
633
+ *
634
+ * @example
635
+ * ```typescript
636
+ * import { extendJurisdiction } from '@classytic/payroll/jurisdiction';
637
+ *
638
+ * // First get your base country jurisdiction (from your data)
639
+ * const usFederal = getJurisdiction({ country: 'US' });
640
+ *
641
+ * // Extend it for a state
642
+ * const california = extendJurisdiction(usFederal!, {
643
+ * id: 'US:CA',
644
+ * name: 'California',
645
+ * parent: 'US',
646
+ * level: 'state',
647
+ * wage: {
648
+ * minimumWage: { amount: 16, effectiveDate: new Date('2024-01-01') },
649
+ * },
650
+ * });
651
+ *
652
+ * registerJurisdiction(california);
653
+ * ```
654
+ */
655
+ declare function extendJurisdiction(base: JurisdictionDefinition, overrides: Partial<JurisdictionDefinition> & {
656
+ id: string;
657
+ name: string;
658
+ }): JurisdictionDefinition;
659
+
660
+ export { type ComplianceRule, type ComplianceViolation, type EmploymentData, type FilingCalendar, type JurisdictionDefinition, type JurisdictionIdentifier, type JurisdictionLevel, type LeaveEntitlement, type OvertimeCalculationBasis, type OvertimeConfiguration, type OvertimeRule, type PaySlipData, type PaySlipTemplate, type StatutoryFilingRequirement, type TaxBracket, type TaxCalculationInput, type TaxCalculationResult, type TaxConfiguration, type WageConfiguration, type WorkingHoursConfiguration, calculateJurisdictionTax, calculateMonthlyTax, checkBulkCompliance, checkCompliance, compareTaxBurden, createJurisdictionDefinition, extendJurisdiction, generateComplianceReport, getJurisdiction, getJurisdictionsByCountry, hasJurisdiction, jurisdictionRegistry, registerJurisdiction, registerJurisdictions, requireJurisdiction };