@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.
Files changed (45) hide show
  1. package/README.md +535 -574
  2. package/dist/attendance.calculator-BZcv2iii.d.ts +336 -0
  3. package/dist/calculators/index.d.ts +4 -0
  4. package/dist/calculators/index.js +439 -0
  5. package/dist/calculators/index.js.map +1 -0
  6. package/dist/core/index.d.ts +321 -0
  7. package/dist/core/index.js +1962 -0
  8. package/dist/core/index.js.map +1 -0
  9. package/dist/error-helpers-Bm6lMny2.d.ts +740 -0
  10. package/dist/index-BKLkuSAs.d.ts +3858 -0
  11. package/dist/index.d.ts +2684 -0
  12. package/dist/index.js +11454 -0
  13. package/dist/index.js.map +1 -0
  14. package/dist/payroll-states-DBt0XVm-.d.ts +598 -0
  15. package/dist/prorating.calculator-C33fWBQf.d.ts +135 -0
  16. package/dist/schemas/index.d.ts +4 -0
  17. package/dist/schemas/index.js +1472 -0
  18. package/dist/schemas/index.js.map +1 -0
  19. package/dist/types-bZdAJueH.d.ts +2271 -0
  20. package/dist/utils/index.d.ts +1007 -0
  21. package/dist/utils/index.js +1789 -0
  22. package/dist/utils/index.js.map +1 -0
  23. package/package.json +81 -24
  24. package/src/config.js +0 -177
  25. package/src/core/compensation.manager.js +0 -242
  26. package/src/core/employment.manager.js +0 -224
  27. package/src/core/payroll.manager.js +0 -499
  28. package/src/enums.js +0 -141
  29. package/src/factories/compensation.factory.js +0 -198
  30. package/src/factories/employee.factory.js +0 -173
  31. package/src/factories/payroll.factory.js +0 -247
  32. package/src/hrm.orchestrator.js +0 -139
  33. package/src/index.js +0 -172
  34. package/src/init.js +0 -41
  35. package/src/models/payroll-record.model.js +0 -126
  36. package/src/plugins/employee.plugin.js +0 -157
  37. package/src/schemas/employment.schema.js +0 -126
  38. package/src/services/compensation.service.js +0 -231
  39. package/src/services/employee.service.js +0 -162
  40. package/src/services/payroll.service.js +0 -213
  41. package/src/utils/calculation.utils.js +0 -91
  42. package/src/utils/date.utils.js +0 -120
  43. package/src/utils/logger.js +0 -36
  44. package/src/utils/query-builders.js +0 -185
  45. 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": "1.0.0",
4
- "description": "Modern HRM and payroll management library for Node.js applications",
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
- "main": "./src/index.js",
6
+ "sideEffects": false,
7
+ "main": "./dist/index.js",
8
+ "module": "./dist/index.js",
9
+ "types": "./dist/index.d.ts",
7
10
  "exports": {
8
- ".": "./src/index.js",
9
- "./init": "./src/init.js",
10
- "./service": "./src/service.js",
11
- "./adapters/*": "./src/adapters/*.js",
12
- "./events/*": "./src/events/*.js",
13
- "./workflows/*": "./src/workflows/*.js",
14
- "./schemas/*": "./src/schemas/*.js"
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
- "src/**/*.js",
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
- "nodejs",
30
- "mongoose"
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
- "mongoose": "^8.0.0"
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
- "mongoose": "^8.0.0",
47
- "mongoose-paginate-v2": "^1.8.0",
48
- "mongoose-aggregate-paginate-v2": "^1.1.0"
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
- "example:basic": "node test/examples/basic-usage.js",
55
- "example:mongoose": "node test/examples/with-mongoose.js",
56
- "example:workflow": "node test/examples/full-workflow.js",
57
- "test": "npm run example:basic && npm run example:mongoose && npm run example:workflow"
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
- }