@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.
Files changed (68) hide show
  1. package/README.md +168 -489
  2. package/dist/core/index.d.ts +480 -0
  3. package/dist/core/index.js +971 -0
  4. package/dist/core/index.js.map +1 -0
  5. package/dist/index-CTjHlCzz.d.ts +721 -0
  6. package/dist/index.d.ts +967 -0
  7. package/dist/index.js +4352 -0
  8. package/dist/index.js.map +1 -0
  9. package/dist/payroll.d.ts +233 -0
  10. package/dist/payroll.js +2103 -0
  11. package/dist/payroll.js.map +1 -0
  12. package/dist/plugin-D9mOr3_d.d.ts +333 -0
  13. package/dist/schemas/index.d.ts +2869 -0
  14. package/dist/schemas/index.js +440 -0
  15. package/dist/schemas/index.js.map +1 -0
  16. package/dist/services/index.d.ts +3 -0
  17. package/dist/services/index.js +1696 -0
  18. package/dist/services/index.js.map +1 -0
  19. package/dist/types-BSYyX2KJ.d.ts +671 -0
  20. package/dist/utils/index.d.ts +873 -0
  21. package/dist/utils/index.js +1046 -0
  22. package/dist/utils/index.js.map +1 -0
  23. package/package.json +54 -37
  24. package/dist/types/config.d.ts +0 -162
  25. package/dist/types/core/compensation.manager.d.ts +0 -54
  26. package/dist/types/core/employment.manager.d.ts +0 -49
  27. package/dist/types/core/payroll.manager.d.ts +0 -60
  28. package/dist/types/enums.d.ts +0 -117
  29. package/dist/types/factories/compensation.factory.d.ts +0 -196
  30. package/dist/types/factories/employee.factory.d.ts +0 -149
  31. package/dist/types/factories/payroll.factory.d.ts +0 -319
  32. package/dist/types/hrm.orchestrator.d.ts +0 -47
  33. package/dist/types/index.d.ts +0 -20
  34. package/dist/types/init.d.ts +0 -30
  35. package/dist/types/models/payroll-record.model.d.ts +0 -3
  36. package/dist/types/plugins/employee.plugin.d.ts +0 -2
  37. package/dist/types/schemas/employment.schema.d.ts +0 -959
  38. package/dist/types/services/compensation.service.d.ts +0 -94
  39. package/dist/types/services/employee.service.d.ts +0 -28
  40. package/dist/types/services/payroll.service.d.ts +0 -30
  41. package/dist/types/utils/calculation.utils.d.ts +0 -26
  42. package/dist/types/utils/date.utils.d.ts +0 -35
  43. package/dist/types/utils/logger.d.ts +0 -12
  44. package/dist/types/utils/query-builders.d.ts +0 -83
  45. package/dist/types/utils/validation.utils.d.ts +0 -33
  46. package/payroll.d.ts +0 -241
  47. package/src/config.js +0 -177
  48. package/src/core/compensation.manager.js +0 -242
  49. package/src/core/employment.manager.js +0 -224
  50. package/src/core/payroll.manager.js +0 -499
  51. package/src/enums.js +0 -141
  52. package/src/factories/compensation.factory.js +0 -198
  53. package/src/factories/employee.factory.js +0 -173
  54. package/src/factories/payroll.factory.js +0 -413
  55. package/src/hrm.orchestrator.js +0 -139
  56. package/src/index.js +0 -172
  57. package/src/init.js +0 -62
  58. package/src/models/payroll-record.model.js +0 -126
  59. package/src/plugins/employee.plugin.js +0 -164
  60. package/src/schemas/employment.schema.js +0 -126
  61. package/src/services/compensation.service.js +0 -231
  62. package/src/services/employee.service.js +0 -162
  63. package/src/services/payroll.service.js +0 -213
  64. package/src/utils/calculation.utils.js +0 -91
  65. package/src/utils/date.utils.js +0 -120
  66. package/src/utils/logger.js +0 -36
  67. package/src/utils/query-builders.js +0 -185
  68. 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
- }