@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 +21 -0
- package/README.md +574 -0
- package/package.json +59 -0
- package/src/config.js +177 -0
- package/src/core/compensation.manager.js +242 -0
- package/src/core/employment.manager.js +224 -0
- package/src/core/payroll.manager.js +499 -0
- package/src/enums.js +141 -0
- package/src/factories/compensation.factory.js +198 -0
- package/src/factories/employee.factory.js +173 -0
- package/src/factories/payroll.factory.js +247 -0
- package/src/hrm.orchestrator.js +139 -0
- package/src/index.js +172 -0
- package/src/init.js +41 -0
- package/src/models/payroll-record.model.js +126 -0
- package/src/plugins/employee.plugin.js +157 -0
- package/src/schemas/employment.schema.js +126 -0
- package/src/services/compensation.service.js +231 -0
- package/src/services/employee.service.js +162 -0
- package/src/services/payroll.service.js +213 -0
- package/src/utils/calculation.utils.js +91 -0
- package/src/utils/date.utils.js +120 -0
- package/src/utils/logger.js +36 -0
- package/src/utils/query-builders.js +185 -0
- package/src/utils/validation.utils.js +122 -0
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
|
+
}
|