@classytic/payroll 1.0.2 → 2.3.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.

Potentially problematic release.


This version of @classytic/payroll might be problematic. Click here for more details.

Files changed (78) hide show
  1. package/README.md +2599 -574
  2. package/dist/calculators/index.d.ts +433 -0
  3. package/dist/calculators/index.js +283 -0
  4. package/dist/calculators/index.js.map +1 -0
  5. package/dist/core/index.d.ts +314 -0
  6. package/dist/core/index.js +1166 -0
  7. package/dist/core/index.js.map +1 -0
  8. package/dist/employee-identity-DXhgOgXE.d.ts +473 -0
  9. package/dist/employee.factory-BlZqhiCk.d.ts +189 -0
  10. package/dist/idempotency-Cw2CWicb.d.ts +52 -0
  11. package/dist/index.d.ts +902 -0
  12. package/dist/index.js +9108 -0
  13. package/dist/index.js.map +1 -0
  14. package/dist/jurisdiction/index.d.ts +660 -0
  15. package/dist/jurisdiction/index.js +533 -0
  16. package/dist/jurisdiction/index.js.map +1 -0
  17. package/dist/payroll.d.ts +429 -0
  18. package/dist/payroll.js +5192 -0
  19. package/dist/payroll.js.map +1 -0
  20. package/dist/schemas/index.d.ts +3262 -0
  21. package/dist/schemas/index.js +780 -0
  22. package/dist/schemas/index.js.map +1 -0
  23. package/dist/services/index.d.ts +582 -0
  24. package/dist/services/index.js +2172 -0
  25. package/dist/services/index.js.map +1 -0
  26. package/dist/shift-compliance/index.d.ts +1171 -0
  27. package/dist/shift-compliance/index.js +1479 -0
  28. package/dist/shift-compliance/index.js.map +1 -0
  29. package/dist/types-BN3K_Uhr.d.ts +1842 -0
  30. package/dist/utils/index.d.ts +893 -0
  31. package/dist/utils/index.js +1515 -0
  32. package/dist/utils/index.js.map +1 -0
  33. package/package.json +72 -37
  34. package/dist/types/config.d.ts +0 -162
  35. package/dist/types/core/compensation.manager.d.ts +0 -54
  36. package/dist/types/core/employment.manager.d.ts +0 -49
  37. package/dist/types/core/payroll.manager.d.ts +0 -60
  38. package/dist/types/enums.d.ts +0 -117
  39. package/dist/types/factories/compensation.factory.d.ts +0 -196
  40. package/dist/types/factories/employee.factory.d.ts +0 -149
  41. package/dist/types/factories/payroll.factory.d.ts +0 -319
  42. package/dist/types/hrm.orchestrator.d.ts +0 -47
  43. package/dist/types/index.d.ts +0 -20
  44. package/dist/types/init.d.ts +0 -30
  45. package/dist/types/models/payroll-record.model.d.ts +0 -3
  46. package/dist/types/plugins/employee.plugin.d.ts +0 -2
  47. package/dist/types/schemas/employment.schema.d.ts +0 -959
  48. package/dist/types/services/compensation.service.d.ts +0 -94
  49. package/dist/types/services/employee.service.d.ts +0 -28
  50. package/dist/types/services/payroll.service.d.ts +0 -30
  51. package/dist/types/utils/calculation.utils.d.ts +0 -26
  52. package/dist/types/utils/date.utils.d.ts +0 -35
  53. package/dist/types/utils/logger.d.ts +0 -12
  54. package/dist/types/utils/query-builders.d.ts +0 -83
  55. package/dist/types/utils/validation.utils.d.ts +0 -33
  56. package/payroll.d.ts +0 -241
  57. package/src/config.js +0 -177
  58. package/src/core/compensation.manager.js +0 -242
  59. package/src/core/employment.manager.js +0 -224
  60. package/src/core/payroll.manager.js +0 -499
  61. package/src/enums.js +0 -141
  62. package/src/factories/compensation.factory.js +0 -198
  63. package/src/factories/employee.factory.js +0 -173
  64. package/src/factories/payroll.factory.js +0 -413
  65. package/src/hrm.orchestrator.js +0 -139
  66. package/src/index.js +0 -172
  67. package/src/init.js +0 -62
  68. package/src/models/payroll-record.model.js +0 -126
  69. package/src/plugins/employee.plugin.js +0 -164
  70. package/src/schemas/employment.schema.js +0 -126
  71. package/src/services/compensation.service.js +0 -231
  72. package/src/services/employee.service.js +0 -162
  73. package/src/services/payroll.service.js +0 -213
  74. package/src/utils/calculation.utils.js +0 -91
  75. package/src/utils/date.utils.js +0 -120
  76. package/src/utils/logger.js +0 -36
  77. package/src/utils/query-builders.js +0 -185
  78. 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
- }