@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.
@@ -1,4 +1,5 @@
1
1
  import { Types } from 'mongoose';
2
+ import { MongoServerError } from 'mongodb';
2
3
 
3
4
  // src/utils/logger.ts
4
5
  var createConsoleLogger = () => ({
@@ -166,10 +167,17 @@ function endOfDay(date) {
166
167
  result.setHours(23, 59, 59, 999);
167
168
  return result;
168
169
  }
170
+ function toUTCDateString(date) {
171
+ const d = new Date(date);
172
+ d.setHours(0, 0, 0, 0);
173
+ return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, "0")}-${String(d.getDate()).padStart(2, "0")}`;
174
+ }
169
175
  function diffInDays(start, end) {
170
- return Math.ceil(
171
- (new Date(end).getTime() - new Date(start).getTime()) / (1e3 * 60 * 60 * 24)
172
- );
176
+ const s = new Date(start);
177
+ const e = new Date(end);
178
+ s.setHours(0, 0, 0, 0);
179
+ e.setHours(0, 0, 0, 0);
180
+ return Math.ceil((e.getTime() - s.getTime()) / (1e3 * 60 * 60 * 24));
173
181
  }
174
182
  function diffInMonths(start, end) {
175
183
  const startDate = new Date(start);
@@ -312,6 +320,7 @@ function getShortMonthName(month) {
312
320
  return months[month - 1] || "";
313
321
  }
314
322
  var date_default = {
323
+ toUTCDateString,
315
324
  addDays,
316
325
  addMonths,
317
326
  addYears,
@@ -473,13 +482,13 @@ function calculateTax(amount, brackets) {
473
482
  };
474
483
  }
475
484
  function calculateOvertime(hourlyRate, overtimeHours, multiplier = 1.5) {
476
- return Math.round(hourlyRate * overtimeHours * multiplier);
485
+ return roundMoney(hourlyRate * overtimeHours * multiplier, 2);
477
486
  }
478
487
  function calculateHourlyRate(monthlySalary, hoursPerMonth = 176) {
479
- return Math.round(monthlySalary / hoursPerMonth);
488
+ return roundMoney(monthlySalary / hoursPerMonth, 2);
480
489
  }
481
490
  function calculateDailyRate(monthlySalary, daysPerMonth = 22) {
482
- return Math.round(monthlySalary / daysPerMonth);
491
+ return roundMoney(monthlySalary / daysPerMonth, 2);
483
492
  }
484
493
  var calculation_default = {
485
494
  sum,
@@ -510,7 +519,7 @@ var DEFAULT_WORK_SCHEDULE = {
510
519
  function countWorkingDays(startDate, endDate, options = {}) {
511
520
  const workDays = options.workingDays || DEFAULT_WORK_SCHEDULE.workingDays;
512
521
  const holidaySet = new Set(
513
- (options.holidays || []).map((d) => new Date(d).toDateString())
522
+ (options.holidays || []).map((d) => toUTCDateString(d))
514
523
  );
515
524
  let totalDays = 0;
516
525
  let workingDays = 0;
@@ -522,7 +531,7 @@ function countWorkingDays(startDate, endDate, options = {}) {
522
531
  end.setHours(0, 0, 0, 0);
523
532
  while (current <= end) {
524
533
  totalDays++;
525
- const isHoliday = holidaySet.has(current.toDateString());
534
+ const isHoliday = holidaySet.has(toUTCDateString(current));
526
535
  const isWorkDay = workDays.includes(current.getDay());
527
536
  if (isHoliday) {
528
537
  holidays++;
@@ -569,7 +578,7 @@ function calculateProRating(input) {
569
578
  };
570
579
  }
571
580
  function applyProRating(baseAmount, ratio) {
572
- return Math.round(baseAmount * ratio);
581
+ return roundMoney(baseAmount * ratio, 2);
573
582
  }
574
583
 
575
584
  // src/enums.ts
@@ -1183,7 +1192,7 @@ function calculateLeaveDays(startDate, endDate, options = {}) {
1183
1192
  holidays = [],
1184
1193
  includeEndDate = true
1185
1194
  } = options;
1186
- const holidaySet = new Set(holidays.map((d) => new Date(d).toDateString()));
1195
+ const holidaySet = new Set(holidays.map((d) => toUTCDateString(d)));
1187
1196
  let count = 0;
1188
1197
  const current = new Date(startDate);
1189
1198
  current.setHours(0, 0, 0, 0);
@@ -1194,7 +1203,7 @@ function calculateLeaveDays(startDate, endDate, options = {}) {
1194
1203
  }
1195
1204
  while (current <= end) {
1196
1205
  const isWorkDay = workingDays.includes(current.getDay());
1197
- const isHoliday = holidaySet.has(current.toDateString());
1206
+ const isHoliday = holidaySet.has(toUTCDateString(current));
1198
1207
  if (isWorkDay && !isHoliday) {
1199
1208
  count++;
1200
1209
  }
@@ -1265,8 +1274,8 @@ function initializeLeaveBalances(hireDate, config = {}, year = (/* @__PURE__ */
1265
1274
  const fiscalYearEnd = new Date(year + 1, fiscalYearStartMonth - 1, 0);
1266
1275
  let prorationRatio = 1;
1267
1276
  if (proRateNewHires && hireDate > fiscalYearStart) {
1268
- const totalDays = diffInDays2(fiscalYearStart, fiscalYearEnd);
1269
- const remainingDays = diffInDays2(hireDate, fiscalYearEnd);
1277
+ const totalDays = diffInDays(fiscalYearStart, fiscalYearEnd);
1278
+ const remainingDays = diffInDays(hireDate, fiscalYearEnd);
1270
1279
  prorationRatio = Math.max(0, Math.min(1, remainingDays / totalDays));
1271
1280
  }
1272
1281
  const balances = [];
@@ -1290,14 +1299,14 @@ function proRateAllocation(fullAllocation, hireDate, fiscalYearStartMonth = 1, y
1290
1299
  return fullAllocation;
1291
1300
  }
1292
1301
  const fiscalYearEnd = new Date(year + 1, fiscalYearStartMonth - 1, 0);
1293
- const totalDays = diffInDays2(fiscalYearStart, fiscalYearEnd);
1294
- const remainingDays = Math.max(0, diffInDays2(hireDate, fiscalYearEnd));
1302
+ const totalDays = diffInDays(fiscalYearStart, fiscalYearEnd);
1303
+ const remainingDays = Math.max(0, diffInDays(hireDate, fiscalYearEnd));
1295
1304
  return Math.round(fullAllocation * remainingDays / totalDays);
1296
1305
  }
1297
1306
  function calculateUnpaidLeaveDeduction(baseSalary, unpaidDays, workingDaysInMonth) {
1298
1307
  if (unpaidDays <= 0 || workingDaysInMonth <= 0) return 0;
1299
- const dailyRate = baseSalary / workingDaysInMonth;
1300
- return Math.round(dailyRate * unpaidDays);
1308
+ const dailyRate = roundMoney(baseSalary / workingDaysInMonth, 2);
1309
+ return roundMoney(dailyRate * unpaidDays, 2);
1301
1310
  }
1302
1311
  function getUnpaidLeaveDays(leaveRequests, status = "approved") {
1303
1312
  return leaveRequests.filter((r) => r.type === "unpaid" && r.status === status).reduce((sum2, r) => sum2 + r.days, 0);
@@ -1336,13 +1345,6 @@ function accrueLeaveToBalance(balances, type, amount, year = (/* @__PURE__ */ ne
1336
1345
  }
1337
1346
  return balances;
1338
1347
  }
1339
- function diffInDays2(start, end) {
1340
- const startDate = new Date(start);
1341
- const endDate = new Date(end);
1342
- startDate.setHours(0, 0, 0, 0);
1343
- endDate.setHours(0, 0, 0, 0);
1344
- return Math.ceil((endDate.getTime() - startDate.getTime()) / (1e3 * 60 * 60 * 24));
1345
- }
1346
1348
  var leave_default = {
1347
1349
  DEFAULT_LEAVE_ALLOCATIONS,
1348
1350
  DEFAULT_CARRY_OVER,
@@ -1416,6 +1418,51 @@ var EmployeeNotFoundError = class extends PayrollError {
1416
1418
  );
1417
1419
  }
1418
1420
  };
1421
+ var DuplicatePayrollError = class extends PayrollError {
1422
+ constructor(employeeId, month, year, payrollRunType, context) {
1423
+ const runTypeInfo = payrollRunType ? ` (${payrollRunType})` : "";
1424
+ super(
1425
+ `Payroll already processed for employee ${employeeId} in ${month}/${year}${runTypeInfo}`,
1426
+ "DUPLICATE_PAYROLL",
1427
+ 409,
1428
+ { employeeId, month, year, payrollRunType, ...context }
1429
+ );
1430
+ }
1431
+ };
1432
+ function isPayrollError(error) {
1433
+ return error instanceof PayrollError;
1434
+ }
1435
+ function extractErrorInfo(error) {
1436
+ if (isPayrollError(error)) {
1437
+ return {
1438
+ code: error.code,
1439
+ status: error.status,
1440
+ message: error.message,
1441
+ context: error.context
1442
+ };
1443
+ }
1444
+ if (error instanceof Error) {
1445
+ return {
1446
+ code: "PAYROLL_ERROR",
1447
+ status: 500,
1448
+ message: error.message
1449
+ };
1450
+ }
1451
+ return {
1452
+ code: "PAYROLL_ERROR",
1453
+ status: 500,
1454
+ message: String(error)
1455
+ };
1456
+ }
1457
+ function toPayrollError(error) {
1458
+ if (isPayrollError(error)) {
1459
+ return error;
1460
+ }
1461
+ if (error instanceof Error) {
1462
+ return new PayrollError(error.message);
1463
+ }
1464
+ return new PayrollError(String(error));
1465
+ }
1419
1466
 
1420
1467
  // src/utils/employee-lookup.ts
1421
1468
  async function findEmployeeSecure(model, options) {
@@ -1623,7 +1670,120 @@ function formatEmployeeId(employeeId) {
1623
1670
  }
1624
1671
  return `employeeId=${employeeId}`;
1625
1672
  }
1673
+ function isMongoError(error) {
1674
+ return error instanceof MongoServerError;
1675
+ }
1676
+ function isDuplicateKeyError(error) {
1677
+ return isMongoError(error) && error.code === 11e3;
1678
+ }
1679
+ function parseDuplicateKeyError(error) {
1680
+ const message = error.message || "";
1681
+ const match = message.match(/dup key: \{ ([^:]+):/);
1682
+ if (match && match[1]) {
1683
+ return match[1].trim();
1684
+ }
1685
+ const errmsgMatch = message.match(/index: ([^_]+)_/);
1686
+ if (errmsgMatch && errmsgMatch[1]) {
1687
+ return errmsgMatch[1].trim();
1688
+ }
1689
+ return "unknown";
1690
+ }
1691
+ function isTransactionError(error) {
1692
+ if (!isMongoError(error)) return false;
1693
+ const message = (error.message || "").toLowerCase();
1694
+ return message.includes("transaction") || message.includes("session") || message.includes("replica set") || error.code === 251 || // NoSuchTransaction
1695
+ error.code === 225 || // TransactionTooOld
1696
+ error.code === 244 || // TransactionAborted
1697
+ error.code === 256 || // TransactionCommitted
1698
+ error.code === 257;
1699
+ }
1700
+ function isTransactionUnsupportedError(error) {
1701
+ if (!isMongoError(error)) return false;
1702
+ const message = (error.message || "").toLowerCase();
1703
+ return message.includes("transaction numbers are only allowed on a replica set member") || message.includes("transactions are only supported on replica sets") || message.includes("mongos") || error.code === 20;
1704
+ }
1705
+ function isConnectionError(error) {
1706
+ if (!isMongoError(error)) return false;
1707
+ return error.code === 11600 || // InterruptedAtShutdown
1708
+ error.code === 11602 || // InterruptedDueToReplStateChange
1709
+ error.code === 91 || // ShutdownInProgress
1710
+ error.code === 89 || // NetworkTimeout
1711
+ error.code === 6;
1712
+ }
1713
+
1714
+ // src/utils/error-helpers.ts
1715
+ function handleTransactionError(error, operationName) {
1716
+ const isUnsupported = isTransactionUnsupportedError(error);
1717
+ const isTxError = isTransactionError(error);
1718
+ const isConnError = isConnectionError(error);
1719
+ const retryable = isTxError && !isUnsupported;
1720
+ const wrappedError = toPayrollError(error);
1721
+ wrappedError.context.operationName = operationName;
1722
+ wrappedError.context.transactionError = true;
1723
+ if (isUnsupported) {
1724
+ wrappedError.context.reason = "transactions_unsupported";
1725
+ } else if (isConnError) {
1726
+ wrappedError.context.reason = "connection_error";
1727
+ } else if (isTxError) {
1728
+ wrappedError.context.reason = "transaction_conflict";
1729
+ }
1730
+ return {
1731
+ isTransactionError: isTxError || isUnsupported,
1732
+ isUnsupported,
1733
+ retryable,
1734
+ error: wrappedError,
1735
+ originalError: error
1736
+ };
1737
+ }
1738
+ function handleDuplicateKeyError(error, context) {
1739
+ if (!isDuplicateKeyError(error)) {
1740
+ return {
1741
+ isDuplicate: false,
1742
+ field: "",
1743
+ error: toPayrollError(error)
1744
+ };
1745
+ }
1746
+ const field = parseDuplicateKeyError(error);
1747
+ const wrappedError = context?.employeeId && context?.month && context?.year ? new DuplicatePayrollError(context.employeeId, context.month, context.year, void 0, { duplicateField: field }) : toPayrollError(error);
1748
+ return {
1749
+ isDuplicate: true,
1750
+ field,
1751
+ error: wrappedError
1752
+ };
1753
+ }
1754
+ function handlePayrollError(error, operationName) {
1755
+ const info = extractErrorInfo(error);
1756
+ const retryable = isConnectionError(error) || isTransactionError(error) && !isTransactionUnsupportedError(error);
1757
+ const operational = error instanceof PayrollError ? error.isOperational() : false;
1758
+ return {
1759
+ code: info.code,
1760
+ status: info.status,
1761
+ message: info.message,
1762
+ context: {
1763
+ ...info.context,
1764
+ operationName
1765
+ },
1766
+ operational,
1767
+ retryable
1768
+ };
1769
+ }
1770
+ function formatUserError(error) {
1771
+ if (error instanceof PayrollError) {
1772
+ return error.message;
1773
+ }
1774
+ if (isDuplicateKeyError(error)) {
1775
+ const field = parseDuplicateKeyError(error);
1776
+ return `A record with this ${field} already exists`;
1777
+ }
1778
+ if (isTransactionUnsupportedError(error)) {
1779
+ return "Database does not support transactions. Please use a replica set.";
1780
+ }
1781
+ if (isConnectionError(error)) {
1782
+ return "Database connection error. Please try again.";
1783
+ }
1784
+ return "An unexpected error occurred";
1785
+ }
1626
1786
 
1627
- export { DEFAULT_CARRY_OVER, DEFAULT_LEAVE_ALLOCATIONS, EmployeeQueryBuilder, PayrollQueryBuilder, QueryBuilder, accrueLeaveToBalance, addDays, addMonths, addYears, applyPercentage, applyProRating, applyTaxBrackets, buildAggregationPipeline, buildEmployeeQuery, buildPayrollQuery, calculateAllowanceAmount, calculateAllowances, calculateCarryOver, calculateCompensationBreakdown, calculateDailyRate, calculateDeductionAmount, calculateDeductions, calculateGross, calculateHourlyRate, calculateLeaveDays, calculateNet, calculateOvertime, calculatePercentage, calculateProRating, calculateProbationEnd, calculateTax, calculateTotalCompensation, calculateUnpaidLeaveDeduction, calculateYearsOfService, calculation_default as calculationUtils, canReceiveSalary, canUpdateEmployment, composeValidators, createChildLogger, createQueryBuilder, createSilentLogger, createValidator, date_default as dateUtils, daysBetween, detectEmployeeIdType, diffInDays, diffInMonths, diffInYears, disableLogging, employee, employeeExistsSecure, enableLogging, endOfDay, endOfMonth, endOfYear, findEmployeeSecure, findEmployeesSecure, formatDateForDB, formatEmployeeId, formatPeriod, getAvailableDays, getCurrentPeriod, getDayName, getDayOfWeek, getDaysInMonth, getLeaveBalance, getLeaveBalances, getLeaveSummary, getLogger, getMonthName, getPayPeriod, getPayPeriodDateRange, getShortMonthName, getUnpaidLeaveDays, getWorkingDaysInMonth, groupStage, hasCompensation, hasCompletedProbation, hasLeaveBalance, hasRequiredFields, inRange, initializeLeaveBalances, isActive, isDateInRange, isEffectiveForPeriod, isEligibleForBonus, isEligibleForPayroll, isEmployed, isInProbation, isInRange, isLoggingEnabled, isObjectIdEmployeeId, isOnLeave, isOnProbation, isPositive, isStringEmployeeId, isSuspended, isTerminated, isValidBankDetails, isValidCompensation, isValidEmploymentType, isValidObjectId, isValidStatus, isWeekday, isWeekend, leave_default as leaveUtils, limitStage, logger, lookupStage, matchStage, max, maxValue, min, minValue, monthsBetween, normalizeEmployeeId, oneOf, parseDBDate, parsePeriod, payroll, percentageOf, proRateAllocation, projectStage, prorateAmount, query_builders_default as queryBuilders, requireOrganizationId, required, resetLogger, resolveOrganizationId, roundMoney, roundMoneyPositive, roundTo, safeToObjectId, setLogger, skipStage, sortStage, startOfDay, startOfMonth, startOfYear, subDays, subMonths, sum, sumAllowances, sumBy, sumDeductions, toObjectId, tryResolveOrganizationId, unwindStage, validateOrganizationId, validation_default as validationUtils };
1787
+ export { DEFAULT_CARRY_OVER, DEFAULT_LEAVE_ALLOCATIONS, EmployeeQueryBuilder, PayrollQueryBuilder, QueryBuilder, accrueLeaveToBalance, addDays, addMonths, addYears, applyPercentage, applyProRating, applyTaxBrackets, buildAggregationPipeline, buildEmployeeQuery, buildPayrollQuery, calculateAllowanceAmount, calculateAllowances, calculateCarryOver, calculateCompensationBreakdown, calculateDailyRate, calculateDeductionAmount, calculateDeductions, calculateGross, calculateHourlyRate, calculateLeaveDays, calculateNet, calculateOvertime, calculatePercentage, calculateProRating, calculateProbationEnd, calculateTax, calculateTotalCompensation, calculateUnpaidLeaveDeduction, calculateYearsOfService, calculation_default as calculationUtils, canReceiveSalary, canUpdateEmployment, composeValidators, createChildLogger, createQueryBuilder, createSilentLogger, createValidator, date_default as dateUtils, daysBetween, detectEmployeeIdType, diffInDays, diffInMonths, diffInYears, disableLogging, employee, employeeExistsSecure, enableLogging, endOfDay, endOfMonth, endOfYear, findEmployeeSecure, findEmployeesSecure, formatDateForDB, formatEmployeeId, formatPeriod, formatUserError, getAvailableDays, getCurrentPeriod, getDayName, getDayOfWeek, getDaysInMonth, getLeaveBalance, getLeaveBalances, getLeaveSummary, getLogger, getMonthName, getPayPeriod, getPayPeriodDateRange, getShortMonthName, getUnpaidLeaveDays, getWorkingDaysInMonth, groupStage, handleDuplicateKeyError, handlePayrollError, handleTransactionError, hasCompensation, hasCompletedProbation, hasLeaveBalance, hasRequiredFields, inRange, initializeLeaveBalances, isActive, isDateInRange, isEffectiveForPeriod, isEligibleForBonus, isEligibleForPayroll, isEmployed, isInProbation, isInRange, isLoggingEnabled, isObjectIdEmployeeId, isOnLeave, isOnProbation, isPositive, isStringEmployeeId, isSuspended, isTerminated, isValidBankDetails, isValidCompensation, isValidEmploymentType, isValidObjectId, isValidStatus, isWeekday, isWeekend, leave_default as leaveUtils, limitStage, logger, lookupStage, matchStage, max, maxValue, min, minValue, monthsBetween, normalizeEmployeeId, oneOf, parseDBDate, parsePeriod, payroll, percentageOf, proRateAllocation, projectStage, prorateAmount, query_builders_default as queryBuilders, requireOrganizationId, required, resetLogger, resolveOrganizationId, roundMoney, roundMoneyPositive, roundTo, safeToObjectId, setLogger, skipStage, sortStage, startOfDay, startOfMonth, startOfYear, subDays, subMonths, sum, sumAllowances, sumBy, sumDeductions, toObjectId, tryResolveOrganizationId, unwindStage, validateOrganizationId, validation_default as validationUtils };
1628
1788
  //# sourceMappingURL=index.js.map
1629
1789
  //# sourceMappingURL=index.js.map