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

Potentially problematic release.


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

Files changed (46) 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 +61 -25
  24. package/payroll.d.ts +0 -241
  25. package/src/config.js +0 -177
  26. package/src/core/compensation.manager.js +0 -242
  27. package/src/core/employment.manager.js +0 -224
  28. package/src/core/payroll.manager.js +0 -499
  29. package/src/enums.js +0 -141
  30. package/src/factories/compensation.factory.js +0 -198
  31. package/src/factories/employee.factory.js +0 -173
  32. package/src/factories/payroll.factory.js +0 -247
  33. package/src/hrm.orchestrator.js +0 -139
  34. package/src/index.js +0 -172
  35. package/src/init.js +0 -41
  36. package/src/models/payroll-record.model.js +0 -126
  37. package/src/plugins/employee.plugin.js +0 -157
  38. package/src/schemas/employment.schema.js +0 -126
  39. package/src/services/compensation.service.js +0 -231
  40. package/src/services/employee.service.js +0 -162
  41. package/src/services/payroll.service.js +0 -213
  42. package/src/utils/calculation.utils.js +0 -91
  43. package/src/utils/date.utils.js +0 -120
  44. package/src/utils/logger.js +0 -36
  45. package/src/utils/query-builders.js +0 -185
  46. package/src/utils/validation.utils.js +0 -122
