@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/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);