@classytic/payroll 1.0.0 → 2.8.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 +535 -574
- package/dist/attendance.calculator-BZcv2iii.d.ts +336 -0
- package/dist/calculators/index.d.ts +4 -0
- package/dist/calculators/index.js +439 -0
- package/dist/calculators/index.js.map +1 -0
- package/dist/core/index.d.ts +321 -0
- package/dist/core/index.js +1962 -0
- package/dist/core/index.js.map +1 -0
- package/dist/error-helpers-Bm6lMny2.d.ts +740 -0
- package/dist/index-BKLkuSAs.d.ts +3858 -0
- package/dist/index.d.ts +2684 -0
- package/dist/index.js +11454 -0
- package/dist/index.js.map +1 -0
- package/dist/payroll-states-DBt0XVm-.d.ts +598 -0
- package/dist/prorating.calculator-C33fWBQf.d.ts +135 -0
- package/dist/schemas/index.d.ts +4 -0
- package/dist/schemas/index.js +1472 -0
- package/dist/schemas/index.js.map +1 -0
- package/dist/types-bZdAJueH.d.ts +2271 -0
- package/dist/utils/index.d.ts +1007 -0
- package/dist/utils/index.js +1789 -0
- package/dist/utils/index.js.map +1 -0
- package/package.json +81 -24
- 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 -247
- package/src/hrm.orchestrator.js +0 -139
- package/src/index.js +0 -172
- package/src/init.js +0 -41
- package/src/models/payroll-record.model.js +0 -126
- package/src/plugins/employee.plugin.js +0 -157
- 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/package.json
CHANGED
|
@@ -1,20 +1,36 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@classytic/payroll",
|
|
3
|
-
"version": "
|
|
4
|
-
"description": "Modern HRM and payroll management
|
|
3
|
+
"version": "2.8.0",
|
|
4
|
+
"description": "Modern HRM and payroll management for Mongoose - Plugin-based, event-driven, multi-tenant ready. Salary processing, compensation management, tax calculations, and employee lifecycle management.",
|
|
5
5
|
"type": "module",
|
|
6
|
-
"
|
|
6
|
+
"sideEffects": false,
|
|
7
|
+
"main": "./dist/index.js",
|
|
8
|
+
"module": "./dist/index.js",
|
|
9
|
+
"types": "./dist/index.d.ts",
|
|
7
10
|
"exports": {
|
|
8
|
-
".":
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
"./
|
|
13
|
-
|
|
14
|
-
|
|
11
|
+
".": {
|
|
12
|
+
"types": "./dist/index.d.ts",
|
|
13
|
+
"default": "./dist/index.js"
|
|
14
|
+
},
|
|
15
|
+
"./schemas": {
|
|
16
|
+
"types": "./dist/schemas/index.d.ts",
|
|
17
|
+
"default": "./dist/schemas/index.js"
|
|
18
|
+
},
|
|
19
|
+
"./utils": {
|
|
20
|
+
"types": "./dist/utils/index.d.ts",
|
|
21
|
+
"default": "./dist/utils/index.js"
|
|
22
|
+
},
|
|
23
|
+
"./calculators": {
|
|
24
|
+
"types": "./dist/calculators/index.d.ts",
|
|
25
|
+
"default": "./dist/calculators/index.js"
|
|
26
|
+
},
|
|
27
|
+
"./core": {
|
|
28
|
+
"types": "./dist/core/index.d.ts",
|
|
29
|
+
"default": "./dist/core/index.js"
|
|
30
|
+
}
|
|
15
31
|
},
|
|
16
32
|
"files": [
|
|
17
|
-
"
|
|
33
|
+
"dist",
|
|
18
34
|
"README.md",
|
|
19
35
|
"LICENSE"
|
|
20
36
|
],
|
|
@@ -23,13 +39,23 @@
|
|
|
23
39
|
"payroll",
|
|
24
40
|
"employee",
|
|
25
41
|
"management",
|
|
26
|
-
"attendance",
|
|
27
|
-
"leave",
|
|
28
42
|
"salary",
|
|
29
|
-
"
|
|
30
|
-
"
|
|
43
|
+
"compensation",
|
|
44
|
+
"tax",
|
|
45
|
+
"multi-tenant",
|
|
46
|
+
"mongoose",
|
|
47
|
+
"typescript",
|
|
48
|
+
"plugin",
|
|
49
|
+
"hr",
|
|
50
|
+
"human-resources",
|
|
51
|
+
"workforce",
|
|
52
|
+
"attendance",
|
|
53
|
+
"nodejs"
|
|
54
|
+
],
|
|
55
|
+
"author": "Classytic <classytic.dev@gmail.com> (https://github.com/classytic)",
|
|
56
|
+
"contributors": [
|
|
57
|
+
"Sadman Chowdhury (https://github.com/siam923)"
|
|
31
58
|
],
|
|
32
|
-
"author": "Sadman Chowdhury",
|
|
33
59
|
"license": "MIT",
|
|
34
60
|
"repository": {
|
|
35
61
|
"type": "git",
|
|
@@ -40,20 +66,51 @@
|
|
|
40
66
|
},
|
|
41
67
|
"homepage": "https://github.com/classytic/payroll#readme",
|
|
42
68
|
"peerDependencies": {
|
|
43
|
-
"
|
|
69
|
+
"@classytic/clockin": "^2.2.0",
|
|
70
|
+
"@classytic/mongokit": "^3.0.0",
|
|
71
|
+
"@classytic/shared-types": "^1.0.0",
|
|
72
|
+
"mongoose": "^8.0.0 || ^9.0.0",
|
|
73
|
+
"lru-cache": "^11.0.0",
|
|
74
|
+
"p-limit": "^6.0.0 || ^7.0.0"
|
|
75
|
+
},
|
|
76
|
+
"peerDependenciesMeta": {
|
|
77
|
+
"@classytic/clockin": {
|
|
78
|
+
"optional": true
|
|
79
|
+
}
|
|
44
80
|
},
|
|
45
81
|
"devDependencies": {
|
|
46
|
-
"
|
|
47
|
-
"
|
|
48
|
-
"
|
|
82
|
+
"@classytic/clockin": "^2.2.0",
|
|
83
|
+
"@classytic/mongokit": "^3.0.0",
|
|
84
|
+
"@classytic/shared-types": "^1.0.0",
|
|
85
|
+
"@types/node": "^22.8.7",
|
|
86
|
+
"@vitest/coverage-v8": "^3.2.4",
|
|
87
|
+
"lru-cache": "^11.2.4",
|
|
88
|
+
"mongodb-memory-server": "^10.0.0",
|
|
89
|
+
"mongoose": "^9.0.0",
|
|
90
|
+
"p-limit": "^7.2.0",
|
|
91
|
+
"tsup": "^8.0.0",
|
|
92
|
+
"typescript": "^5.7.0",
|
|
93
|
+
"vitest": "^3.0.0"
|
|
49
94
|
},
|
|
50
95
|
"engines": {
|
|
51
96
|
"node": ">=18.0.0"
|
|
52
97
|
},
|
|
53
98
|
"scripts": {
|
|
54
|
-
"
|
|
55
|
-
"
|
|
56
|
-
"
|
|
57
|
-
"test": "
|
|
99
|
+
"build": "tsup",
|
|
100
|
+
"dev": "tsup --watch",
|
|
101
|
+
"test": "vitest run",
|
|
102
|
+
"test:local": "node scripts/test-local.mjs",
|
|
103
|
+
"test:watch": "vitest",
|
|
104
|
+
"test:coverage": "vitest run --coverage",
|
|
105
|
+
"test:exports": "node scripts/test-exports.mjs",
|
|
106
|
+
"typecheck": "tsc --noEmit",
|
|
107
|
+
"lint": "tsc --noEmit",
|
|
108
|
+
"prepublishOnly": "npm run build && npm run typecheck && npm run test:exports",
|
|
109
|
+
"publish:dry": "npm publish --dry-run --access public",
|
|
110
|
+
"publish:npm": "npm publish --access public",
|
|
111
|
+
"release": "npm run build && npm run typecheck && npm run test:exports && npm publish --access public",
|
|
112
|
+
"release:patch": "npm version patch && npm run release",
|
|
113
|
+
"release:minor": "npm version minor && npm run release",
|
|
114
|
+
"release:major": "npm version major && npm run release"
|
|
58
115
|
}
|
|
59
116
|
}
|
package/src/config.js
DELETED
|
@@ -1,177 +0,0 @@
|
|
|
1
|
-
export const HRM_CONFIG = {
|
|
2
|
-
// Data Retention: Auto-delete exported records after 2 years
|
|
3
|
-
// Organizations must download records via /payroll/export API before deletion
|
|
4
|
-
dataRetention: {
|
|
5
|
-
payrollRecordsTTL: 63072000, // 2 years in seconds
|
|
6
|
-
exportWarningDays: 30, // Warn before TTL expires
|
|
7
|
-
},
|
|
8
|
-
|
|
9
|
-
payroll: {
|
|
10
|
-
defaultCurrency: 'BDT',
|
|
11
|
-
allowProRating: true,
|
|
12
|
-
attendanceIntegration: true,
|
|
13
|
-
autoDeductions: true,
|
|
14
|
-
overtimeEnabled: false,
|
|
15
|
-
},
|
|
16
|
-
|
|
17
|
-
salary: {
|
|
18
|
-
minimumWage: 0,
|
|
19
|
-
maximumAllowances: 10,
|
|
20
|
-
maximumDeductions: 10,
|
|
21
|
-
defaultFrequency: 'monthly',
|
|
22
|
-
},
|
|
23
|
-
|
|
24
|
-
employment: {
|
|
25
|
-
defaultProbationMonths: 3,
|
|
26
|
-
maxProbationMonths: 6,
|
|
27
|
-
allowReHiring: true,
|
|
28
|
-
trackEmploymentHistory: true,
|
|
29
|
-
},
|
|
30
|
-
|
|
31
|
-
validation: {
|
|
32
|
-
requireBankDetails: false,
|
|
33
|
-
requireEmployeeId: true,
|
|
34
|
-
uniqueEmployeeIdPerOrg: true,
|
|
35
|
-
allowMultiTenantEmployees: true,
|
|
36
|
-
},
|
|
37
|
-
};
|
|
38
|
-
|
|
39
|
-
// ============ ORGANIZATION ROLES CONFIGURATION ============
|
|
40
|
-
// Defines available organization roles for frontend authentication and access control
|
|
41
|
-
// Frontend is responsible for implementing access control based on these roles
|
|
42
|
-
export const ORG_ROLES = {
|
|
43
|
-
OWNER: {
|
|
44
|
-
key: 'owner',
|
|
45
|
-
label: 'Owner',
|
|
46
|
-
description: 'Full organization access (set by Organization model)',
|
|
47
|
-
},
|
|
48
|
-
MANAGER: {
|
|
49
|
-
key: 'manager',
|
|
50
|
-
label: 'Manager',
|
|
51
|
-
description: 'Management and administrative features',
|
|
52
|
-
},
|
|
53
|
-
TRAINER: {
|
|
54
|
-
key: 'trainer',
|
|
55
|
-
label: 'Trainer',
|
|
56
|
-
description: 'Training and coaching features',
|
|
57
|
-
},
|
|
58
|
-
STAFF: {
|
|
59
|
-
key: 'staff',
|
|
60
|
-
label: 'Staff',
|
|
61
|
-
description: 'General staff access to basic features',
|
|
62
|
-
},
|
|
63
|
-
INTERN: {
|
|
64
|
-
key: 'intern',
|
|
65
|
-
label: 'Intern',
|
|
66
|
-
description: 'Limited access for interns',
|
|
67
|
-
},
|
|
68
|
-
CONSULTANT: {
|
|
69
|
-
key: 'consultant',
|
|
70
|
-
label: 'Consultant',
|
|
71
|
-
description: 'Project-based consultant access',
|
|
72
|
-
},
|
|
73
|
-
};
|
|
74
|
-
|
|
75
|
-
// Get all available org role keys
|
|
76
|
-
export const ORG_ROLE_KEYS = Object.values(ORG_ROLES).map(role => role.key);
|
|
77
|
-
|
|
78
|
-
// ============ ROLE MAPPING CONFIGURATION ============
|
|
79
|
-
// Maps employee attributes (department, position, employment type) to organization roles
|
|
80
|
-
// Priority: department > employmentType > default
|
|
81
|
-
export const ROLE_MAPPING = {
|
|
82
|
-
// Department-based mapping (highest priority)
|
|
83
|
-
byDepartment: {
|
|
84
|
-
management: 'manager',
|
|
85
|
-
training: 'trainer',
|
|
86
|
-
sales: 'staff',
|
|
87
|
-
operations: 'staff',
|
|
88
|
-
finance: 'staff',
|
|
89
|
-
hr: 'staff',
|
|
90
|
-
marketing: 'staff',
|
|
91
|
-
it: 'staff',
|
|
92
|
-
support: 'staff',
|
|
93
|
-
},
|
|
94
|
-
|
|
95
|
-
// Employment type mapping (fallback)
|
|
96
|
-
byEmploymentType: {
|
|
97
|
-
full_time: 'staff',
|
|
98
|
-
part_time: 'staff',
|
|
99
|
-
contract: 'consultant',
|
|
100
|
-
intern: 'intern',
|
|
101
|
-
consultant: 'consultant',
|
|
102
|
-
},
|
|
103
|
-
|
|
104
|
-
// Default role if no match found
|
|
105
|
-
default: 'staff',
|
|
106
|
-
};
|
|
107
|
-
|
|
108
|
-
export const SALARY_BANDS = {
|
|
109
|
-
intern: { min: 10000, max: 20000 },
|
|
110
|
-
junior: { min: 20000, max: 40000 },
|
|
111
|
-
mid: { min: 40000, max: 70000 },
|
|
112
|
-
senior: { min: 70000, max: 120000 },
|
|
113
|
-
lead: { min: 100000, max: 200000 },
|
|
114
|
-
executive: { min: 150000, max: 500000 },
|
|
115
|
-
};
|
|
116
|
-
|
|
117
|
-
export const TAX_BRACKETS = {
|
|
118
|
-
BDT: [
|
|
119
|
-
{ min: 0, max: 300000, rate: 0 },
|
|
120
|
-
{ min: 300000, max: 400000, rate: 0.05 },
|
|
121
|
-
{ min: 400000, max: 500000, rate: 0.10 },
|
|
122
|
-
{ min: 500000, max: 600000, rate: 0.15 },
|
|
123
|
-
{ min: 600000, max: 3000000, rate: 0.20 },
|
|
124
|
-
{ min: 3000000, max: Infinity, rate: 0.25 },
|
|
125
|
-
],
|
|
126
|
-
};
|
|
127
|
-
|
|
128
|
-
export function calculateTax(annualIncome, currency = 'BDT') {
|
|
129
|
-
const brackets = TAX_BRACKETS[currency];
|
|
130
|
-
if (!brackets) return 0;
|
|
131
|
-
|
|
132
|
-
let tax = 0;
|
|
133
|
-
for (const bracket of brackets) {
|
|
134
|
-
if (annualIncome > bracket.min) {
|
|
135
|
-
const taxableAmount = Math.min(annualIncome, bracket.max) - bracket.min;
|
|
136
|
-
tax += taxableAmount * bracket.rate;
|
|
137
|
-
}
|
|
138
|
-
}
|
|
139
|
-
return Math.round(tax);
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
export function getSalaryBand(amount) {
|
|
143
|
-
for (const [band, range] of Object.entries(SALARY_BANDS)) {
|
|
144
|
-
if (amount >= range.min && amount <= range.max) {
|
|
145
|
-
return band;
|
|
146
|
-
}
|
|
147
|
-
}
|
|
148
|
-
return 'custom';
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
/**
|
|
152
|
-
* Determines the appropriate organization role for an employee
|
|
153
|
-
* Based on department, employment type, and position
|
|
154
|
-
* @param {Object} employmentData - Employee's employment data
|
|
155
|
-
* @param {string} employmentData.department - Employee's department
|
|
156
|
-
* @param {string} employmentData.type - Employment type (full_time, intern, etc.)
|
|
157
|
-
* @param {string} employmentData.position - Job position/title
|
|
158
|
-
* @returns {string} Organization role key
|
|
159
|
-
*/
|
|
160
|
-
export function determineOrgRole(employmentData = {}) {
|
|
161
|
-
const { department, type: employmentType } = employmentData;
|
|
162
|
-
|
|
163
|
-
// Priority 1: Department-based mapping
|
|
164
|
-
if (department && ROLE_MAPPING.byDepartment[department]) {
|
|
165
|
-
return ROLE_MAPPING.byDepartment[department];
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
// Priority 2: Employment type mapping
|
|
169
|
-
if (employmentType && ROLE_MAPPING.byEmploymentType[employmentType]) {
|
|
170
|
-
return ROLE_MAPPING.byEmploymentType[employmentType];
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
// Priority 3: Default role
|
|
174
|
-
return ROLE_MAPPING.default;
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
export default HRM_CONFIG;
|
|
@@ -1,242 +0,0 @@
|
|
|
1
|
-
import logger from '../utils/logger.js';
|
|
2
|
-
|
|
3
|
-
export async function updateSalary({
|
|
4
|
-
EmployeeModel,
|
|
5
|
-
employeeId,
|
|
6
|
-
compensation = {},
|
|
7
|
-
effectiveFrom = new Date(),
|
|
8
|
-
context = {},
|
|
9
|
-
session = null
|
|
10
|
-
}) {
|
|
11
|
-
const employee = await EmployeeModel.findById(employeeId).session(session);
|
|
12
|
-
|
|
13
|
-
if (!employee) {
|
|
14
|
-
throw new Error('Employee not found');
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
if (employee.status === 'terminated') {
|
|
18
|
-
throw new Error('Cannot update salary for terminated employee');
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
const oldSalary = employee.compensation.netSalary;
|
|
22
|
-
|
|
23
|
-
if (compensation.baseAmount !== undefined) {
|
|
24
|
-
employee.compensation.baseAmount = compensation.baseAmount;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
if (compensation.frequency) {
|
|
28
|
-
employee.compensation.frequency = compensation.frequency;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
if (compensation.currency) {
|
|
32
|
-
employee.compensation.currency = compensation.currency;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
employee.compensation.effectiveFrom = effectiveFrom;
|
|
36
|
-
employee.updateSalaryCalculations();
|
|
37
|
-
|
|
38
|
-
await employee.save({ session });
|
|
39
|
-
|
|
40
|
-
logger.info('Salary updated', {
|
|
41
|
-
employeeId: employee.employeeId,
|
|
42
|
-
organizationId: employee.organizationId,
|
|
43
|
-
oldSalary,
|
|
44
|
-
newSalary: employee.compensation.netSalary,
|
|
45
|
-
effectiveFrom,
|
|
46
|
-
});
|
|
47
|
-
|
|
48
|
-
return employee;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
export async function addAllowance({
|
|
52
|
-
EmployeeModel,
|
|
53
|
-
employeeId,
|
|
54
|
-
type,
|
|
55
|
-
amount,
|
|
56
|
-
taxable = true,
|
|
57
|
-
recurring = true,
|
|
58
|
-
effectiveFrom = new Date(),
|
|
59
|
-
effectiveTo = null,
|
|
60
|
-
context = {},
|
|
61
|
-
session = null
|
|
62
|
-
}) {
|
|
63
|
-
const employee = await EmployeeModel.findById(employeeId).session(session);
|
|
64
|
-
|
|
65
|
-
if (!employee) {
|
|
66
|
-
throw new Error('Employee not found');
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
if (employee.status === 'terminated') {
|
|
70
|
-
throw new Error('Cannot add allowance to terminated employee');
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
if (!employee.compensation.allowances) {
|
|
74
|
-
employee.compensation.allowances = [];
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
employee.compensation.allowances.push({
|
|
78
|
-
type,
|
|
79
|
-
amount,
|
|
80
|
-
taxable,
|
|
81
|
-
recurring,
|
|
82
|
-
effectiveFrom,
|
|
83
|
-
effectiveTo,
|
|
84
|
-
});
|
|
85
|
-
|
|
86
|
-
employee.updateSalaryCalculations();
|
|
87
|
-
|
|
88
|
-
await employee.save({ session });
|
|
89
|
-
|
|
90
|
-
logger.info('Allowance added', {
|
|
91
|
-
employeeId: employee.employeeId,
|
|
92
|
-
organizationId: employee.organizationId,
|
|
93
|
-
type,
|
|
94
|
-
amount,
|
|
95
|
-
});
|
|
96
|
-
|
|
97
|
-
return employee;
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
export async function removeAllowance({
|
|
101
|
-
EmployeeModel,
|
|
102
|
-
employeeId,
|
|
103
|
-
type,
|
|
104
|
-
context = {},
|
|
105
|
-
session = null
|
|
106
|
-
}) {
|
|
107
|
-
const employee = await EmployeeModel.findById(employeeId).session(session);
|
|
108
|
-
|
|
109
|
-
if (!employee) {
|
|
110
|
-
throw new Error('Employee not found');
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
const before = employee.compensation.allowances?.length || 0;
|
|
114
|
-
employee.removeAllowance(type);
|
|
115
|
-
const after = employee.compensation.allowances?.length || 0;
|
|
116
|
-
|
|
117
|
-
if (before === after) {
|
|
118
|
-
throw new Error(`Allowance type '${type}' not found`);
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
await employee.save({ session });
|
|
122
|
-
|
|
123
|
-
logger.info('Allowance removed', {
|
|
124
|
-
employeeId: employee.employeeId,
|
|
125
|
-
organizationId: employee.organizationId,
|
|
126
|
-
type,
|
|
127
|
-
});
|
|
128
|
-
|
|
129
|
-
return employee;
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
export async function addDeduction({
|
|
133
|
-
EmployeeModel,
|
|
134
|
-
employeeId,
|
|
135
|
-
type,
|
|
136
|
-
amount,
|
|
137
|
-
auto = false,
|
|
138
|
-
recurring = true,
|
|
139
|
-
description = '',
|
|
140
|
-
effectiveFrom = new Date(),
|
|
141
|
-
effectiveTo = null,
|
|
142
|
-
context = {},
|
|
143
|
-
session = null
|
|
144
|
-
}) {
|
|
145
|
-
const employee = await EmployeeModel.findById(employeeId).session(session);
|
|
146
|
-
|
|
147
|
-
if (!employee) {
|
|
148
|
-
throw new Error('Employee not found');
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
if (employee.status === 'terminated') {
|
|
152
|
-
throw new Error('Cannot add deduction to terminated employee');
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
if (!employee.compensation.deductions) {
|
|
156
|
-
employee.compensation.deductions = [];
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
employee.compensation.deductions.push({
|
|
160
|
-
type,
|
|
161
|
-
amount,
|
|
162
|
-
auto,
|
|
163
|
-
recurring,
|
|
164
|
-
description,
|
|
165
|
-
effectiveFrom,
|
|
166
|
-
effectiveTo,
|
|
167
|
-
});
|
|
168
|
-
|
|
169
|
-
employee.updateSalaryCalculations();
|
|
170
|
-
|
|
171
|
-
await employee.save({ session });
|
|
172
|
-
|
|
173
|
-
logger.info('Deduction added', {
|
|
174
|
-
employeeId: employee.employeeId,
|
|
175
|
-
organizationId: employee.organizationId,
|
|
176
|
-
type,
|
|
177
|
-
amount,
|
|
178
|
-
auto,
|
|
179
|
-
});
|
|
180
|
-
|
|
181
|
-
return employee;
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
export async function removeDeduction({
|
|
185
|
-
EmployeeModel,
|
|
186
|
-
employeeId,
|
|
187
|
-
type,
|
|
188
|
-
context = {},
|
|
189
|
-
session = null
|
|
190
|
-
}) {
|
|
191
|
-
const employee = await EmployeeModel.findById(employeeId).session(session);
|
|
192
|
-
|
|
193
|
-
if (!employee) {
|
|
194
|
-
throw new Error('Employee not found');
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
const before = employee.compensation.deductions?.length || 0;
|
|
198
|
-
employee.removeDeduction(type);
|
|
199
|
-
const after = employee.compensation.deductions?.length || 0;
|
|
200
|
-
|
|
201
|
-
if (before === after) {
|
|
202
|
-
throw new Error(`Deduction type '${type}' not found`);
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
await employee.save({ session });
|
|
206
|
-
|
|
207
|
-
logger.info('Deduction removed', {
|
|
208
|
-
employeeId: employee.employeeId,
|
|
209
|
-
organizationId: employee.organizationId,
|
|
210
|
-
type,
|
|
211
|
-
});
|
|
212
|
-
|
|
213
|
-
return employee;
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
export async function updateBankDetails({
|
|
217
|
-
EmployeeModel,
|
|
218
|
-
employeeId,
|
|
219
|
-
bankDetails = {},
|
|
220
|
-
context = {},
|
|
221
|
-
session = null
|
|
222
|
-
}) {
|
|
223
|
-
const employee = await EmployeeModel.findById(employeeId).session(session);
|
|
224
|
-
|
|
225
|
-
if (!employee) {
|
|
226
|
-
throw new Error('Employee not found');
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
employee.bankDetails = {
|
|
230
|
-
...employee.bankDetails,
|
|
231
|
-
...bankDetails,
|
|
232
|
-
};
|
|
233
|
-
|
|
234
|
-
await employee.save({ session });
|
|
235
|
-
|
|
236
|
-
logger.info('Bank details updated', {
|
|
237
|
-
employeeId: employee.employeeId,
|
|
238
|
-
organizationId: employee.organizationId,
|
|
239
|
-
});
|
|
240
|
-
|
|
241
|
-
return employee;
|
|
242
|
-
}
|