@classytic/payroll 1.0.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Sadman Chowdhury
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,574 @@
1
+ # 🎯 HRM Library - Human Resource Management
2
+
3
+ Modern, flexible, production-ready HRM system following Stripe/Passport.js architecture patterns.
4
+
5
+ ## 🌟 Key Features
6
+
7
+ ### Multi-Tenant Support
8
+ - Same user can be employee in multiple organizations
9
+ - Complete data isolation per tenant
10
+ - Re-hiring support with full employment history
11
+
12
+ ### Smart Payroll
13
+ - **Pro-rated calculations** (mid-month hires)
14
+ - **Attendance integration** (unpaid leave auto-deduction)
15
+ - **Automatic deductions** (loans, advances, tax)
16
+ - **Bulk payroll processing**
17
+ - **Transaction integration** (seamless with existing system)
18
+
19
+ ### Data Retention
20
+ - **Auto-deletion**: PayrollRecords expire after 2 years (MongoDB TTL)
21
+ - **Export before deletion**: Required export for compliance
22
+ - **Configurable retention**: Adjust via `HRM_CONFIG`
23
+
24
+ ### Flexible Architecture
25
+ - **Reusable schemas**: Merge with your custom fields
26
+ - **Plugin system**: Adds methods, virtuals, indexes
27
+ - **Clean DSL**: `hrm.hire()`, `hrm.processSalary()`, `hrm.terminate()`
28
+ - **Dependency injection**: Models injected at bootstrap
29
+
30
+ ## 📁 Structure
31
+
32
+ ```
33
+ lib/hrm/
34
+ ├── index.js # Public exports
35
+ ├── init.js # Bootstrap initialization
36
+ ├── hrm.orchestrator.js # Clean API (Stripe-like)
37
+ ├── enums.js # Single source of truth
38
+ ├── config.js # Configurable settings
39
+
40
+ ├── models/
41
+ │ └── payroll-record.model.js # Universal payroll ledger
42
+
43
+ ├── schemas/
44
+ │ └── employment.schema.js # Reusable mongoose schemas
45
+
46
+ ├── plugins/
47
+ │ └── employee.plugin.js # Mongoose plugin (methods/virtuals)
48
+
49
+ ├── core/ # Domain business logic
50
+ │ ├── employment.manager.js # Hire/terminate operations
51
+ │ ├── compensation.manager.js # Salary/allowance operations
52
+ │ └── payroll.manager.js # Payroll processing
53
+
54
+ ├── factories/ # Clean object creation
55
+ │ ├── employee.factory.js # Employee creation with defaults
56
+ │ ├── payroll.factory.js # Payroll generation
57
+ │ └── compensation.factory.js # Compensation breakdown
58
+
59
+ ├── services/ # High-level operations
60
+ │ ├── employee.service.js # Employee CRUD + queries
61
+ │ ├── payroll.service.js # Batch payroll processing
62
+ │ └── compensation.service.js # Compensation calculations
63
+
64
+ └── utils/ # Pure, reusable functions
65
+ ├── date.utils.js # Date calculations
66
+ ├── calculation.utils.js # Salary calculations
67
+ ├── validation.utils.js # Validators
68
+ └── query-builders.js # Fluent query API
69
+ ```
70
+
71
+ ## 🚀 Quick Start
72
+
73
+ ### 1. Create Your Employee Model
74
+
75
+ ```javascript
76
+ // modules/employee/employee.model.js
77
+ import mongoose from 'mongoose';
78
+ import { employmentFields, employeePlugin } from '#lib/hrm/index.js';
79
+
80
+ const employeeSchema = new mongoose.Schema({
81
+ // Core HRM fields (required)
82
+ ...employmentFields,
83
+
84
+ // Your custom fields
85
+ certifications: [{ name: String, issuedDate: Date }],
86
+ specializations: [String],
87
+ emergencyContact: { name: String, phone: String },
88
+ // ... any other fields you need
89
+ });
90
+
91
+ // Apply HRM plugin (adds methods, virtuals, indexes)
92
+ employeeSchema.plugin(employeePlugin);
93
+
94
+ export default mongoose.model('Employee', employeeSchema);
95
+ ```
96
+
97
+ ### 2. Bootstrap Integration
98
+
99
+ ```javascript
100
+ // bootstrap/hrm.js
101
+ import { initializeHRM } from '#lib/hrm/index.js';
102
+ import Employee from '../modules/employee/employee.model.js';
103
+ import PayrollRecord from '#lib/hrm/models/payroll-record.model.js';
104
+ import Transaction from '../modules/transaction/transaction.model.js';
105
+ import Attendance from '#lib/attendance/models/attendance.model.js';
106
+
107
+ export async function loadHRM() {
108
+ initializeHRM({
109
+ EmployeeModel: Employee,
110
+ PayrollRecordModel: PayrollRecord,
111
+ TransactionModel: Transaction,
112
+ AttendanceModel: Attendance, // Optional
113
+ });
114
+ }
115
+ ```
116
+
117
+ ### 3. Use the HRM API
118
+
119
+ ```javascript
120
+ import { hrm } from '#lib/hrm/index.js';
121
+
122
+ // Hire employee
123
+ const employee = await hrm.hire({
124
+ organizationId,
125
+ userId,
126
+ employment: {
127
+ employeeId: 'EMP-001',
128
+ type: 'full_time',
129
+ department: 'training',
130
+ position: 'Senior Trainer',
131
+ hireDate: new Date(),
132
+ },
133
+ compensation: {
134
+ baseAmount: 50000,
135
+ frequency: 'monthly',
136
+ allowances: [
137
+ { type: 'housing', amount: 10000 },
138
+ { type: 'transport', amount: 5000 }
139
+ ]
140
+ },
141
+ bankDetails: {
142
+ accountName: 'John Doe',
143
+ accountNumber: '1234567890',
144
+ bankName: 'Example Bank'
145
+ },
146
+ context: { userId: hrManagerId }
147
+ });
148
+
149
+ // Process salary (creates Transaction automatically)
150
+ const result = await hrm.processSalary({
151
+ employeeId: employee._id,
152
+ month: 11,
153
+ year: 2025,
154
+ paymentDate: new Date(),
155
+ paymentMethod: 'bank',
156
+ context: { userId: hrManagerId }
157
+ });
158
+
159
+ // Bulk payroll (all active employees)
160
+ const results = await hrm.processBulkPayroll({
161
+ organizationId,
162
+ month: 11,
163
+ year: 2025,
164
+ context: { userId: hrManagerId }
165
+ });
166
+ ```
167
+
168
+
169
+ ## 🎨 Complete API Reference
170
+
171
+ ### Employment Lifecycle
172
+
173
+ ```javascript
174
+ // Hire
175
+ await hrm.hire({ organizationId, userId, employment, compensation, bankDetails, context });
176
+
177
+ // Update employment details
178
+ await hrm.updateEmployment({ employeeId, updates: { department: 'management' }, context });
179
+
180
+ // Terminate
181
+ await hrm.terminate({ employeeId, terminationDate, reason: 'resignation', notes, context });
182
+
183
+ // Re-hire (same employee, new stint)
184
+ await hrm.reHire({ employeeId, hireDate, position, compensation, context });
185
+
186
+ // List employees
187
+ await hrm.listEmployees({
188
+ organizationId,
189
+ filters: { status: 'active', department: 'training', minSalary: 40000 },
190
+ pagination: { page: 1, limit: 20 }
191
+ });
192
+
193
+ // Get single employee
194
+ await hrm.getEmployee({ employeeId, populateUser: true });
195
+ ```
196
+
197
+ ### Compensation Management
198
+
199
+ ```javascript
200
+ // Update salary
201
+ await hrm.updateSalary({
202
+ employeeId,
203
+ compensation: { baseAmount: 60000 },
204
+ effectiveFrom: new Date(),
205
+ context
206
+ });
207
+
208
+ // Add allowance
209
+ await hrm.addAllowance({
210
+ employeeId,
211
+ type: 'meal',
212
+ amount: 3000,
213
+ taxable: true,
214
+ recurring: true,
215
+ context
216
+ });
217
+
218
+ // Remove allowance
219
+ await hrm.removeAllowance({ employeeId, type: 'meal', context });
220
+
221
+ // Add deduction
222
+ await hrm.addDeduction({
223
+ employeeId,
224
+ type: 'loan',
225
+ amount: 5000,
226
+ auto: true, // Auto-deduct from salary
227
+ description: 'Personal loan repayment',
228
+ context
229
+ });
230
+
231
+ // Remove deduction
232
+ await hrm.removeDeduction({ employeeId, type: 'loan', context });
233
+
234
+ // Update bank details
235
+ await hrm.updateBankDetails({
236
+ employeeId,
237
+ bankDetails: { accountNumber: '9876543210', bankName: 'New Bank' },
238
+ context
239
+ });
240
+ ```
241
+
242
+ ### Payroll Processing
243
+
244
+ ```javascript
245
+ // Process single salary
246
+ await hrm.processSalary({
247
+ employeeId,
248
+ month: 11,
249
+ year: 2025,
250
+ paymentDate: new Date(),
251
+ paymentMethod: 'bank',
252
+ context
253
+ });
254
+
255
+ // Bulk payroll
256
+ await hrm.processBulkPayroll({
257
+ organizationId,
258
+ month: 11,
259
+ year: 2025,
260
+ employeeIds: [], // Empty = all active employees
261
+ paymentDate: new Date(),
262
+ paymentMethod: 'bank',
263
+ context
264
+ });
265
+
266
+ // Payroll history
267
+ await hrm.payrollHistory({
268
+ employeeId,
269
+ organizationId,
270
+ month: 11,
271
+ year: 2025,
272
+ status: 'paid',
273
+ pagination: { page: 1, limit: 20 }
274
+ });
275
+
276
+ // Payroll summary
277
+ await hrm.payrollSummary({
278
+ organizationId,
279
+ month: 11,
280
+ year: 2025
281
+ });
282
+
283
+ // Export payroll data (before auto-deletion)
284
+ const records = await hrm.exportPayroll({
285
+ organizationId,
286
+ startDate: new Date('2023-01-01'),
287
+ endDate: new Date('2023-12-31'),
288
+ format: 'json'
289
+ });
290
+ ```
291
+
292
+ ## 📊 Data Models
293
+
294
+ ### Employee (Your Model + HRM Fields)
295
+
296
+ ```javascript
297
+ {
298
+ // Identity & tenant
299
+ userId: ObjectId, // Links to User
300
+ organizationId: ObjectId, // Multi-tenant isolation
301
+ employeeId: "EMP-001", // Custom ID (unique per org)
302
+
303
+ // Employment
304
+ employmentType: "full_time", // full_time, part_time, contract, intern
305
+ status: "active", // active, on_leave, suspended, terminated
306
+ department: "training",
307
+ position: "Senior Trainer",
308
+
309
+ // Dates
310
+ hireDate: Date,
311
+ terminationDate: Date,
312
+ probationEndDate: Date,
313
+
314
+ // Employment history (re-hiring support)
315
+ employmentHistory: [{
316
+ hireDate: Date,
317
+ terminationDate: Date,
318
+ reason: String,
319
+ finalSalary: Number
320
+ }],
321
+
322
+ // Compensation
323
+ compensation: {
324
+ baseAmount: 50000,
325
+ frequency: "monthly",
326
+ currency: "BDT",
327
+
328
+ allowances: [
329
+ { type: "housing", amount: 10000, taxable: true },
330
+ { type: "transport", amount: 5000, taxable: false }
331
+ ],
332
+
333
+ deductions: [
334
+ { type: "loan", amount: 2000, auto: true }
335
+ ],
336
+
337
+ grossSalary: 65000, // Auto-calculated
338
+ netSalary: 63000, // Auto-calculated
339
+ },
340
+
341
+ // Bank details
342
+ bankDetails: {
343
+ accountName: String,
344
+ accountNumber: String,
345
+ bankName: String
346
+ },
347
+
348
+ // Payroll stats (pre-calculated)
349
+ payrollStats: {
350
+ totalPaid: 500000,
351
+ lastPaymentDate: Date,
352
+ nextPaymentDate: Date,
353
+ paymentsThisYear: 10,
354
+ averageMonthly: 50000
355
+ },
356
+
357
+ // YOUR CUSTOM FIELDS
358
+ certifications: [...],
359
+ specializations: [...],
360
+ emergencyContact: {...}
361
+ }
362
+ ```
363
+
364
+ ### PayrollRecord (Universal Ledger)
365
+
366
+ ```javascript
367
+ {
368
+ organizationId: ObjectId,
369
+ employeeId: ObjectId,
370
+ userId: ObjectId,
371
+
372
+ period: {
373
+ month: 11,
374
+ year: 2025,
375
+ startDate: Date,
376
+ endDate: Date,
377
+ payDate: Date
378
+ },
379
+
380
+ breakdown: {
381
+ baseAmount: 50000,
382
+ allowances: [...],
383
+ deductions: [...],
384
+ grossSalary: 65000,
385
+ netSalary: 63000,
386
+
387
+ // Smart calculations
388
+ workingDays: 30,
389
+ actualDays: 25, // If joined mid-month
390
+ proRatedAmount: 41667, // Pro-rated salary
391
+ attendanceDeduction: 0, // From attendance integration
392
+ overtimeAmount: 0,
393
+ bonusAmount: 0
394
+ },
395
+
396
+ transactionId: ObjectId, // Links to Transaction
397
+ status: "paid",
398
+ paidAt: Date,
399
+
400
+ // Export tracking
401
+ exported: false, // Must export before TTL deletion
402
+ exportedAt: Date
403
+ }
404
+ ```
405
+
406
+ ## ⚙️ Configuration
407
+
408
+ ```javascript
409
+ // lib/hrm/config.js
410
+ export const HRM_CONFIG = {
411
+ dataRetention: {
412
+ payrollRecordsTTL: 63072000, // 2 years in seconds
413
+ exportWarningDays: 30, // Warn before deletion
414
+ archiveBeforeDeletion: true,
415
+ },
416
+
417
+ payroll: {
418
+ defaultCurrency: 'BDT',
419
+ allowProRating: true, // Mid-month hire calculations
420
+ attendanceIntegration: true, // Unpaid leave deductions
421
+ autoDeductions: true, // Auto-deduct loans/advances
422
+ },
423
+
424
+ employment: {
425
+ defaultProbationMonths: 3,
426
+ allowReHiring: true, // Re-hire terminated employees
427
+ trackEmploymentHistory: true,
428
+ },
429
+
430
+ validation: {
431
+ requireBankDetails: false,
432
+ allowMultiTenantEmployees: true, // Same user in multiple orgs
433
+ },
434
+ };
435
+ ```
436
+
437
+ ## 🔑 Key Concepts
438
+
439
+ ### Multi-Tenant Architecture
440
+
441
+ Same user can work at multiple gyms:
442
+ ```javascript
443
+ // User "john@example.com" (userId: 123)
444
+ // Works at Gym A
445
+ { userId: 123, organizationId: "gymA", employeeId: "EMP-001", status: "active" }
446
+
447
+ // Also works at Gym B
448
+ { userId: 123, organizationId: "gymB", employeeId: "STAFF-05", status: "active" }
449
+ ```
450
+
451
+ Indexes ensure uniqueness:
452
+ - `{ userId: 1, organizationId: 1 }` unique
453
+ - `{ organizationId: 1, employeeId: 1 }` unique
454
+
455
+ ### Re-Hiring Flow
456
+
457
+ ```javascript
458
+ // Employee leaves
459
+ await hrm.terminate({
460
+ employeeId,
461
+ reason: 'resignation',
462
+ terminationDate: new Date()
463
+ });
464
+ // status: 'terminated', data preserved
465
+
466
+ // Employee comes back
467
+ await hrm.reHire({
468
+ employeeId,
469
+ hireDate: new Date(),
470
+ position: 'Manager', // Optional: new position
471
+ compensation: { baseAmount: 60000 } // Optional: new salary
472
+ });
473
+ // status: 'active', previous stint added to employmentHistory[]
474
+ ```
475
+
476
+ ### Smart Payroll Calculations
477
+
478
+ **Pro-Rating (Mid-Month Hire)**:
479
+ ```javascript
480
+ // Employee hired on Nov 15
481
+ // Working days: 15 out of 30
482
+ // Base salary: 60,000
483
+ // Pro-rated: 60,000 × (15/30) = 30,000
484
+ ```
485
+
486
+ **Attendance Integration**:
487
+ ```javascript
488
+ // Monthly salary: 60,000
489
+ // Working days: 30
490
+ // Attended days: 25
491
+ // Absent days: 5
492
+ // Daily rate: 60,000 / 30 = 2,000
493
+ // Deduction: 5 × 2,000 = 10,000
494
+ // Final: 60,000 - 10,000 = 50,000
495
+ ```
496
+
497
+ **Auto Deductions**:
498
+ ```javascript
499
+ compensation: {
500
+ baseAmount: 60000,
501
+ allowances: [{ type: 'housing', amount: 10000 }],
502
+ deductions: [
503
+ { type: 'loan', amount: 5000, auto: true }, // Auto-deduct
504
+ { type: 'tax', amount: 3000, auto: true }
505
+ ],
506
+ grossSalary: 70000,
507
+ netSalary: 62000 // 70000 - 5000 - 3000
508
+ }
509
+ ```
510
+
511
+ ### Transaction Integration
512
+
513
+ Every salary payment creates a Transaction:
514
+ ```javascript
515
+ {
516
+ organizationId,
517
+ type: 'expense',
518
+ category: 'salary',
519
+ amount: 63000,
520
+ method: 'bank',
521
+ status: 'completed',
522
+ referenceId: employeeId,
523
+ referenceModel: 'Employee',
524
+ metadata: {
525
+ employeeId: 'EMP-001',
526
+ payrollRecordId: ObjectId(...),
527
+ period: { month: 11, year: 2025 },
528
+ breakdown: { ... }
529
+ }
530
+ }
531
+ ```
532
+
533
+ ### Data Retention & Export
534
+
535
+ PayrollRecords auto-delete after 2 years:
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**
package/package.json ADDED
@@ -0,0 +1,59 @@
1
+ {
2
+ "name": "@classytic/payroll",
3
+ "version": "1.0.0",
4
+ "description": "Modern HRM and payroll management library for Node.js applications",
5
+ "type": "module",
6
+ "main": "./src/index.js",
7
+ "exports": {
8
+ ".": "./src/index.js",
9
+ "./init": "./src/init.js",
10
+ "./service": "./src/service.js",
11
+ "./adapters/*": "./src/adapters/*.js",
12
+ "./events/*": "./src/events/*.js",
13
+ "./workflows/*": "./src/workflows/*.js",
14
+ "./schemas/*": "./src/schemas/*.js"
15
+ },
16
+ "files": [
17
+ "src/**/*.js",
18
+ "README.md",
19
+ "LICENSE"
20
+ ],
21
+ "keywords": [
22
+ "hrm",
23
+ "payroll",
24
+ "employee",
25
+ "management",
26
+ "attendance",
27
+ "leave",
28
+ "salary",
29
+ "nodejs",
30
+ "mongoose"
31
+ ],
32
+ "author": "Sadman Chowdhury",
33
+ "license": "MIT",
34
+ "repository": {
35
+ "type": "git",
36
+ "url": "git+https://github.com/classytic/payroll.git"
37
+ },
38
+ "bugs": {
39
+ "url": "https://github.com/classytic/payroll/issues"
40
+ },
41
+ "homepage": "https://github.com/classytic/payroll#readme",
42
+ "peerDependencies": {
43
+ "mongoose": "^8.0.0"
44
+ },
45
+ "devDependencies": {
46
+ "mongoose": "^8.0.0",
47
+ "mongoose-paginate-v2": "^1.8.0",
48
+ "mongoose-aggregate-paginate-v2": "^1.1.0"
49
+ },
50
+ "engines": {
51
+ "node": ">=18.0.0"
52
+ },
53
+ "scripts": {
54
+ "example:basic": "node test/examples/basic-usage.js",
55
+ "example:mongoose": "node test/examples/with-mongoose.js",
56
+ "example:workflow": "node test/examples/full-workflow.js",
57
+ "test": "npm run example:basic && npm run example:mongoose && npm run example:workflow"
58
+ }
59
+ }