@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
|
@@ -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
|
-
};
|