@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/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
|
-
}
|
|
@@ -1,224 +0,0 @@
|
|
|
1
|
-
import logger from '../utils/logger.js';
|
|
2
|
-
import { HRM_CONFIG } from '../config.js';
|
|
3
|
-
import { EmployeeFactory } from '../factories/employee.factory.js';
|
|
4
|
-
import { employee as employeeQuery } from '../utils/query-builders.js';
|
|
5
|
-
import { isEmployed } from '../utils/validation.utils.js';
|
|
6
|
-
|
|
7
|
-
export async function hireEmployee({
|
|
8
|
-
EmployeeModel,
|
|
9
|
-
organizationId,
|
|
10
|
-
userId,
|
|
11
|
-
employment = {},
|
|
12
|
-
compensation = {},
|
|
13
|
-
bankDetails = {},
|
|
14
|
-
context = {},
|
|
15
|
-
session = null
|
|
16
|
-
}) {
|
|
17
|
-
const existingQuery = employeeQuery()
|
|
18
|
-
.forUser(userId)
|
|
19
|
-
.forOrganization(organizationId)
|
|
20
|
-
.employed()
|
|
21
|
-
.build();
|
|
22
|
-
|
|
23
|
-
const existing = await EmployeeModel.findOne(existingQuery).session(session);
|
|
24
|
-
|
|
25
|
-
if (existing) {
|
|
26
|
-
throw new Error('User is already an active employee in this organization');
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
const employeeData = EmployeeFactory.create({
|
|
30
|
-
userId,
|
|
31
|
-
organizationId,
|
|
32
|
-
employment,
|
|
33
|
-
compensation: {
|
|
34
|
-
...compensation,
|
|
35
|
-
currency: compensation.currency || HRM_CONFIG.payroll.defaultCurrency,
|
|
36
|
-
},
|
|
37
|
-
bankDetails,
|
|
38
|
-
});
|
|
39
|
-
|
|
40
|
-
const employee = await EmployeeModel.create([employeeData], { session });
|
|
41
|
-
|
|
42
|
-
logger.info('Employee hired', {
|
|
43
|
-
employeeId: employee[0].employeeId,
|
|
44
|
-
organizationId,
|
|
45
|
-
userId,
|
|
46
|
-
position: employment.position,
|
|
47
|
-
});
|
|
48
|
-
|
|
49
|
-
return employee[0];
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
export async function updateEmployment({
|
|
53
|
-
EmployeeModel,
|
|
54
|
-
employeeId,
|
|
55
|
-
updates = {},
|
|
56
|
-
context = {},
|
|
57
|
-
session = null
|
|
58
|
-
}) {
|
|
59
|
-
const employee = await EmployeeModel.findById(employeeId).session(session);
|
|
60
|
-
|
|
61
|
-
if (!employee) {
|
|
62
|
-
throw new Error('Employee not found');
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
if (employee.status === 'terminated') {
|
|
66
|
-
throw new Error('Cannot update terminated employee. Use re-hire instead.');
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
const allowedUpdates = ['department', 'position', 'employmentType', 'status', 'workSchedule'];
|
|
70
|
-
|
|
71
|
-
for (const [key, value] of Object.entries(updates)) {
|
|
72
|
-
if (allowedUpdates.includes(key)) {
|
|
73
|
-
employee[key] = value;
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
await employee.save({ session });
|
|
78
|
-
|
|
79
|
-
logger.info('Employee updated', {
|
|
80
|
-
employeeId: employee.employeeId,
|
|
81
|
-
organizationId: employee.organizationId,
|
|
82
|
-
updates: Object.keys(updates),
|
|
83
|
-
});
|
|
84
|
-
|
|
85
|
-
return employee;
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
export async function terminateEmployee({
|
|
89
|
-
EmployeeModel,
|
|
90
|
-
employeeId,
|
|
91
|
-
terminationDate = new Date(),
|
|
92
|
-
reason = 'resignation',
|
|
93
|
-
notes = '',
|
|
94
|
-
context = {},
|
|
95
|
-
session = null
|
|
96
|
-
}) {
|
|
97
|
-
const employee = await EmployeeModel.findById(employeeId).session(session);
|
|
98
|
-
|
|
99
|
-
if (!employee) {
|
|
100
|
-
throw new Error('Employee not found');
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
employee.terminate(reason, terminationDate);
|
|
104
|
-
|
|
105
|
-
if (notes) {
|
|
106
|
-
employee.notes = (employee.notes || '') + `\nTermination: ${notes}`;
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
await employee.save({ session });
|
|
110
|
-
|
|
111
|
-
logger.info('Employee terminated', {
|
|
112
|
-
employeeId: employee.employeeId,
|
|
113
|
-
organizationId: employee.organizationId,
|
|
114
|
-
reason,
|
|
115
|
-
});
|
|
116
|
-
|
|
117
|
-
return employee;
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
export async function reHireEmployee({
|
|
121
|
-
EmployeeModel,
|
|
122
|
-
employeeId,
|
|
123
|
-
hireDate = new Date(),
|
|
124
|
-
position = null,
|
|
125
|
-
department = null,
|
|
126
|
-
compensation = null,
|
|
127
|
-
context = {},
|
|
128
|
-
session = null
|
|
129
|
-
}) {
|
|
130
|
-
const employee = await EmployeeModel.findById(employeeId).session(session);
|
|
131
|
-
|
|
132
|
-
if (!employee) {
|
|
133
|
-
throw new Error('Employee not found');
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
if (!HRM_CONFIG.employment.allowReHiring) {
|
|
137
|
-
throw new Error('Re-hiring is not enabled');
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
employee.reHire(hireDate, position, department);
|
|
141
|
-
|
|
142
|
-
if (compensation) {
|
|
143
|
-
employee.compensation = {
|
|
144
|
-
...employee.compensation,
|
|
145
|
-
...compensation,
|
|
146
|
-
};
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
await employee.save({ session });
|
|
150
|
-
|
|
151
|
-
logger.info('Employee re-hired', {
|
|
152
|
-
employeeId: employee.employeeId,
|
|
153
|
-
organizationId: employee.organizationId,
|
|
154
|
-
});
|
|
155
|
-
|
|
156
|
-
return employee;
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
export async function getEmployeeList({
|
|
160
|
-
EmployeeModel,
|
|
161
|
-
organizationId,
|
|
162
|
-
filters = {},
|
|
163
|
-
pagination = {},
|
|
164
|
-
session = null
|
|
165
|
-
}) {
|
|
166
|
-
let queryBuilder = employeeQuery().forOrganization(organizationId);
|
|
167
|
-
|
|
168
|
-
if (filters.status) {
|
|
169
|
-
queryBuilder = queryBuilder.withStatus(filters.status);
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
if (filters.department) {
|
|
173
|
-
queryBuilder = queryBuilder.inDepartment(filters.department);
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
if (filters.employmentType) {
|
|
177
|
-
queryBuilder = queryBuilder.withEmploymentType(filters.employmentType);
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
if (filters.minSalary) {
|
|
181
|
-
queryBuilder = queryBuilder.whereGte('compensation.netSalary', filters.minSalary);
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
if (filters.maxSalary) {
|
|
185
|
-
queryBuilder = queryBuilder.whereLte('compensation.netSalary', filters.maxSalary);
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
const query = queryBuilder.build();
|
|
189
|
-
|
|
190
|
-
const options = {
|
|
191
|
-
page: pagination.page || 1,
|
|
192
|
-
limit: pagination.limit || 20,
|
|
193
|
-
sort: pagination.sort || { createdAt: -1 },
|
|
194
|
-
populate: [
|
|
195
|
-
{ path: 'userId', select: 'name email phone' },
|
|
196
|
-
],
|
|
197
|
-
...pagination,
|
|
198
|
-
};
|
|
199
|
-
|
|
200
|
-
const result = await EmployeeModel.paginate(query, options);
|
|
201
|
-
|
|
202
|
-
return result;
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
export async function getEmployeeById({
|
|
206
|
-
EmployeeModel,
|
|
207
|
-
employeeId,
|
|
208
|
-
populateUser = true,
|
|
209
|
-
session = null
|
|
210
|
-
}) {
|
|
211
|
-
let query = EmployeeModel.findById(employeeId).session(session);
|
|
212
|
-
|
|
213
|
-
if (populateUser) {
|
|
214
|
-
query = query.populate('userId', 'name email phone');
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
const employee = await query;
|
|
218
|
-
|
|
219
|
-
if (!employee) {
|
|
220
|
-
throw new Error('Employee not found');
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
return employee;
|
|
224
|
-
}
|