@@ -1,499 +0,0 @@
1
- import mongoose from 'mongoose';
2
- import logger from '../utils/logger.js';
3
- import { HRM_CONFIG } from '../config.js';
4
- import { HRM_TRANSACTION_CATEGORIES } from '../enums.js';
5
- import { PayrollFactory } from '../factories/payroll.factory.js';
6
- import { payroll as payrollQuery } from '../utils/query-builders.js';
7
- import {
8
- calculateGross,
9
- calculateNet,
10
- sumAllowances,
11
- sumDeductions,
12
- } from '../utils/calculation.utils.js';
13
- import {
14
- getPayPeriod,
15
- diffInDays,
16
- addMonths,
17
- } from '../utils/date.utils.js';
18
-
19
- export async function processSalary({
20
- EmployeeModel,
21
- PayrollRecordModel,
22
- TransactionModel,
23
- AttendanceModel = null,
24
- employeeId,
25
- month,
26
- year,
27
- paymentDate = new Date(),
28
- paymentMethod = 'bank',
29
- context = {},
30
- session = null
31
- }) {
32
- const employee = await EmployeeModel.findById(employeeId)
33
- .populate('userId', 'name email')
34
- .session(session);
35
-
36
- if (!employee) {
37
- throw new Error('Employee not found');
38
- }
39
-
40
- if (!employee.canReceiveSalary()) {
41
- throw new Error('Employee is not eligible to receive salary');
42
- }
43
-
44
- const existingQuery = payrollQuery()
45
- .forEmployee(employeeId)
46
- .forPeriod(month, year)
47
- .whereIn('status', ['paid', 'processing'])
48
- .build();
49
-
50
- const existing = await PayrollRecordModel.findOne(existingQuery).session(session);
51
-
52
- if (existing) {
53
- throw new Error(`Salary already processed for ${month}/${year}`);
54
- }
55
-
56
- const period = calculatePayPeriod(month, year, paymentDate);
57
-
58
- const breakdown = await calculateSalaryBreakdown({
59
- employee,
60
- period,
61
- AttendanceModel,
62
- session
63
- });
64
-
65
- const payrollRecord = await PayrollRecordModel.create([{
66
- organizationId: employee.organizationId,
67
- employeeId: employee._id,
68
- userId: employee.userId._id,
69
- period,
70
- breakdown,
71
- status: 'processing',
72
- paymentMethod,
73
- processedBy: context.userId,
74
- }], { session });
75
-
76
- const transaction = await TransactionModel.create([{
77
- organizationId: employee.organizationId,
78
- type: 'expense',
79
- category: HRM_TRANSACTION_CATEGORIES.SALARY,
80
- amount: breakdown.netSalary,
81
- method: paymentMethod,
82
- status: 'completed',
83
- date: paymentDate,
84
- referenceId: employee._id,
85
- referenceModel: 'Employee',
86
- handledBy: context.userId,
87
- notes: `Salary payment - ${employee.userId.name} (${period.month}/${period.year})`,
88
- metadata: {
89
- employeeId: employee.employeeId,
90
- payrollRecordId: payrollRecord[0]._id,
91
- period: { month, year },
92
- breakdown: {
93
- base: breakdown.baseAmount,
94
- allowances: sumAllowances(breakdown.allowances),
95
- deductions: sumDeductions(breakdown.deductions),
96
- gross: breakdown.grossSalary,
97
- net: breakdown.netSalary,
98
- }
99
- }
100
- }], { session });
101
-
102
- payrollRecord[0].markAsPaid(transaction[0]._id, paymentDate);
103
- await payrollRecord[0].save({ session });
104
-
105
- await updatePayrollStats(employee, breakdown.netSalary, paymentDate, session);
106
-
107
- logger.info('Salary processed', {
108
- employeeId: employee.employeeId,
109
- organizationId: employee.organizationId,
110
- month,
111
- year,
112
- amount: breakdown.netSalary,
113
- transactionId: transaction[0]._id,
114
- });
115
-
116
- return {
117
- payrollRecord: payrollRecord[0],
118
- transaction: transaction[0],
119
- employee
120
- };
121
- }
122
-
123
- export async function processBulkPayroll({
124
- EmployeeModel,
125
- PayrollRecordModel,
126
- TransactionModel,
127
- AttendanceModel = null,
128
- organizationId,
129
- month,
130
- year,
131
- employeeIds = [],
132
- paymentDate = new Date(),
133
- paymentMethod = 'bank',
134
- context = {},
135
- session: providedSession = null
136
- }) {
137
- const useSession = providedSession || await mongoose.startSession();
138
- const shouldCommit = !providedSession;
139
-
140
- try {
141
- if (shouldCommit) await useSession.startTransaction();
142
-
143
- const query = {
144
- organizationId,
145
- status: 'active',
146
- };
147
-
148
- if (employeeIds.length > 0) {
149
- query._id = { $in: employeeIds };
150
- }
151
-
152
- const employees = await EmployeeModel.find(query).session(useSession);
153
-
154
- const results = {
155
- successful: [],
156
- failed: [],
157
- total: employees.length,
158
- };
159
-
160
- for (const employee of employees) {
161
- try {
162
- const result = await processSalary({
163
- EmployeeModel,
164
- PayrollRecordModel,
165
- TransactionModel,
166
- AttendanceModel,
167
- employeeId: employee._id,
168
- month,
169
- year,
170
- paymentDate,
171
- paymentMethod,
172
- context,
173
- session: useSession
174
- });
175
-
176
- results.successful.push({
177
- employeeId: employee.employeeId,
178
- amount: result.payrollRecord.breakdown.netSalary,
179
- transactionId: result.transaction._id,
180
- });
181
- } catch (error) {
182
- results.failed.push({
183
- employeeId: employee.employeeId,
184
- error: error.message,
185
- });
186
-
187
- logger.error('Failed to process salary', {
188
- employeeId: employee.employeeId,
189
- error: error.message,
190
- });
191
- }
192
- }
193
-
194
- if (shouldCommit) await useSession.commitTransaction();
195
-
196
- logger.info('Bulk payroll processed', {
197
- organizationId,
198
- month,
199
- year,
200
- total: results.total,
201
- successful: results.successful.length,
202
- failed: results.failed.length,
203
- });
204
-
205
- return results;
206
- } catch (error) {
207
- if (shouldCommit) await useSession.abortTransaction();
208
- throw error;
209
- } finally {
210
- if (shouldCommit) await useSession.endSession();
211
- }
212
- }
213
-
214
- export async function getPayrollHistory({
215
- PayrollRecordModel,
216
- employeeId = null,
217
- organizationId = null,
218
- month = null,
219
- year = null,
220
- status = null,
221
- pagination = {},
222
- session = null
223
- }) {
224
- let queryBuilder = payrollQuery();
225
-
226
- if (employeeId) {
227
- queryBuilder = queryBuilder.forEmployee(employeeId);
228
- }
229
-
230
- if (organizationId) {
231
- queryBuilder = queryBuilder.forOrganization(organizationId);
232
- }
233
-
234
- if (month || year) {
235
- queryBuilder = queryBuilder.forPeriod(month, year);
236
- }
237
-
238
- if (status) {
239
- queryBuilder = queryBuilder.withStatus(status);
240
- }
241
-
242
- const query = queryBuilder.build();
243
-
244
- const options = {
245
- page: pagination.page || 1,
246
- limit: pagination.limit || 20,
247
- sort: pagination.sort || { 'period.year': -1, 'period.month': -1 },
248
- populate: [
249
- { path: 'employeeId', select: 'employeeId position department' },
250
- { path: 'userId', select: 'name email' },
251
- { path: 'transactionId', select: 'amount method status date' },
252
- ],
253
- ...pagination,
254
- };
255
-
256
- const result = await PayrollRecordModel.paginate(query, options);
257
-
258
- return result;
259
- }
260
-
261
- export async function getPayrollSummary({
262
- PayrollRecordModel,
263
- organizationId,
264
- month = null,
265
- year = null,
266
- session = null
267
- }) {
268
- const query = { organizationId };
269
-
270
- if (month) query['period.month'] = month;
271
- if (year) query['period.year'] = year;
272
-
273
- const summary = await PayrollRecordModel.aggregate([
274
- { $match: query },
275
- {
276
- $group: {
277
- _id: null,
278
- totalGross: { $sum: '$breakdown.grossSalary' },
279
- totalNet: { $sum: '$breakdown.netSalary' },
280
- totalDeductions: { $sum: { $sum: '$breakdown.deductions.amount' } },
281
- employeeCount: { $sum: 1 },
282
- paidCount: {
283
- $sum: { $cond: [{ $eq: ['$status', 'paid'] }, 1, 0] }
284
- },
285
- pendingCount: {
286
- $sum: { $cond: [{ $eq: ['$status', 'pending'] }, 1, 0] }
287
- },
288
- }
289
- }
290
- ]).session(session);
291
-
292
- return summary[0] || {
293
- totalGross: 0,
294
- totalNet: 0,
295
- totalDeductions: 0,
296
- employeeCount: 0,
297
- paidCount: 0,
298
- pendingCount: 0,
299
- };
300
- }
301
-
302
- export async function exportPayrollData({
303
- PayrollRecordModel,
304
- organizationId,
305
- startDate,
306
- endDate,
307
- format = 'json',
308
- session = null
309
- }) {
310
- const query = {
311
- organizationId,
312
- 'period.payDate': {
313
- $gte: startDate,
314
- $lte: endDate
315
- }
316
- };
317
-
318
- const records = await PayrollRecordModel.find(query)
319
- .populate('employeeId', 'employeeId position department')
320
- .populate('userId', 'name email')
321
- .populate('transactionId', 'amount method status date')
322
- .sort({ 'period.year': -1, 'period.month': -1 })
323
- .session(session);
324
-
325
- await PayrollRecordModel.updateMany(
326
- query,
327
- { exported: true, exportedAt: new Date() }
328
- ).session(session);
329
-
330
- logger.info('Payroll data exported', {
331
- organizationId,
332
- count: records.length,
333
- startDate,
334
- endDate,
335
- });
336
-
337
- return records;
338
- }
339
-
340
- function calculatePayPeriod(month, year, paymentDate) {
341
- return {
342
- ...getPayPeriod(month, year),
343
- payDate: paymentDate
344
- };
345
- }
346
-
347
- async function calculateSalaryBreakdown({
348
- employee,
349
- period,
350
- AttendanceModel,
351
- session
352
- }) {
353
- const comp = employee.compensation;
354
- let baseAmount = comp.baseAmount;
355
-
356
- const proRateInfo = calculateProRating(employee.hireDate, period);
357
- if (proRateInfo.isProRated) {
358
- baseAmount = Math.round(baseAmount * proRateInfo.ratio);
359
- }
360
-
361
- let attendanceDeduction = 0;
362
- if (AttendanceModel && HRM_CONFIG.payroll.attendanceIntegration) {
363
- attendanceDeduction = await calculateAttendanceDeduction({
364
- AttendanceModel,
365
- employeeId: employee._id,
366
- organizationId: employee.organizationId,
367
- period,
368
- dailyRate: baseAmount / proRateInfo.totalDays,
369
- session
370
- });
371
- }
372
-
373
- const allowances = (comp.allowances || []).map(a => ({
374
- type: a.type,
375
- amount: a.amount,
376
- taxable: a.taxable
377
- }));
378
-
379
- const deductions = (comp.deductions || [])
380
- .filter(d => d.auto || d.recurring)
381
- .map(d => ({
382
- type: d.type,
383
- amount: d.amount,
384
- description: d.description
385
- }));
386
-
387
- if (attendanceDeduction > 0) {
388
- deductions.push({
389
- type: 'absence',
390
- amount: attendanceDeduction,
391
- description: 'Unpaid leave deduction'
392
- });
393
- }
394
-
395
- const grossSalary = calculateGross(baseAmount, allowances);
396
- const netSalary = calculateNet(grossSalary, deductions);
397
-
398
- return {
399
- baseAmount,
400
- allowances,
401
- deductions,
402
- grossSalary,
403
- netSalary,
404
- workingDays: proRateInfo.totalDays,
405
- actualDays: proRateInfo.actualDays,
406
- proRatedAmount: proRateInfo.isProRated ? baseAmount : 0,
407
- attendanceDeduction,
408
- };
409
- }
410
-
411
- function calculateProRating(hireDate, period) {
412
- const periodStart = period.startDate;
413
- const periodEnd = period.endDate;
414
- const totalDays = diffInDays(periodStart, periodEnd) + 1;
415
-
416
- if (hireDate <= periodStart) {
417
- return {
418
- isProRated: false,
419
- totalDays,
420
- actualDays: totalDays,
421
- ratio: 1
422
- };
423
- }
424
-
425
- if (hireDate > periodStart && hireDate <= periodEnd) {
426
- const actualDays = diffInDays(hireDate, periodEnd) + 1;
427
- const ratio = actualDays / totalDays;
428
-
429
- return {
430
- isProRated: true,
431
- totalDays,
432
- actualDays,
433
- ratio
434
- };
435
- }
436
-
437
- return {
438
- isProRated: false,
439
- totalDays,
440
- actualDays: 0,
441
- ratio: 0
442
- };
443
- }
444
-
445
- async function calculateAttendanceDeduction({
446
- AttendanceModel,
447
- employeeId,
448
- organizationId,
449
- period,
450
- dailyRate,
451
- session
452
- }) {
453
- try {
454
- const attendance = await AttendanceModel.findOne({
455
- tenantId: organizationId,
456
- targetId: employeeId,
457
- targetModel: 'Employee',
458
- year: period.year,
459
- month: period.month
460
- }).session(session);
461
-
462
- if (!attendance) return 0;
463
-
464
- const totalDays = Math.ceil((period.endDate - period.startDate) / (24 * 60 * 60 * 1000)) + 1;
465
-
466
- // ⭐ Smart work days calculation (accounts for full days, half days, paid leave)
467
- // totalWorkDays = fullDays + (halfDays * 0.5) + paidLeaveDays
468
- // This is automatically calculated by the attendance model based on attendance types
469
- const workedDays = attendance.totalWorkDays || 0;
470
-
471
- const absentDays = Math.max(0, totalDays - workedDays);
472
-
473
- return Math.round(absentDays * dailyRate);
474
- } catch (error) {
475
- logger.warn('Failed to calculate attendance deduction', {
476
- employeeId,
477
- error: error.message
478
- });
479
- return 0;
480
- }
481
- }
482
-
483
- async function updatePayrollStats(employee, amount, paymentDate, session) {
484
- if (!employee.payrollStats) {
485
- employee.payrollStats = {};
486
- }
487
-
488
- employee.payrollStats.totalPaid = (employee.payrollStats.totalPaid || 0) + amount;
489
- employee.payrollStats.lastPaymentDate = paymentDate;
490
- employee.payrollStats.paymentsThisYear = (employee.payrollStats.paymentsThisYear || 0) + 1;
491
-
492
- const avgMonthly = employee.payrollStats.totalPaid / employee.payrollStats.paymentsThisYear;
493
- employee.payrollStats.averageMonthly = Math.round(avgMonthly);
494
-
495
- employee.payrollStats.nextPaymentDate = addMonths(paymentDate, 1);
496
- employee.payrollStats.updatedAt = new Date();
497
-
498
- await employee.save({ session });
499
- }
package/src/enums.js DELETED
@@ -1,141 +0,0 @@
1
- export const EMPLOYMENT_TYPE = {
2
- FULL_TIME: 'full_time',
3
- PART_TIME: 'part_time',
4
- CONTRACT: 'contract',
5
- INTERN: 'intern',
6
- CONSULTANT: 'consultant',
7
- };
8
-
9
- export const EMPLOYMENT_TYPE_VALUES = Object.values(EMPLOYMENT_TYPE);
10
-
11
- export const EMPLOYEE_STATUS = {
12
- ACTIVE: 'active',
13
- ON_LEAVE: 'on_leave',
14
- SUSPENDED: 'suspended',
15
- TERMINATED: 'terminated',
16
- };
17
-
18
- export const EMPLOYEE_STATUS_VALUES = Object.values(EMPLOYEE_STATUS);
19
-
20
- export const DEPARTMENT = {
21
- MANAGEMENT: 'management',
22
- TRAINING: 'training',
23
- SALES: 'sales',
24
- OPERATIONS: 'operations',
25
- SUPPORT: 'support',
26
- HR: 'hr',
27
- MAINTENANCE: 'maintenance',
28
- MARKETING: 'marketing',
29
- };
30
-
31
- export const DEPARTMENT_VALUES = Object.values(DEPARTMENT);
32
-
33
- export const PAYMENT_FREQUENCY = {
34
- MONTHLY: 'monthly',
35
- BI_WEEKLY: 'bi_weekly',
36
- WEEKLY: 'weekly',
37
- HOURLY: 'hourly',
38
- DAILY: 'daily',
39
- };
40
-
41
- export const PAYMENT_FREQUENCY_VALUES = Object.values(PAYMENT_FREQUENCY);
42
-
43
- export const ALLOWANCE_TYPE = {
44
- HOUSING: 'housing',
45
- TRANSPORT: 'transport',
46
- MEAL: 'meal',
47
- MOBILE: 'mobile',
48
- MEDICAL: 'medical',
49
- EDUCATION: 'education',
50
- OTHER: 'other',
51
- };
52
-
53
- export const ALLOWANCE_TYPE_VALUES = Object.values(ALLOWANCE_TYPE);
54
-
55
- export const DEDUCTION_TYPE = {
56
- TAX: 'tax',
57
- LOAN: 'loan',
58
- ADVANCE: 'advance',
59
- PROVIDENT_FUND: 'provident_fund',
60
- INSURANCE: 'insurance',
61
- ABSENCE: 'absence',
62
- OTHER: 'other',
63
- };
64
-
65
- export const DEDUCTION_TYPE_VALUES = Object.values(DEDUCTION_TYPE);
66
-
67
- export const PAYROLL_STATUS = {
68
- PENDING: 'pending',
69
- PROCESSING: 'processing',
70
- PAID: 'paid',
71
- FAILED: 'failed',
72
- CANCELLED: 'cancelled',
73
- };
74
-
75
- export const PAYROLL_STATUS_VALUES = Object.values(PAYROLL_STATUS);
76
-
77
- export const TERMINATION_REASON = {
78
- RESIGNATION: 'resignation',
79
- RETIREMENT: 'retirement',
80
- TERMINATION: 'termination',
81
- CONTRACT_END: 'contract_end',
82
- MUTUAL_AGREEMENT: 'mutual_agreement',
83
- OTHER: 'other',
84
- };
85
-
86
- export const TERMINATION_REASON_VALUES = Object.values(TERMINATION_REASON);
87
-
88
- export const PAYMENT_METHOD = {
89
- BANK: 'bank',
90
- CASH: 'cash',
91
- BKASH: 'bkash',
92
- NAGAD: 'nagad',
93
- ROCKET: 'rocket',
94
- };
95
-
96
- export const PAYMENT_METHOD_VALUES = Object.values(PAYMENT_METHOD);
97
-
98
- /**
99
- * HRM Library Transaction Categories
100
- * Categories managed by HRM workflows (not manually creatable)
101
- */
102
- export const HRM_TRANSACTION_CATEGORIES = {
103
- SALARY: 'salary', // Regular salary payments
104
- BONUS: 'bonus', // Performance/festival bonuses
105
- COMMISSION: 'commission', // Sales commissions
106
- OVERTIME: 'overtime', // Overtime payments
107
- SEVERANCE: 'severance', // Severance/termination pay
108
- };
109
-
110
- export const HRM_CATEGORY_VALUES = Object.values(HRM_TRANSACTION_CATEGORIES);
111
-
112
- /**
113
- * Check if category is HRM-managed (created by HRM workflows only)
114
- */
115
- export function isHRMManagedCategory(category) {
116
- return HRM_CATEGORY_VALUES.includes(category);
117
- }
118
-
119
- export default {
120
- EMPLOYMENT_TYPE,
121
- EMPLOYMENT_TYPE_VALUES,
122
- EMPLOYEE_STATUS,
123
- EMPLOYEE_STATUS_VALUES,
124
- DEPARTMENT,
125
- DEPARTMENT_VALUES,
126
- PAYMENT_FREQUENCY,
127
- PAYMENT_FREQUENCY_VALUES,
128
- PAYMENT_METHOD,
129
- PAYMENT_METHOD_VALUES,
130
- ALLOWANCE_TYPE,
131
- ALLOWANCE_TYPE_VALUES,
132
- DEDUCTION_TYPE,
133
- DEDUCTION_TYPE_VALUES,
134
- PAYROLL_STATUS,
135
- PAYROLL_STATUS_VALUES,
136
- TERMINATION_REASON,
137
- TERMINATION_REASON_VALUES,
138
- HRM_TRANSACTION_CATEGORIES,
139
- HRM_CATEGORY_VALUES,
140
- isHRMManagedCategory,
141
- };