@classytic/payroll 1.0.2 โ 2.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/README.md +168 -489
- package/dist/core/index.d.ts +480 -0
- package/dist/core/index.js +971 -0
- package/dist/core/index.js.map +1 -0
- package/dist/index-CTjHlCzz.d.ts +721 -0
- package/dist/index.d.ts +967 -0
- package/dist/index.js +4352 -0
- package/dist/index.js.map +1 -0
- package/dist/payroll.d.ts +233 -0
- package/dist/payroll.js +2103 -0
- package/dist/payroll.js.map +1 -0
- package/dist/plugin-D9mOr3_d.d.ts +333 -0
- package/dist/schemas/index.d.ts +2869 -0
- package/dist/schemas/index.js +440 -0
- package/dist/schemas/index.js.map +1 -0
- package/dist/services/index.d.ts +3 -0
- package/dist/services/index.js +1696 -0
- package/dist/services/index.js.map +1 -0
- package/dist/types-BSYyX2KJ.d.ts +671 -0
- package/dist/utils/index.d.ts +873 -0
- package/dist/utils/index.js +1046 -0
- package/dist/utils/index.js.map +1 -0
- package/package.json +54 -37
- package/dist/types/config.d.ts +0 -162
- package/dist/types/core/compensation.manager.d.ts +0 -54
- package/dist/types/core/employment.manager.d.ts +0 -49
- package/dist/types/core/payroll.manager.d.ts +0 -60
- package/dist/types/enums.d.ts +0 -117
- package/dist/types/factories/compensation.factory.d.ts +0 -196
- package/dist/types/factories/employee.factory.d.ts +0 -149
- package/dist/types/factories/payroll.factory.d.ts +0 -319
- package/dist/types/hrm.orchestrator.d.ts +0 -47
- package/dist/types/index.d.ts +0 -20
- package/dist/types/init.d.ts +0 -30
- package/dist/types/models/payroll-record.model.d.ts +0 -3
- package/dist/types/plugins/employee.plugin.d.ts +0 -2
- package/dist/types/schemas/employment.schema.d.ts +0 -959
- package/dist/types/services/compensation.service.d.ts +0 -94
- package/dist/types/services/employee.service.d.ts +0 -28
- package/dist/types/services/payroll.service.d.ts +0 -30
- package/dist/types/utils/calculation.utils.d.ts +0 -26
- package/dist/types/utils/date.utils.d.ts +0 -35
- package/dist/types/utils/logger.d.ts +0 -12
- package/dist/types/utils/query-builders.d.ts +0 -83
- package/dist/types/utils/validation.utils.d.ts +0 -33
- package/payroll.d.ts +0 -241
- package/src/config.js +0 -177
- package/src/core/compensation.manager.js +0 -242
- package/src/core/employment.manager.js +0 -224
- package/src/core/payroll.manager.js +0 -499
- package/src/enums.js +0 -141
- package/src/factories/compensation.factory.js +0 -198
- package/src/factories/employee.factory.js +0 -173
- package/src/factories/payroll.factory.js +0 -413
- package/src/hrm.orchestrator.js +0 -139
- package/src/index.js +0 -172
- package/src/init.js +0 -62
- package/src/models/payroll-record.model.js +0 -126
- package/src/plugins/employee.plugin.js +0 -164
- package/src/schemas/employment.schema.js +0 -126
- package/src/services/compensation.service.js +0 -231
- package/src/services/employee.service.js +0 -162
- package/src/services/payroll.service.js +0 -213
- package/src/utils/calculation.utils.js +0 -91
- package/src/utils/date.utils.js +0 -120
- package/src/utils/logger.js +0 -36
- package/src/utils/query-builders.js +0 -185
- package/src/utils/validation.utils.js +0 -122
package/README.md
CHANGED
|
@@ -1,574 +1,253 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @classytic/payroll
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Enterprise-grade payroll for Mongoose. Simple, powerful, production-ready.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
[](https://www.npmjs.com/package/@classytic/payroll)
|
|
6
|
+
[](https://www.typescriptlang.org/)
|
|
7
|
+
[](https://opensource.org/licenses/MIT)
|
|
6
8
|
|
|
7
|
-
|
|
8
|
-
- Same user can be employee in multiple organizations
|
|
9
|
-
- Complete data isolation per tenant
|
|
10
|
-
- Re-hiring support with full employment history
|
|
9
|
+
## Why This Package?
|
|
11
10
|
|
|
12
|
-
|
|
13
|
-
- **
|
|
14
|
-
- **
|
|
15
|
-
- **
|
|
16
|
-
- **
|
|
17
|
-
- **Transaction
|
|
11
|
+
- ๐ฏ **One clear way to do things** - No confusion, no multiple paths
|
|
12
|
+
- โก **Attendance built-in** - Uses `@classytic/clockin` natively
|
|
13
|
+
- ๐ข **Multi-tenant & Single-tenant** - Both supported out of the box
|
|
14
|
+
- ๐ฐ **Smart calculations** - Pro-rating, tax, deductions, all automatic
|
|
15
|
+
- ๐งช **Pure functions** - Test easily, preview without DB
|
|
16
|
+
- ๐ **Transaction-safe** - Atomic operations, no partial writes
|
|
17
|
+
- ๐ฆ **Zero config** - Works immediately with smart defaults
|
|
18
18
|
|
|
19
|
-
|
|
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`
|
|
19
|
+
## Installation
|
|
23
20
|
|
|
24
|
-
|
|
25
|
-
|
|
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
|
|
21
|
+
```bash
|
|
22
|
+
npm install @classytic/payroll @classytic/clockin mongoose
|
|
69
23
|
```
|
|
70
24
|
|
|
71
|
-
##
|
|
25
|
+
## Quick Start (3 steps)
|
|
72
26
|
|
|
73
|
-
### 1. Create
|
|
27
|
+
### 1. Create Models
|
|
74
28
|
|
|
75
|
-
```
|
|
76
|
-
// modules/employee/employee.model.js
|
|
29
|
+
```typescript
|
|
77
30
|
import mongoose from 'mongoose';
|
|
78
|
-
import {
|
|
31
|
+
import { createAttendanceSchema } from '@classytic/clockin/schemas';
|
|
32
|
+
import { employeeSchema, employeePlugin, payrollRecordSchema, createHolidaySchema } from '@classytic/payroll';
|
|
79
33
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
...employmentFields,
|
|
34
|
+
// Attendance (from ClockIn - required for payroll)
|
|
35
|
+
const Attendance = mongoose.model('Attendance', createAttendanceSchema());
|
|
83
36
|
|
|
84
|
-
|
|
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)
|
|
37
|
+
// Employee (with payroll plugin)
|
|
92
38
|
employeeSchema.plugin(employeePlugin);
|
|
39
|
+
const Employee = mongoose.model('Employee', employeeSchema);
|
|
40
|
+
|
|
41
|
+
// PayrollRecord
|
|
42
|
+
const PayrollRecord = mongoose.model('PayrollRecord', payrollRecordSchema);
|
|
93
43
|
|
|
94
|
-
|
|
44
|
+
// Transaction (your own model)
|
|
45
|
+
const Transaction = mongoose.model('Transaction', transactionSchema);
|
|
46
|
+
|
|
47
|
+
// Holiday (optional - use our schema or your own)
|
|
48
|
+
const Holiday = mongoose.model('Holiday', createHolidaySchema());
|
|
95
49
|
```
|
|
96
50
|
|
|
97
|
-
### 2.
|
|
51
|
+
### 2. Initialize
|
|
98
52
|
|
|
99
|
-
```
|
|
100
|
-
|
|
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';
|
|
53
|
+
```typescript
|
|
54
|
+
import { createPayrollInstance } from '@classytic/payroll';
|
|
106
55
|
|
|
107
|
-
|
|
108
|
-
|
|
56
|
+
const payroll = createPayrollInstance()
|
|
57
|
+
.withModels({
|
|
109
58
|
EmployeeModel: Employee,
|
|
110
59
|
PayrollRecordModel: PayrollRecord,
|
|
111
60
|
TransactionModel: Transaction,
|
|
112
|
-
AttendanceModel: Attendance,
|
|
113
|
-
})
|
|
114
|
-
|
|
61
|
+
AttendanceModel: Attendance,
|
|
62
|
+
})
|
|
63
|
+
.build();
|
|
115
64
|
```
|
|
116
65
|
|
|
117
|
-
### 3. Use
|
|
118
|
-
|
|
119
|
-
```javascript
|
|
120
|
-
import { hrm } from '#lib/hrm/index.js';
|
|
66
|
+
### 3. Use It
|
|
121
67
|
|
|
68
|
+
```typescript
|
|
122
69
|
// Hire employee
|
|
123
|
-
const employee = await
|
|
124
|
-
|
|
125
|
-
|
|
70
|
+
const employee = await payroll.hire({
|
|
71
|
+
userId: user._id,
|
|
72
|
+
organizationId: org._id,
|
|
126
73
|
employment: {
|
|
127
|
-
|
|
74
|
+
position: 'Software Engineer',
|
|
75
|
+
department: 'engineering',
|
|
128
76
|
type: 'full_time',
|
|
129
|
-
department: 'training',
|
|
130
|
-
position: 'Senior Trainer',
|
|
131
|
-
hireDate: new Date(),
|
|
132
77
|
},
|
|
133
78
|
compensation: {
|
|
134
|
-
baseAmount:
|
|
135
|
-
|
|
79
|
+
baseAmount: 100000,
|
|
80
|
+
currency: 'USD',
|
|
136
81
|
allowances: [
|
|
137
|
-
{ type: 'housing', amount:
|
|
138
|
-
|
|
139
|
-
]
|
|
140
|
-
},
|
|
141
|
-
bankDetails: {
|
|
142
|
-
accountName: 'John Doe',
|
|
143
|
-
accountNumber: '1234567890',
|
|
144
|
-
bankName: 'Example Bank'
|
|
82
|
+
{ type: 'housing', amount: 20000, taxable: true },
|
|
83
|
+
],
|
|
145
84
|
},
|
|
146
|
-
context: { userId: hrManagerId }
|
|
147
85
|
});
|
|
148
86
|
|
|
149
|
-
// Process
|
|
150
|
-
const result = await
|
|
87
|
+
// Process monthly payroll (automatic attendance deductions)
|
|
88
|
+
const result = await payroll.processSalary({
|
|
151
89
|
employeeId: employee._id,
|
|
152
|
-
month:
|
|
153
|
-
year:
|
|
154
|
-
paymentDate: new Date(),
|
|
155
|
-
paymentMethod: 'bank',
|
|
156
|
-
context: { userId: hrManagerId }
|
|
90
|
+
month: 3,
|
|
91
|
+
year: 2024,
|
|
157
92
|
});
|
|
158
93
|
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
94
|
+
console.log(result.payrollRecord.breakdown);
|
|
95
|
+
// {
|
|
96
|
+
// baseSalary: 100000,
|
|
97
|
+
// allowances: 20000,
|
|
98
|
+
// deductions: 9090, // โ Attendance deduction
|
|
99
|
+
// tax: 2500,
|
|
100
|
+
// gross: 120000,
|
|
101
|
+
// net: 108410
|
|
102
|
+
// }
|
|
166
103
|
```
|
|
167
104
|
|
|
105
|
+
## Single-Tenant Setup
|
|
168
106
|
|
|
169
|
-
|
|
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 });
|
|
107
|
+
Building a single-organization HRM? No `organizationId` needed:
|
|
182
108
|
|
|
183
|
-
|
|
184
|
-
|
|
109
|
+
```typescript
|
|
110
|
+
const payroll = createPayrollInstance()
|
|
111
|
+
.withModels({ EmployeeModel, PayrollRecordModel, TransactionModel, AttendanceModel })
|
|
112
|
+
.forSingleTenant() // โ That's it!
|
|
113
|
+
.build();
|
|
185
114
|
|
|
186
|
-
//
|
|
187
|
-
await
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
115
|
+
// No organizationId anywhere
|
|
116
|
+
const employee = await payroll.hire({
|
|
117
|
+
userId: user._id,
|
|
118
|
+
employment: { position: 'Manager', type: 'full_time' },
|
|
119
|
+
compensation: { baseAmount: 150000, currency: 'USD' },
|
|
191
120
|
});
|
|
192
|
-
|
|
193
|
-
// Get single employee
|
|
194
|
-
await hrm.getEmployee({ employeeId, populateUser: true });
|
|
195
121
|
```
|
|
196
122
|
|
|
197
|
-
|
|
123
|
+
## Attendance (ClockIn)
|
|
198
124
|
|
|
199
|
-
|
|
200
|
-
// Update salary
|
|
201
|
-
await hrm.updateSalary({
|
|
202
|
-
employeeId,
|
|
203
|
-
compensation: { baseAmount: 60000 },
|
|
204
|
-
effectiveFrom: new Date(),
|
|
205
|
-
context
|
|
206
|
-
});
|
|
125
|
+
Attendance is **native**, not an add-on:
|
|
207
126
|
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
type: 'meal',
|
|
212
|
-
amount: 3000,
|
|
213
|
-
taxable: true,
|
|
214
|
-
recurring: true,
|
|
215
|
-
context
|
|
216
|
-
});
|
|
127
|
+
```typescript
|
|
128
|
+
import { ClockIn } from '@classytic/clockin';
|
|
129
|
+
import { getAttendance } from '@classytic/payroll';
|
|
217
130
|
|
|
218
|
-
//
|
|
219
|
-
|
|
131
|
+
// Initialize ClockIn
|
|
132
|
+
const clockin = ClockIn.create()
|
|
133
|
+
.withModels({ Attendance, Membership: Employee })
|
|
134
|
+
.build();
|
|
220
135
|
|
|
221
|
-
//
|
|
222
|
-
await
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
auto: true, // Auto-deduct from salary
|
|
227
|
-
description: 'Personal loan repayment',
|
|
228
|
-
context
|
|
136
|
+
// Employees check in
|
|
137
|
+
await clockin.checkIn.record({
|
|
138
|
+
member: employee,
|
|
139
|
+
targetModel: 'Employee',
|
|
140
|
+
data: { method: 'qr_code' },
|
|
229
141
|
});
|
|
230
142
|
|
|
231
|
-
//
|
|
232
|
-
|
|
143
|
+
// Payroll automatically uses attendance
|
|
144
|
+
const attendance = await getAttendance(Attendance, {
|
|
145
|
+
organizationId: org._id,
|
|
146
|
+
employeeId: employee._id,
|
|
147
|
+
month: 3,
|
|
148
|
+
year: 2024,
|
|
149
|
+
expectedDays: 22,
|
|
150
|
+
});
|
|
233
151
|
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
152
|
+
await payroll.processSalary({
|
|
153
|
+
employeeId: employee._id,
|
|
154
|
+
month: 3,
|
|
155
|
+
year: 2024,
|
|
156
|
+
attendance, // โ Deductions automatically applied
|
|
239
157
|
});
|
|
240
158
|
```
|
|
241
159
|
|
|
242
|
-
|
|
160
|
+
## Holidays
|
|
243
161
|
|
|
244
|
-
|
|
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
|
-
});
|
|
162
|
+
Simple approach - one way:
|
|
254
163
|
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
organizationId,
|
|
258
|
-
month: 11,
|
|
259
|
-
year: 2025,
|
|
260
|
-
employeeIds: [], // Empty = all active employees
|
|
261
|
-
paymentDate: new Date(),
|
|
262
|
-
paymentMethod: 'bank',
|
|
263
|
-
context
|
|
264
|
-
});
|
|
164
|
+
```typescript
|
|
165
|
+
import { getHolidays } from '@classytic/payroll';
|
|
265
166
|
|
|
266
|
-
//
|
|
267
|
-
await
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
pagination: { page: 1, limit: 20 }
|
|
167
|
+
// Add sudden off day
|
|
168
|
+
await Holiday.create({
|
|
169
|
+
organizationId: org._id,
|
|
170
|
+
date: new Date('2024-03-17'),
|
|
171
|
+
name: 'Emergency closure',
|
|
172
|
+
type: 'company',
|
|
173
|
+
paid: true,
|
|
274
174
|
});
|
|
275
175
|
|
|
276
|
-
//
|
|
277
|
-
await
|
|
278
|
-
organizationId,
|
|
279
|
-
|
|
280
|
-
|
|
176
|
+
// Get holidays when processing
|
|
177
|
+
const holidays = await getHolidays(Holiday, {
|
|
178
|
+
organizationId: org._id,
|
|
179
|
+
startDate: new Date('2024-03-01'),
|
|
180
|
+
endDate: new Date('2024-03-31'),
|
|
281
181
|
});
|
|
282
182
|
|
|
283
|
-
//
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
183
|
+
// Pass to payroll
|
|
184
|
+
await payroll.processSalary({
|
|
185
|
+
employeeId,
|
|
186
|
+
month: 3,
|
|
187
|
+
year: 2024,
|
|
188
|
+
options: { holidays },
|
|
289
189
|
});
|
|
290
190
|
```
|
|
291
191
|
|
|
292
|
-
##
|
|
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
|
-
],
|
|
192
|
+
## Logging
|
|
336
193
|
|
|
337
|
-
|
|
338
|
-
netSalary: 63000, // Auto-calculated
|
|
339
|
-
},
|
|
194
|
+
Control logging in production:
|
|
340
195
|
|
|
341
|
-
|
|
342
|
-
|
|
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
|
-
},
|
|
196
|
+
```typescript
|
|
197
|
+
import { disableLogging, enableLogging } from '@classytic/payroll/utils';
|
|
356
198
|
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
emergencyContact: {...}
|
|
199
|
+
// Disable in production
|
|
200
|
+
if (process.env.NODE_ENV === 'production') {
|
|
201
|
+
disableLogging();
|
|
361
202
|
}
|
|
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
203
|
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
204
|
+
// Or use custom logger
|
|
205
|
+
payroll.initialize({
|
|
206
|
+
...models,
|
|
207
|
+
logger: {
|
|
208
|
+
info: (msg, meta) => pino.info(meta, msg),
|
|
209
|
+
error: (msg, meta) => pino.error(meta, msg),
|
|
210
|
+
warn: (msg, meta) => pino.warn(meta, msg),
|
|
211
|
+
debug: (msg, meta) => pino.debug(meta, msg),
|
|
428
212
|
},
|
|
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
213
|
});
|
|
473
|
-
// status: 'active', previous stint added to employmentHistory[]
|
|
474
214
|
```
|
|
475
215
|
|
|
476
|
-
|
|
216
|
+
## API
|
|
477
217
|
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
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
|
-
```
|
|
218
|
+
```typescript
|
|
219
|
+
// Employee lifecycle
|
|
220
|
+
payroll.hire({ ... })
|
|
221
|
+
payroll.updateEmployment({ ... })
|
|
222
|
+
payroll.terminate({ ... })
|
|
223
|
+
payroll.reHire({ ... })
|
|
496
224
|
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
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
|
-
```
|
|
225
|
+
// Compensation
|
|
226
|
+
payroll.updateSalary({ ... })
|
|
227
|
+
payroll.addAllowance({ ... })
|
|
228
|
+
payroll.addDeduction({ ... })
|
|
510
229
|
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
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
|
-
```
|
|
230
|
+
// Payroll processing
|
|
231
|
+
payroll.processSalary({ ... })
|
|
232
|
+
payroll.processBulkPayroll({ ... })
|
|
233
|
+
payroll.payrollHistory({ ... })
|
|
234
|
+
payroll.payrollSummary({ ... })
|
|
532
235
|
|
|
533
|
-
|
|
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
|
|
236
|
+
// Pure functions (for previews/testing)
|
|
237
|
+
import { calculateSalaryBreakdown, countWorkingDays, calculateTax } from '@classytic/payroll/core';
|
|
553
238
|
```
|
|
554
239
|
|
|
555
|
-
##
|
|
240
|
+
## Documentation
|
|
556
241
|
|
|
557
|
-
- **
|
|
558
|
-
- **
|
|
559
|
-
- **
|
|
560
|
-
- **
|
|
561
|
-
- **Production-ready**: Transaction integration, data retention, comprehensive error handling
|
|
242
|
+
- **[HRM Guide](./docs/HRM_GUIDE.md)** - Complete guide to building HRM
|
|
243
|
+
- **[Single Tenant](./docs/SINGLE_TENANT.md)** - Single-organization setup
|
|
244
|
+
- **[Integration](./docs/INTEGRATION.md)** - Fastify, Express plugins
|
|
245
|
+
- **[Development](./docs/DEVELOPMENT.md)** - Contributing
|
|
562
246
|
|
|
563
|
-
##
|
|
247
|
+
## Related Packages
|
|
564
248
|
|
|
565
|
-
|
|
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
|
|
249
|
+
- **[@classytic/clockin](https://npmjs.com/package/@classytic/clockin)** - Attendance management (required peer dependency)
|
|
570
250
|
|
|
571
|
-
|
|
251
|
+
## License
|
|
572
252
|
|
|
573
|
-
|
|
574
|
-
**Ready for multi-tenant gym management**
|
|
253
|
+
MIT ยฉ [Sadman Chowdhury](https://github.com/classytic)
|