@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.

Potentially problematic release.


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

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/init.js DELETED
@@ -1,62 +0,0 @@
1
- import { hrm } from './hrm.orchestrator.js';
2
- import logger, { setLogger } from './utils/logger.js';
3
-
4
- let initialized = false;
5
-
6
- /**
7
- * Initialize HRM/Payroll framework
8
- *
9
- * @param {Object} options - Initialization options
10
- * @param {Model} options.EmployeeModel - Employee model (required)
11
- * @param {Model} options.PayrollRecordModel - Payroll record model (required)
12
- * @param {Model} options.TransactionModel - Transaction model (required)
13
- * @param {Model} options.AttendanceModel - Optional attendance model for integration
14
- * @param {Object} options.logger - Optional custom logger
15
- *
16
- * @example Multi-Tenant (default)
17
- * initializeHRM({
18
- * EmployeeModel,
19
- * PayrollRecordModel,
20
- * TransactionModel
21
- * });
22
- *
23
- * @example Single-Tenant
24
- * // For single-tenant apps, add organizationId with default value in your Employee schema:
25
- * // organizationId: { type: ObjectId, required: true, default: () => FIXED_ORG_ID }
26
- */
27
- export function initializeHRM({ EmployeeModel, PayrollRecordModel, TransactionModel, AttendanceModel = null, logger: customLogger }) {
28
- // Allow users to inject their own logger
29
- if (customLogger) {
30
- setLogger(customLogger);
31
- }
32
-
33
- if (initialized) {
34
- logger.warn('HRM already initialized, skipping');
35
- return;
36
- }
37
-
38
- if (!EmployeeModel || !PayrollRecordModel || !TransactionModel) {
39
- throw new Error(
40
- 'HRM initialization requires EmployeeModel, PayrollRecordModel, and TransactionModel'
41
- );
42
- }
43
-
44
- hrm.configure({
45
- EmployeeModel,
46
- PayrollRecordModel,
47
- TransactionModel,
48
- AttendanceModel,
49
- });
50
-
51
- initialized = true;
52
-
53
- logger.info('HRM library initialized', {
54
- hasAttendanceIntegration: !!AttendanceModel,
55
- });
56
- }
57
-
58
- export function isInitialized() {
59
- return initialized;
60
- }
61
-
62
- export default initializeHRM;
@@ -1,126 +0,0 @@
1
- import mongoose from 'mongoose';
2
- import mongoosePaginate from 'mongoose-paginate-v2';
3
- import aggregatePaginate from 'mongoose-aggregate-paginate-v2';
4
- import { PAYROLL_STATUS_VALUES } from '../enums.js';
5
- import { HRM_CONFIG } from '../config.js';
6
-
7
- const { Schema } = mongoose;
8
-
9
- const payrollBreakdownSchema = new Schema({
10
- baseAmount: { type: Number, required: true, min: 0 },
11
-
12
- allowances: [{
13
- type: String,
14
- amount: { type: Number, min: 0 },
15
- taxable: { type: Boolean, default: true },
16
- }],
17
-
18
- deductions: [{
19
- type: String,
20
- amount: { type: Number, min: 0 },
21
- description: String,
22
- }],
23
-
24
- grossSalary: { type: Number, required: true, min: 0 },
25
- netSalary: { type: Number, required: true, min: 0 },
26
-
27
- workingDays: { type: Number, min: 0 },
28
- actualDays: { type: Number, min: 0 },
29
- proRatedAmount: { type: Number, default: 0, min: 0 },
30
- attendanceDeduction: { type: Number, default: 0, min: 0 },
31
- overtimeAmount: { type: Number, default: 0, min: 0 },
32
- bonusAmount: { type: Number, default: 0, min: 0 },
33
- }, { _id: false });
34
-
35
- const periodSchema = new Schema({
36
- month: { type: Number, required: true, min: 1, max: 12 },
37
- year: { type: Number, required: true, min: 2020 },
38
- startDate: { type: Date, required: true },
39
- endDate: { type: Date, required: true },
40
- payDate: { type: Date, required: true },
41
- }, { _id: false });
42
-
43
- const payrollRecordSchema = new Schema({
44
- organizationId: { type: Schema.Types.ObjectId, ref: 'Organization', required: true, index: true },
45
- employeeId: { type: Schema.Types.ObjectId, required: true, index: true },
46
- userId: { type: Schema.Types.ObjectId, ref: 'User', index: true },
47
-
48
- period: { type: periodSchema, required: true },
49
-
50
- breakdown: { type: payrollBreakdownSchema, required: true },
51
-
52
- transactionId: { type: Schema.Types.ObjectId, ref: 'Transaction' },
53
-
54
- status: {
55
- type: String,
56
- enum: PAYROLL_STATUS_VALUES,
57
- default: 'pending',
58
- index: true
59
- },
60
-
61
- paidAt: { type: Date },
62
- paymentMethod: { type: String },
63
-
64
- processedBy: { type: Schema.Types.ObjectId, ref: 'User' },
65
- notes: { type: String },
66
- payslipUrl: { type: String },
67
-
68
- exported: { type: Boolean, default: false },
69
- exportedAt: { type: Date },
70
- }, {
71
- timestamps: true,
72
- roleBasedSelect: {
73
- user: '-notes -processedBy',
74
- admin: '',
75
- superadmin: '',
76
- }
77
- });
78
-
79
- payrollRecordSchema.index({ organizationId: 1, employeeId: 1, 'period.month': 1, 'period.year': 1 }, { unique: true });
80
- payrollRecordSchema.index({ organizationId: 1, 'period.year': 1, 'period.month': 1 });
81
- payrollRecordSchema.index({ employeeId: 1, 'period.year': -1, 'period.month': -1 });
82
- payrollRecordSchema.index({ status: 1, createdAt: -1 });
83
- payrollRecordSchema.index({ organizationId: 1, status: 1, 'period.payDate': 1 });
84
-
85
- payrollRecordSchema.index(
86
- { createdAt: 1 },
87
- {
88
- expireAfterSeconds: HRM_CONFIG.dataRetention.payrollRecordsTTL,
89
- partialFilterExpression: { exported: true }
90
- }
91
- );
92
-
93
- payrollRecordSchema.virtual('totalAmount').get(function() {
94
- return this.breakdown?.netSalary || 0;
95
- });
96
-
97
- payrollRecordSchema.virtual('isPaid').get(function() {
98
- return this.status === 'paid';
99
- });
100
-
101
- payrollRecordSchema.virtual('periodLabel').get(function() {
102
- const months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
103
- return `${months[this.period.month - 1]} ${this.period.year}`;
104
- });
105
-
106
- payrollRecordSchema.methods.markAsPaid = function(transactionId, paidAt = new Date()) {
107
- this.status = 'paid';
108
- this.transactionId = transactionId;
109
- this.paidAt = paidAt;
110
- };
111
-
112
- payrollRecordSchema.methods.markAsExported = function() {
113
- this.exported = true;
114
- this.exportedAt = new Date();
115
- };
116
-
117
- payrollRecordSchema.methods.canBeDeleted = function() {
118
- return this.exported && this.status === 'paid';
119
- };
120
-
121
- payrollRecordSchema.plugin(mongoosePaginate);
122
- payrollRecordSchema.plugin(aggregatePaginate);
123
-
124
- const PayrollRecord = mongoose.models.PayrollRecord || mongoose.model('PayrollRecord', payrollRecordSchema);
125
-
126
- export default PayrollRecord;
@@ -1,164 +0,0 @@
1
- import logger from '../utils/logger.js';
2
- import { diffInDays } from '../utils/date.utils.js';
3
- import { sumDeductions } from '../utils/calculation.utils.js';
4
- import {
5
- isActive,
6
- isTerminated,
7
- canReceiveSalary as canReceiveSalaryUtil,
8
- } from '../utils/validation.utils.js';
9
- import { CompensationFactory } from '../factories/compensation.factory.js';
10
-
11
- export function employeePlugin(schema, options = {}) {
12
-
13
- schema.virtual('currentSalary').get(function() {
14
- return this.compensation?.netSalary || 0;
15
- });
16
-
17
- schema.virtual('isActive').get(function() {
18
- return isActive(this);
19
- });
20
-
21
- schema.virtual('isTerminated').get(function() {
22
- return isTerminated(this);
23
- });
24
-
25
- schema.virtual('yearsOfService').get(function() {
26
- const end = this.terminationDate || new Date();
27
- const days = diffInDays(this.hireDate, end);
28
- return Math.max(0, Math.floor((days / 365.25) * 10) / 10);
29
- });
30
-
31
- schema.virtual('isOnProbation').get(function() {
32
- if (!this.probationEndDate) return false;
33
- return new Date() < this.probationEndDate;
34
- });
35
-
36
- schema.methods.calculateSalary = function() {
37
- if (!this.compensation) {
38
- return { gross: 0, deductions: 0, net: 0 };
39
- }
40
-
41
- const breakdown = CompensationFactory.calculateBreakdown(this.compensation);
42
-
43
- return {
44
- gross: breakdown.grossAmount,
45
- deductions: sumDeductions(breakdown.deductions),
46
- net: breakdown.netAmount
47
- };
48
- };
49
-
50
- schema.methods.updateSalaryCalculations = function() {
51
- const calculated = this.calculateSalary();
52
- this.compensation.grossSalary = calculated.gross;
53
- this.compensation.netSalary = calculated.net;
54
- this.compensation.lastModified = new Date();
55
- };
56
-
57
- schema.methods.canReceiveSalary = function() {
58
- return (
59
- this.status === 'active' &&
60
- this.compensation?.baseAmount > 0 &&
61
- (!options.requireBankDetails || this.bankDetails?.accountNumber)
62
- );
63
- };
64
-
65
- schema.methods.addAllowance = function(type, amount, taxable = true) {
66
- if (!this.compensation.allowances) {
67
- this.compensation.allowances = [];
68
- }
69
- this.compensation.allowances.push({ type, amount, taxable });
70
- this.updateSalaryCalculations();
71
- };
72
-
73
- schema.methods.addDeduction = function(type, amount, auto = false, description = '') {
74
- if (!this.compensation.deductions) {
75
- this.compensation.deductions = [];
76
- }
77
- this.compensation.deductions.push({ type, amount, auto, description });
78
- this.updateSalaryCalculations();
79
- };
80
-
81
- schema.methods.removeAllowance = function(type) {
82
- if (!this.compensation.allowances) return;
83
- this.compensation.allowances = this.compensation.allowances.filter(a => a.type !== type);
84
- this.updateSalaryCalculations();
85
- };
86
-
87
- schema.methods.removeDeduction = function(type) {
88
- if (!this.compensation.deductions) return;
89
- this.compensation.deductions = this.compensation.deductions.filter(d => d.type !== type);
90
- this.updateSalaryCalculations();
91
- };
92
-
93
- schema.methods.terminate = function(reason, terminationDate = new Date()) {
94
- if (this.status === 'terminated') {
95
- throw new Error('Employee already terminated');
96
- }
97
-
98
- if (!this.employmentHistory) {
99
- this.employmentHistory = [];
100
- }
101
-
102
- this.employmentHistory.push({
103
- hireDate: this.hireDate,
104
- terminationDate,
105
- reason,
106
- finalSalary: this.compensation?.netSalary || 0,
107
- position: this.position,
108
- department: this.department,
109
- });
110
-
111
- this.status = 'terminated';
112
- this.terminationDate = terminationDate;
113
-
114
- logger.info('Employee terminated', {
115
- employeeId: this.employeeId,
116
- organizationId: this.organizationId,
117
- reason,
118
- });
119
- };
120
-
121
- schema.methods.reHire = function(hireDate = new Date(), position = null, department = null) {
122
- if (this.status !== 'terminated') {
123
- throw new Error('Can only re-hire terminated employees');
124
- }
125
-
126
- this.status = 'active';
127
- this.hireDate = hireDate;
128
- this.terminationDate = null;
129
-
130
- if (position) this.position = position;
131
- if (department) this.department = department;
132
-
133
- logger.info('Employee re-hired', {
134
- employeeId: this.employeeId,
135
- organizationId: this.organizationId,
136
- });
137
- };
138
-
139
- /**
140
- * Pre-save hook to automatically update salary calculations
141
- * when compensation is modified.
142
- *
143
- * Mongoose v9 compatible - uses async function without next callback
144
- * - Use throw instead of next(err) for errors
145
- * - Use return instead of return next()
146
- */
147
- schema.pre('save', async function() {
148
- if (this.isModified('compensation')) {
149
- this.updateSalaryCalculations();
150
- }
151
- });
152
-
153
- schema.index({ organizationId: 1, employeeId: 1 }, { unique: true });
154
- schema.index({ userId: 1, organizationId: 1 }, { unique: true });
155
- schema.index({ organizationId: 1, status: 1 });
156
- schema.index({ organizationId: 1, department: 1 });
157
- schema.index({ organizationId: 1, 'compensation.netSalary': -1 });
158
-
159
- logger.debug('Employee plugin applied', {
160
- requireBankDetails: options.requireBankDetails || false,
161
- });
162
- }
163
-
164
- export default employeePlugin;
@@ -1,126 +0,0 @@
1
- import mongoose from 'mongoose';
2
- import {
3
- EMPLOYMENT_TYPE_VALUES,
4
- EMPLOYEE_STATUS_VALUES,
5
- DEPARTMENT_VALUES,
6
- PAYMENT_FREQUENCY_VALUES,
7
- ALLOWANCE_TYPE_VALUES,
8
- DEDUCTION_TYPE_VALUES,
9
- TERMINATION_REASON_VALUES,
10
- } from '../enums.js';
11
-
12
- const { Schema } = mongoose;
13
-
14
- const allowanceSchema = new Schema({
15
- type: { type: String, enum: ALLOWANCE_TYPE_VALUES, required: true },
16
- amount: { type: Number, required: true, min: 0 },
17
- taxable: { type: Boolean, default: true },
18
- recurring: { type: Boolean, default: true },
19
- effectiveFrom: { type: Date, default: () => new Date() },
20
- effectiveTo: { type: Date },
21
- }, { _id: false });
22
-
23
- const deductionSchema = new Schema({
24
- type: { type: String, enum: DEDUCTION_TYPE_VALUES, required: true },
25
- amount: { type: Number, required: true, min: 0 },
26
- auto: { type: Boolean, default: false },
27
- recurring: { type: Boolean, default: true },
28
- effectiveFrom: { type: Date, default: () => new Date() },
29
- effectiveTo: { type: Date },
30
- description: { type: String },
31
- }, { _id: false });
32
-
33
- const compensationSchema = new Schema({
34
- baseAmount: { type: Number, required: true, min: 0 },
35
- frequency: { type: String, enum: PAYMENT_FREQUENCY_VALUES, default: 'monthly' },
36
- currency: { type: String, default: 'BDT' },
37
-
38
- allowances: [allowanceSchema],
39
- deductions: [deductionSchema],
40
-
41
- grossSalary: { type: Number, default: 0 },
42
- netSalary: { type: Number, default: 0 },
43
-
44
- effectiveFrom: { type: Date, default: () => new Date() },
45
- lastModified: { type: Date, default: () => new Date() },
46
- }, { _id: false });
47
-
48
- const workScheduleSchema = new Schema({
49
- hoursPerWeek: { type: Number, min: 0, max: 168 },
50
- hoursPerDay: { type: Number, min: 0, max: 24 }, // 🆕 Standard hours per day (e.g., 8 for full-time, 4 for part-time)
51
- workingDays: [{ type: Number, min: 0, max: 6 }], // Array of days (0=Sunday, 6=Saturday)
52
- shiftStart: { type: String }, // e.g., "09:00"
53
- shiftEnd: { type: String }, // e.g., "17:00"
54
- }, { _id: false });
55
-
56
- const bankDetailsSchema = new Schema({
57
- accountName: { type: String },
58
- accountNumber: { type: String },
59
- bankName: { type: String },
60
- branchName: { type: String },
61
- routingNumber: { type: String },
62
- }, { _id: false });
63
-
64
- const employmentHistorySchema = new Schema({
65
- hireDate: { type: Date, required: true },
66
- terminationDate: { type: Date, required: true },
67
- reason: { type: String, enum: TERMINATION_REASON_VALUES },
68
- finalSalary: { type: Number },
69
- position: { type: String },
70
- department: { type: String },
71
- notes: { type: String },
72
- }, { timestamps: true });
73
-
74
- const payrollStatsSchema = new Schema({
75
- totalPaid: { type: Number, default: 0, min: 0 },
76
- lastPaymentDate: { type: Date },
77
- nextPaymentDate: { type: Date },
78
- paymentsThisYear: { type: Number, default: 0, min: 0 },
79
- averageMonthly: { type: Number, default: 0, min: 0 },
80
- updatedAt: { type: Date, default: () => new Date() },
81
- }, { _id: false });
82
-
83
- export const employmentFields = {
84
- userId: { type: Schema.Types.ObjectId, ref: 'User', required: true },
85
- organizationId: { type: Schema.Types.ObjectId, ref: 'Organization', required: true },
86
-
87
- employeeId: { type: String, required: true },
88
-
89
- employmentType: {
90
- type: String,
91
- enum: EMPLOYMENT_TYPE_VALUES,
92
- default: 'full_time'
93
- },
94
-
95
- status: {
96
- type: String,
97
- enum: EMPLOYEE_STATUS_VALUES,
98
- default: 'active'
99
- },
100
-
101
- department: { type: String, enum: DEPARTMENT_VALUES },
102
- position: { type: String, required: true },
103
-
104
- hireDate: { type: Date, required: true },
105
- terminationDate: { type: Date },
106
- probationEndDate: { type: Date },
107
-
108
- employmentHistory: [employmentHistorySchema],
109
-
110
- compensation: { type: compensationSchema, required: true },
111
- workSchedule: workScheduleSchema,
112
- bankDetails: bankDetailsSchema,
113
- payrollStats: { type: payrollStatsSchema, default: () => ({}) },
114
- };
115
-
116
- export {
117
- allowanceSchema,
118
- deductionSchema,
119
- compensationSchema,
120
- workScheduleSchema,
121
- bankDetailsSchema,
122
- employmentHistorySchema,
123
- payrollStatsSchema,
124
- };
125
-
126
- export default employmentFields;
@@ -1,231 +0,0 @@
1
- /**
2
- * Compensation Service - Beautiful Compensation Management
3
- * Clean abstraction for salary adjustments and calculations
4
- */
5
-
6
- import {
7
- CompensationFactory,
8
- CompensationPresets,
9
- } from '../factories/compensation.factory.js';
10
-
11
- export class CompensationService {
12
- constructor(EmployeeModel) {
13
- this.EmployeeModel = EmployeeModel;
14
- }
15
-
16
- async getEmployeeCompensation(employeeId) {
17
- const employee = await this.EmployeeModel.findById(employeeId);
18
- if (!employee) throw new Error('Employee not found');
19
-
20
- return employee.compensation;
21
- }
22
-
23
- async calculateBreakdown(employeeId) {
24
- const compensation = await this.getEmployeeCompensation(employeeId);
25
- return CompensationFactory.calculateBreakdown(compensation);
26
- }
27
-
28
- async updateBaseAmount(employeeId, newAmount, effectiveFrom) {
29
- const employee = await this.EmployeeModel.findById(employeeId);
30
- if (!employee) throw new Error('Employee not found');
31
-
32
- const updatedCompensation = CompensationFactory.updateBaseAmount(
33
- employee.compensation,
34
- newAmount,
35
- effectiveFrom
36
- );
37
-
38
- employee.compensation = updatedCompensation;
39
- await employee.save();
40
-
41
- return this.calculateBreakdown(employeeId);
42
- }
43
-
44
- async applyIncrement(employeeId, { percentage, amount, effectiveFrom }) {
45
- const employee = await this.EmployeeModel.findById(employeeId);
46
- if (!employee) throw new Error('Employee not found');
47
-
48
- const updatedCompensation = CompensationFactory.applyIncrement(
49
- employee.compensation,
50
- { percentage, amount, effectiveFrom }
51
- );
52
-
53
- employee.compensation = updatedCompensation;
54
- employee.incrementHistory = employee.incrementHistory || [];
55
- employee.incrementHistory.push({
56
- previousAmount: employee.compensation.baseAmount,
57
- newAmount: updatedCompensation.baseAmount,
58
- percentage,
59
- amount,
60
- effectiveFrom: effectiveFrom || new Date(),
61
- appliedAt: new Date(),
62
- });
63
-
64
- await employee.save();
65
-
66
- return this.calculateBreakdown(employeeId);
67
- }
68
-
69
- async addAllowance(employeeId, allowance) {
70
- const employee = await this.EmployeeModel.findById(employeeId);
71
- if (!employee) throw new Error('Employee not found');
72
-
73
- const updatedCompensation = CompensationFactory.addAllowance(
74
- employee.compensation,
75
- allowance
76
- );
77
-
78
- employee.compensation = updatedCompensation;
79
- await employee.save();
80
-
81
- return this.calculateBreakdown(employeeId);
82
- }
83
-
84
- async removeAllowance(employeeId, allowanceType) {
85
- const employee = await this.EmployeeModel.findById(employeeId);
86
- if (!employee) throw new Error('Employee not found');
87
-
88
- const updatedCompensation = CompensationFactory.removeAllowance(
89
- employee.compensation,
90
- allowanceType
91
- );
92
-
93
- employee.compensation = updatedCompensation;
94
- await employee.save();
95
-
96
- return this.calculateBreakdown(employeeId);
97
- }
98
-
99
- async addDeduction(employeeId, deduction) {
100
- const employee = await this.EmployeeModel.findById(employeeId);
101
- if (!employee) throw new Error('Employee not found');
102
-
103
- const updatedCompensation = CompensationFactory.addDeduction(
104
- employee.compensation,
105
- deduction
106
- );
107
-
108
- employee.compensation = updatedCompensation;
109
- await employee.save();
110
-
111
- return this.calculateBreakdown(employeeId);
112
- }
113
-
114
- async removeDeduction(employeeId, deductionType) {
115
- const employee = await this.EmployeeModel.findById(employeeId);
116
- if (!employee) throw new Error('Employee not found');
117
-
118
- const updatedCompensation = CompensationFactory.removeDeduction(
119
- employee.compensation,
120
- deductionType
121
- );
122
-
123
- employee.compensation = updatedCompensation;
124
- await employee.save();
125
-
126
- return this.calculateBreakdown(employeeId);
127
- }
128
-
129
- async setStandardCompensation(employeeId, baseAmount) {
130
- const employee = await this.EmployeeModel.findById(employeeId);
131
- if (!employee) throw new Error('Employee not found');
132
-
133
- employee.compensation = CompensationPresets.standard(baseAmount);
134
- await employee.save();
135
-
136
- return this.calculateBreakdown(employeeId);
137
- }
138
-
139
- async compareCompensation(employeeId1, employeeId2) {
140
- const breakdown1 = await this.calculateBreakdown(employeeId1);
141
- const breakdown2 = await this.calculateBreakdown(employeeId2);
142
-
143
- return {
144
- employee1: breakdown1,
145
- employee2: breakdown2,
146
- difference: {
147
- base: breakdown2.baseAmount - breakdown1.baseAmount,
148
- gross: breakdown2.grossAmount - breakdown1.grossAmount,
149
- net: breakdown2.netAmount - breakdown1.netAmount,
150
- },
151
- ratio: {
152
- base: breakdown2.baseAmount / breakdown1.baseAmount,
153
- gross: breakdown2.grossAmount / breakdown1.grossAmount,
154
- net: breakdown2.netAmount / breakdown1.netAmount,
155
- },
156
- };
157
- }
158
-
159
- async getDepartmentCompensationStats(organizationId, department) {
160
- const employees = await this.EmployeeModel.find({
161
- organizationId,
162
- department,
163
- status: { $in: ['active', 'on_leave'] },
164
- });
165
-
166
- const breakdowns = employees.map((emp) =>
167
- CompensationFactory.calculateBreakdown(emp.compensation)
168
- );
169
-
170
- const totals = breakdowns.reduce(
171
- (acc, breakdown) => ({
172
- totalBase: acc.totalBase + breakdown.baseAmount,
173
- totalGross: acc.totalGross + breakdown.grossAmount,
174
- totalNet: acc.totalNet + breakdown.netAmount,
175
- }),
176
- { totalBase: 0, totalGross: 0, totalNet: 0 }
177
- );
178
-
179
- return {
180
- department,
181
- employeeCount: employees.length,
182
- ...totals,
183
- averageBase: totals.totalBase / employees.length,
184
- averageGross: totals.totalGross / employees.length,
185
- averageNet: totals.totalNet / employees.length,
186
- };
187
- }
188
-
189
- async getOrganizationCompensationStats(organizationId) {
190
- const employees = await this.EmployeeModel.find({
191
- organizationId,
192
- status: { $in: ['active', 'on_leave'] },
193
- });
194
-
195
- const breakdowns = employees.map((emp) =>
196
- CompensationFactory.calculateBreakdown(emp.compensation)
197
- );
198
-
199
- const totals = breakdowns.reduce(
200
- (acc, breakdown) => ({
201
- totalBase: acc.totalBase + breakdown.baseAmount,
202
- totalGross: acc.totalGross + breakdown.grossAmount,
203
- totalNet: acc.totalNet + breakdown.netAmount,
204
- }),
205
- { totalBase: 0, totalGross: 0, totalNet: 0 }
206
- );
207
-
208
- const byDepartment = {};
209
- employees.forEach((emp) => {
210
- const dept = emp.department || 'unassigned';
211
- if (!byDepartment[dept]) {
212
- byDepartment[dept] = { count: 0, totalNet: 0 };
213
- }
214
- const breakdown = CompensationFactory.calculateBreakdown(emp.compensation);
215
- byDepartment[dept].count++;
216
- byDepartment[dept].totalNet += breakdown.netAmount;
217
- });
218
-
219
- return {
220
- employeeCount: employees.length,
221
- ...totals,
222
- averageBase: totals.totalBase / employees.length,
223
- averageGross: totals.totalGross / employees.length,
224
- averageNet: totals.totalNet / employees.length,
225
- byDepartment,
226
- };
227
- }
228
- }
229
-
230
- export const createCompensationService = (EmployeeModel) =>
231
- new CompensationService(EmployeeModel);