@classytic/payroll 2.7.5 → 2.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -123,76 +123,6 @@ var TAX_STATUS = {
123
123
  CANCELLED: "cancelled"
124
124
  };
125
125
  var TAX_STATUS_VALUES = Object.values(TAX_STATUS);
126
-
127
- // src/config.ts
128
- var HRM_CONFIG = {
129
- dataRetention: {
130
- /**
131
- * Default retention period for payroll records in seconds
132
- *
133
- * STANDARD APPROACH: expireAt field + configurable TTL index
134
- *
135
- * ## How It Works:
136
- * 1. Set expireAt date on each payroll record
137
- * 2. Call PayrollRecord.configureRetention() at app startup
138
- * 3. MongoDB deletes documents when expireAt is reached
139
- *
140
- * ## Usage:
141
- *
142
- * @example Configure at initialization
143
- * ```typescript
144
- * await payroll.init({ ... });
145
- * await PayrollRecord.configureRetention(0); // 0 = delete when expireAt reached
146
- * ```
147
- *
148
- * @example Set expireAt per record
149
- * ```typescript
150
- * const expireAt = PayrollRecord.calculateExpireAt(7); // 7 years
151
- * await PayrollRecord.updateOne({ _id }, { expireAt });
152
- * ```
153
- *
154
- * ## Jurisdiction Requirements:
155
- * - USA: 7 years → 220752000 seconds
156
- * - EU/UK: 6 years → 189216000 seconds
157
- * - Germany: 10 years → 315360000 seconds
158
- * - India: 8 years → 252288000 seconds
159
- *
160
- * Set to 0 to disable TTL
161
- */
162
- payrollRecordsTTL: 63072e3}};
163
- var ORG_ROLES = {
164
- OWNER: {
165
- key: "owner",
166
- label: "Owner",
167
- description: "Full organization access (set by Organization model)"
168
- },
169
- MANAGER: {
170
- key: "manager",
171
- label: "Manager",
172
- description: "Management and administrative features"
173
- },
174
- TRAINER: {
175
- key: "trainer",
176
- label: "Trainer",
177
- description: "Training and coaching features"
178
- },
179
- STAFF: {
180
- key: "staff",
181
- label: "Staff",
182
- description: "General staff access to basic features"
183
- },
184
- INTERN: {
185
- key: "intern",
186
- label: "Intern",
187
- description: "Limited access for interns"
188
- },
189
- CONSULTANT: {
190
- key: "consultant",
191
- label: "Consultant",
192
- description: "Project-based consultant access"
193
- }
194
- };
195
- Object.values(ORG_ROLES).map((role) => role.key);
196
126
  var periodSchema = new Schema(
197
127
  {
198
128
  month: { type: Number, required: true, min: 1, max: 12 },
@@ -1311,7 +1241,63 @@ function createPayrollRecordFields(options = {}) {
1311
1241
  reversalTransactionId: { type: Schema.Types.ObjectId },
1312
1242
  originalPayrollId: { type: Schema.Types.ObjectId },
1313
1243
  // TTL expiration (per-document)
1314
- expireAt: { type: Date }
1244
+ expireAt: { type: Date },
1245
+ // Payroll run type (v2.8.0+)
1246
+ payrollRunType: {
1247
+ type: String,
1248
+ enum: ["regular", "off-cycle", "supplemental", "retroactive"],
1249
+ default: "regular"
1250
+ },
1251
+ // Payment frequency at time of processing (v2.9.0+)
1252
+ // Stored for proper idempotency key reconstruction in void/reverse operations
1253
+ paymentFrequency: {
1254
+ type: String,
1255
+ enum: PAYMENT_FREQUENCY_VALUES,
1256
+ default: "monthly"
1257
+ },
1258
+ // Retroactive adjustment details (v2.8.0+)
1259
+ retroactiveAdjustment: {
1260
+ type: new Schema(
1261
+ {
1262
+ originalPeriod: {
1263
+ month: { type: Number, required: true, min: 1, max: 12 },
1264
+ year: { type: Number, required: true }
1265
+ },
1266
+ originalPayrollId: { type: Schema.Types.ObjectId },
1267
+ reason: { type: String, required: true },
1268
+ adjustmentAmount: { type: Number, required: true },
1269
+ approved: { type: Boolean },
1270
+ approvedBy: { type: Schema.Types.ObjectId },
1271
+ approvedAt: { type: Date }
1272
+ },
1273
+ { _id: false }
1274
+ ),
1275
+ required: false
1276
+ },
1277
+ // Employer contributions (v2.8.0+)
1278
+ employerContributions: [
1279
+ {
1280
+ type: {
1281
+ type: String,
1282
+ enum: ["social_security", "pension", "unemployment", "health_insurance", "other"],
1283
+ required: true
1284
+ },
1285
+ amount: { type: Number, required: true },
1286
+ description: { type: String },
1287
+ mandatory: { type: Boolean },
1288
+ referenceNumber: { type: String }
1289
+ }
1290
+ ],
1291
+ // Corrections history (v2.8.0+)
1292
+ corrections: [
1293
+ {
1294
+ previousAmount: { type: Number },
1295
+ newAmount: { type: Number },
1296
+ reason: { type: String },
1297
+ correctedBy: { type: Schema.Types.ObjectId, ref: userRef },
1298
+ correctedAt: { type: Date, default: Date.now }
1299
+ }
1300
+ ]
1315
1301
  };
1316
1302
  }
1317
1303
  var employeeIndexes = [
@@ -1342,17 +1328,51 @@ var employeeIndexes = [
1342
1328
  { fields: { organizationId: 1, "compensation.netSalary": -1 } }
1343
1329
  ];
1344
1330
  var payrollRecordIndexes = [
1345
- // Composite index for common queries (not unique - app handles duplicates)
1331
+ /**
1332
+ * UNIQUE Compound Index (v2.9.0+) - PRIMARY duplicate protection
1333
+ *
1334
+ * Prevents duplicate payrolls at the database level for the same:
1335
+ * - Organization, Employee, Period (month + year + startDate), Payroll run type
1336
+ *
1337
+ * The period.startDate is critical for non-monthly frequencies (weekly, bi_weekly,
1338
+ * daily, hourly) where multiple payroll runs can occur within the same calendar month.
1339
+ *
1340
+ * Partial filter excludes voided records to allow re-processing.
1341
+ * Duplicate inserts fail with E11000 → converted to DuplicatePayrollError.
1342
+ */
1343
+ {
1344
+ fields: {
1345
+ organizationId: 1,
1346
+ employeeId: 1,
1347
+ "period.month": 1,
1348
+ "period.year": 1,
1349
+ "period.startDate": 1,
1350
+ payrollRunType: 1
1351
+ },
1352
+ options: {
1353
+ unique: true,
1354
+ name: "unique_payroll_per_period_startdate_runtype",
1355
+ // Only enforce for non-voided records (uses $eq which is supported in partial indexes)
1356
+ // When a record is voided, isVoided is set to true, excluding it from unique constraint
1357
+ partialFilterExpression: {
1358
+ isVoided: { $eq: false }
1359
+ }
1360
+ }
1361
+ },
1362
+ // Composite index for common queries
1346
1363
  { fields: { organizationId: 1, employeeId: 1, "period.month": 1, "period.year": 1 } },
1347
1364
  { fields: { organizationId: 1, "period.year": 1, "period.month": 1 } },
1348
1365
  { fields: { employeeId: 1, "period.year": -1, "period.month": -1 } },
1349
1366
  { fields: { status: 1, createdAt: -1 } },
1350
1367
  { fields: { organizationId: 1, status: 1, "period.payDate": 1 } },
1368
+ // Index for payroll run type queries (supplemental, retroactive, etc.)
1369
+ { fields: { organizationId: 1, payrollRunType: 1, "period.year": 1, "period.month": 1 } },
1370
+ // TTL index using expireAt field for per-document retention (jurisdiction-specific)
1351
1371
  {
1352
- fields: { createdAt: 1 },
1372
+ fields: { expireAt: 1 },
1353
1373
  options: {
1354
- expireAfterSeconds: HRM_CONFIG.dataRetention.payrollRecordsTTL
1355
- // TTL applies to ALL records (user handles backups/exports at app level)
1374
+ expireAfterSeconds: 0
1375
+ // Delete immediately when expireAt is reached
1356
1376
  }
1357
1377
  }
1358
1378
  ];