@7365admin1/module-hygiene 4.8.0 → 4.10.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/dist/index.js CHANGED
@@ -84,7 +84,12 @@ module.exports = __toCommonJS(src_exports);
84
84
 
85
85
  // src/models/hygiene-base.model.ts
86
86
  var allowedTypes = ["common", "toilet"];
87
- var allowedStatus = ["ready", "ongoing", "completed"];
87
+ var allowedStatus = [
88
+ "open",
89
+ "ongoing",
90
+ "completed",
91
+ "closed"
92
+ ];
88
93
  var allowedPeriods = ["today", "thisWeek", "thisMonth"];
89
94
 
90
95
  // src/repositories/hygiene-dashboard.repository.ts
@@ -1366,10 +1371,14 @@ function useAreaService() {
1366
1371
  try {
1367
1372
  dataArray = JSON.parse(dataJson);
1368
1373
  } catch (error) {
1369
- throw new import_node_server_utils8.BadRequestError("Invalid JSON format for data in excel");
1374
+ throw new import_node_server_utils8.BadRequestError(
1375
+ "We couldn't read the uploaded file. Please make sure you're uploading the correct file and try again."
1376
+ );
1370
1377
  }
1371
1378
  if (!dataArray || dataArray.length === 0) {
1372
- throw new import_node_server_utils8.NotFoundError("No data found in the uploaded file");
1379
+ throw new import_node_server_utils8.NotFoundError(
1380
+ "The uploaded file is empty. Please make sure your file contains data and try again."
1381
+ );
1373
1382
  }
1374
1383
  let availableUnits = [];
1375
1384
  try {
@@ -1470,33 +1479,35 @@ function useAreaService() {
1470
1479
  }
1471
1480
  }
1472
1481
  }
1473
- let message = `Upload completed: ${insertedAreaIds.length} areas successfully created`;
1482
+ let message = `Upload complete! ${insertedAreaIds.length} ${insertedAreaIds.length === 1 ? "area was" : "areas were"} successfully added`;
1474
1483
  if (duplicateAreas.length > 0) {
1475
- message += `, ${duplicateAreas.length} areas skipped (already exist)`;
1484
+ message += `, ${duplicateAreas.length} ${duplicateAreas.length === 1 ? "area was" : "areas were"} skipped because they already exist`;
1476
1485
  }
1477
1486
  if (failedAreas.length > 0) {
1478
- message += `, ${failedAreas.length} areas failed`;
1487
+ message += `, ${failedAreas.length} ${failedAreas.length === 1 ? "area" : "areas"} could not be saved`;
1479
1488
  }
1480
1489
  if (skippedRows.length > 0) {
1481
- message += `, ${skippedRows.length} rows skipped (invalid data)`;
1490
+ message += `, ${skippedRows.length} ${skippedRows.length === 1 ? "row was" : "rows were"} skipped due to missing information`;
1482
1491
  }
1483
1492
  import_node_server_utils8.logger.info(message);
1484
1493
  if (insertedAreaIds.length === 0) {
1485
1494
  if (duplicateAreas.length > 0 && failedAreas.length === 0 && skippedRows.length === 0) {
1486
1495
  return {
1487
- message: `No new areas were created. All ${duplicateAreas.length} areas already exist in the system: ${duplicateAreas.join(", ")}`
1496
+ message: `No new areas were added. All ${duplicateAreas.length} ${duplicateAreas.length === 1 ? "area" : "areas"} in your file already exist in the system: ${duplicateAreas.join(
1497
+ ", "
1498
+ )}.`
1488
1499
  };
1489
1500
  } else if (failedAreas.length > 0) {
1490
1501
  throw new import_node_server_utils8.BadRequestError(
1491
- `No areas were created. ${failedAreas.length} areas failed due to errors. Please check your data format and ensure area names are valid.`
1502
+ `No areas were added. ${failedAreas.length} ${failedAreas.length === 1 ? "area" : "areas"} could not be saved. Please review the area names and information in your file, then try again.`
1492
1503
  );
1493
1504
  } else if (skippedRows.length > 0 && duplicateAreas.length === 0) {
1494
1505
  throw new import_node_server_utils8.BadRequestError(
1495
- `No areas were created. All ${skippedRows.length} rows contained invalid or missing data.`
1506
+ `No areas were added. ${skippedRows.length} ${skippedRows.length === 1 ? "row" : "rows"} in your file had missing or incomplete information. Please fill in all required fields and try again.`
1496
1507
  );
1497
1508
  } else {
1498
1509
  return {
1499
- message: `No new areas were created. ${duplicateAreas.length} areas already exist, ${skippedRows.length} rows had invalid data.`
1510
+ message: `No new areas were added. ${duplicateAreas.length} ${duplicateAreas.length === 1 ? "area" : "areas"} already exist${skippedRows.length > 0 ? `, and ${skippedRows.length} ${skippedRows.length === 1 ? "row" : "rows"} had missing or incomplete information` : ""}.`
1500
1511
  };
1501
1512
  }
1502
1513
  }
@@ -1507,11 +1518,11 @@ function useAreaService() {
1507
1518
  throw error;
1508
1519
  } else if (error.message.includes("validation")) {
1509
1520
  throw new import_node_server_utils8.BadRequestError(
1510
- "Upload failed due to invalid data format. Please check that all required fields are properly filled."
1521
+ "Upload failed because some required information is missing or incorrect. Please review your file and make sure all fields are filled in properly."
1511
1522
  );
1512
1523
  } else {
1513
1524
  throw new import_node_server_utils8.BadRequestError(
1514
- `Upload failed: ${error.message || "Please check your data format and try again."}`
1525
+ "Something went wrong while uploading. Please check your file and try again."
1515
1526
  );
1516
1527
  }
1517
1528
  }
@@ -1520,11 +1531,15 @@ function useAreaService() {
1520
1531
  try {
1521
1532
  const areas = await getAreasForChecklist(site);
1522
1533
  if (!areas || !Array.isArray(areas) || areas.length === 0) {
1523
- throw new import_node_server_utils8.BadRequestError("No data found to export");
1534
+ throw new import_node_server_utils8.BadRequestError(
1535
+ "There are no areas to export yet. Please add some areas first, then try again."
1536
+ );
1524
1537
  }
1525
1538
  const excelBuffer = await generateAreaExcel(areas);
1526
1539
  if (!excelBuffer || excelBuffer.length === 0) {
1527
- throw new Error("Generated Excel file is empty or invalid.");
1540
+ throw new Error(
1541
+ "Something went wrong while preparing your export file. Please try again."
1542
+ );
1528
1543
  }
1529
1544
  return excelBuffer;
1530
1545
  } catch (error) {
@@ -1824,10 +1839,14 @@ function useUnitService() {
1824
1839
  try {
1825
1840
  dataArray = JSON.parse(dataJson);
1826
1841
  } catch (error) {
1827
- throw new import_node_server_utils11.BadRequestError("Invalid JSON format for data in excel");
1842
+ throw new import_node_server_utils11.BadRequestError(
1843
+ "We couldn't read the uploaded file. Please make sure you're uploading the correct file and try again."
1844
+ );
1828
1845
  }
1829
1846
  if (!dataArray || dataArray.length === 0) {
1830
- throw new import_node_server_utils11.NotFoundError("No data found in the uploaded file");
1847
+ throw new import_node_server_utils11.NotFoundError(
1848
+ "The uploaded file is empty. Please make sure your file contains data and try again."
1849
+ );
1831
1850
  }
1832
1851
  const insertedUnitIds = [];
1833
1852
  const duplicateUnits = [];
@@ -1868,29 +1887,31 @@ function useUnitService() {
1868
1887
  }
1869
1888
  }
1870
1889
  }
1871
- let message = `Upload completed: ${insertedUnitIds.length} units successfully created`;
1890
+ let message = `Upload complete! ${insertedUnitIds.length} ${insertedUnitIds.length === 1 ? "unit was" : "units were"} successfully added`;
1872
1891
  if (duplicateUnits.length > 0) {
1873
- message += `, ${duplicateUnits.length} units skipped (already exist)`;
1892
+ message += `, ${duplicateUnits.length} ${duplicateUnits.length === 1 ? "unit was" : "units were"} skipped because they already exist`;
1874
1893
  }
1875
1894
  if (failedUnits.length > 0) {
1876
- message += `, ${failedUnits.length} units failed`;
1895
+ message += `, ${failedUnits.length} ${failedUnits.length === 1 ? "unit" : "units"} could not be saved`;
1877
1896
  }
1878
1897
  if (skippedRows.length > 0) {
1879
- message += `, ${skippedRows.length} rows skipped (invalid data)`;
1898
+ message += `, ${skippedRows.length} ${skippedRows.length === 1 ? "row was" : "rows were"} skipped due to missing information`;
1880
1899
  }
1881
1900
  import_node_server_utils11.logger.info(message);
1882
1901
  if (insertedUnitIds.length === 0) {
1883
1902
  if (duplicateUnits.length > 0 && failedUnits.length === 0) {
1884
1903
  throw new import_node_server_utils11.BadRequestError(
1885
- `No new units were created. All ${duplicateUnits.length} units already exist in the system: ${duplicateUnits.join(", ")}`
1904
+ `No new units were added. All ${duplicateUnits.length} ${duplicateUnits.length === 1 ? "unit" : "units"} in your file already exist in the system: ${duplicateUnits.join(
1905
+ ", "
1906
+ )}.`
1886
1907
  );
1887
1908
  } else if (failedUnits.length > 0) {
1888
1909
  throw new import_node_server_utils11.BadRequestError(
1889
- `No units were created. Please check your data format and ensure unit names are valid.`
1910
+ `No units were added. ${failedUnits.length} ${failedUnits.length === 1 ? "unit" : "units"} could not be saved. Please review the unit names in your file and try again.`
1890
1911
  );
1891
1912
  } else if (skippedRows.length > 0) {
1892
1913
  throw new import_node_server_utils11.BadRequestError(
1893
- `No units were created. All rows contained invalid or missing unit names.`
1914
+ `No units were added. ${skippedRows.length} ${skippedRows.length === 1 ? "row" : "rows"} in your file had missing or incomplete information. Please fill in all required fields and try again.`
1894
1915
  );
1895
1916
  }
1896
1917
  }
@@ -1901,15 +1922,15 @@ function useUnitService() {
1901
1922
  throw error;
1902
1923
  } else if (error.message.includes("duplicate")) {
1903
1924
  throw new import_node_server_utils11.BadRequestError(
1904
- "Upload failed due to duplicate unit names. Please ensure all unit names are unique."
1925
+ "Upload failed because some unit names are already taken. Please make sure each unit has a unique name and try again."
1905
1926
  );
1906
1927
  } else if (error.message.includes("validation")) {
1907
1928
  throw new import_node_server_utils11.BadRequestError(
1908
- "Upload failed due to invalid data format. Please check that all required fields are properly filled."
1929
+ "Upload failed because some required information is missing or incorrect. Please review your file and make sure all fields are filled in properly."
1909
1930
  );
1910
1931
  } else {
1911
1932
  throw new import_node_server_utils11.BadRequestError(
1912
- `Upload failed: ${error.message || "Please check your data format and try again."}`
1933
+ "Something went wrong while uploading. Please check your file and try again."
1913
1934
  );
1914
1935
  }
1915
1936
  }
@@ -1941,7 +1962,9 @@ function useUnitService() {
1941
1962
  session?.startTransaction();
1942
1963
  const isExistingArea = await verifyAreaByUnitId(_id);
1943
1964
  if (isExistingArea) {
1944
- throw new import_node_server_utils11.BadRequestError("Failed to delete unit, unit is in use.");
1965
+ throw new import_node_server_utils11.BadRequestError(
1966
+ "This unit can't be deleted because it's currently being used in one or more areas. Please remove it from those areas first, then try again."
1967
+ );
1945
1968
  }
1946
1969
  const result = await _deleteUnit(_id, session);
1947
1970
  await session?.commitTransaction();
@@ -1967,11 +1990,15 @@ function useUnitService() {
1967
1990
  site
1968
1991
  });
1969
1992
  if (!data || !data.items || data.items.length === 0) {
1970
- throw new import_node_server_utils11.BadRequestError("No data found to export");
1993
+ throw new import_node_server_utils11.BadRequestError(
1994
+ "There are no units to export yet. Please add some units first, then try again."
1995
+ );
1971
1996
  }
1972
1997
  const excelBuffer = await _generateUnitExcel(data.items);
1973
1998
  if (!excelBuffer || excelBuffer.length === 0) {
1974
- throw new Error("Generated Excel file is empty or invalid.");
1999
+ throw new Error(
2000
+ "Something went wrong while preparing your export file. Please try again."
2001
+ );
1975
2002
  }
1976
2003
  return excelBuffer;
1977
2004
  } catch (error) {
@@ -2178,7 +2205,7 @@ function MParentChecklist(value) {
2178
2205
  }
2179
2206
  return {
2180
2207
  site: value.site,
2181
- status: "ready",
2208
+ status: "open",
2182
2209
  createdAt: value.createdAt ?? /* @__PURE__ */ new Date(),
2183
2210
  updatedAt: value.updatedAt ?? ""
2184
2211
  };
@@ -2368,18 +2395,23 @@ function useParentChecklistRepo() {
2368
2395
  status: {
2369
2396
  $switch: {
2370
2397
  branches: [
2371
- { case: { $eq: ["$status", "ready"] }, then: "Ready" },
2398
+ { case: { $eq: ["$status", "open"] }, then: "Open" },
2372
2399
  {
2373
2400
  case: { $eq: ["$status", "ongoing"] },
2374
2401
  then: "Ongoing"
2375
2402
  },
2376
- { case: { $eq: ["$status", "completed"] }, then: "Completed" }
2403
+ { case: { $eq: ["$status", "completed"] }, then: "Completed" },
2404
+ {
2405
+ case: { $eq: ["$status", "closed"] },
2406
+ then: "Closed"
2407
+ }
2377
2408
  ],
2378
2409
  default: "$status"
2379
2410
  }
2380
2411
  },
2381
2412
  completedAt: 1,
2382
- createdAt: 1
2413
+ createdAt: 1,
2414
+ closeIn: { $add: ["$createdAt", 24 * 60 * 60 * 1e3] }
2383
2415
  }
2384
2416
  });
2385
2417
  pipeline.push(
@@ -2467,11 +2499,42 @@ function useParentChecklistRepo() {
2467
2499
  throw error;
2468
2500
  }
2469
2501
  }
2502
+ async function closeExpiredParentChecklists() {
2503
+ try {
2504
+ const expiryDate = new Date(Date.now() - 24 * 60 * 60 * 1e3);
2505
+ const result = await collection.updateMany(
2506
+ {
2507
+ status: { $in: ["open", "ongoing", "completed"] },
2508
+ createdAt: { $lte: expiryDate }
2509
+ },
2510
+ {
2511
+ $set: {
2512
+ status: "closed",
2513
+ updatedAt: /* @__PURE__ */ new Date()
2514
+ }
2515
+ }
2516
+ );
2517
+ if (result.modifiedCount > 0) {
2518
+ delNamespace().catch((err) => {
2519
+ import_node_server_utils14.logger.error(
2520
+ "Failed to invalidate cache after closing expired parent checklists",
2521
+ err
2522
+ );
2523
+ });
2524
+ import_node_server_utils14.logger.info(`Closed ${result.modifiedCount} expired parent checklists`);
2525
+ }
2526
+ return result.modifiedCount;
2527
+ } catch (error) {
2528
+ import_node_server_utils14.logger.error("Failed to close expired parent checklists", error);
2529
+ throw error;
2530
+ }
2531
+ }
2470
2532
  return {
2471
2533
  createIndex,
2472
2534
  createParentChecklist,
2473
2535
  getAllParentChecklist,
2474
- updateParentChecklistStatuses
2536
+ updateParentChecklistStatuses,
2537
+ closeExpiredParentChecklists
2475
2538
  };
2476
2539
  }
2477
2540
 
@@ -2553,7 +2616,7 @@ function useParentChecklistController() {
2553
2616
  var import_joi8 = __toESM(require("joi"));
2554
2617
  var import_mongodb8 = require("mongodb");
2555
2618
  var import_node_server_utils16 = require("@7365admin1/node-server-utils");
2556
- var allowedChecklistStatus = ["ready", "completed"];
2619
+ var allowedChecklistStatus = ["open", "completed", "closed"];
2557
2620
  var areaChecklistSchema = import_joi8.default.object({
2558
2621
  schedule: import_joi8.default.string().hex().required(),
2559
2622
  area: import_joi8.default.string().hex().required(),
@@ -2601,7 +2664,7 @@ function MAreaChecklist(value) {
2601
2664
  name: unit.name,
2602
2665
  approve: false,
2603
2666
  reject: false,
2604
- status: "ready",
2667
+ status: "open",
2605
2668
  remarks: "",
2606
2669
  completedBy: "",
2607
2670
  timestamp: ""
@@ -2622,7 +2685,7 @@ function MAreaChecklist(value) {
2622
2685
  name: value.name,
2623
2686
  type: value.type,
2624
2687
  checklist: value.checklist || [],
2625
- status: "ready",
2688
+ status: "open",
2626
2689
  createdBy: value.createdBy,
2627
2690
  createdAt: /* @__PURE__ */ new Date(),
2628
2691
  completedAt: "",
@@ -2780,17 +2843,37 @@ function useAreaChecklistRepo() {
2780
2843
  preserveNullAndEmptyArrays: true
2781
2844
  }
2782
2845
  },
2846
+ {
2847
+ $addFields: {
2848
+ statusOrder: {
2849
+ $switch: {
2850
+ branches: [
2851
+ { case: { $eq: ["$status", "open"] }, then: 0 },
2852
+ { case: { $eq: ["$status", "ongoing"] }, then: 1 },
2853
+ { case: { $eq: ["$status", "completed"] }, then: 3 },
2854
+ { case: { $eq: ["$status", "closed"] }, then: 4 }
2855
+ ],
2856
+ default: 2
2857
+ }
2858
+ }
2859
+ }
2860
+ },
2861
+ { $sort: { statusOrder: 1, _id: -1 } },
2783
2862
  {
2784
2863
  $project: {
2785
2864
  name: 1,
2786
2865
  status: {
2787
2866
  $switch: {
2788
2867
  branches: [
2789
- { case: { $eq: ["$status", "ready"] }, then: "Ready" },
2868
+ { case: { $eq: ["$status", "open"] }, then: "Open" },
2790
2869
  { case: { $eq: ["$status", "ongoing"] }, then: "Ongoing" },
2791
2870
  {
2792
2871
  case: { $eq: ["$status", "completed"] },
2793
2872
  then: "Completed"
2873
+ },
2874
+ {
2875
+ case: { $eq: ["$status", "closed"] },
2876
+ then: "Closed"
2794
2877
  }
2795
2878
  ],
2796
2879
  default: "$status"
@@ -2815,7 +2898,6 @@ function useAreaChecklistRepo() {
2815
2898
  createdByName: "$createdByDoc.name"
2816
2899
  }
2817
2900
  },
2818
- { $sort: { _id: -1 } },
2819
2901
  { $skip: page * limit },
2820
2902
  { $limit: limit }
2821
2903
  ];
@@ -2892,11 +2974,15 @@ function useAreaChecklistRepo() {
2892
2974
  status: {
2893
2975
  $switch: {
2894
2976
  branches: [
2895
- { case: { $eq: ["$status", "ready"] }, then: "Ready" },
2977
+ { case: { $eq: ["$status", "open"] }, then: "Open" },
2896
2978
  { case: { $eq: ["$status", "ongoing"] }, then: "Ongoing" },
2897
2979
  {
2898
2980
  case: { $eq: ["$status", "completed"] },
2899
2981
  then: "Completed"
2982
+ },
2983
+ {
2984
+ case: { $eq: ["$status", "closed"] },
2985
+ then: "Closed"
2900
2986
  }
2901
2987
  ],
2902
2988
  default: "$status"
@@ -2961,11 +3047,15 @@ function useAreaChecklistRepo() {
2961
3047
  status: {
2962
3048
  $switch: {
2963
3049
  branches: [
2964
- { case: { $eq: ["$status", "ready"] }, then: "Ready" },
3050
+ { case: { $eq: ["$status", "open"] }, then: "Open" },
2965
3051
  { case: { $eq: ["$status", "ongoing"] }, then: "Ongoing" },
2966
3052
  {
2967
3053
  case: { $eq: ["$status", "completed"] },
2968
3054
  then: "Completed"
3055
+ },
3056
+ {
3057
+ case: { $eq: ["$status", "closed"] },
3058
+ then: "Closed"
2969
3059
  }
2970
3060
  ],
2971
3061
  default: "$status"
@@ -3025,12 +3115,16 @@ function useAreaChecklistRepo() {
3025
3115
  $switch: {
3026
3116
  branches: [
3027
3117
  {
3028
- case: { $eq: ["$checklist.units.status", "ready"] },
3029
- then: "Ready"
3118
+ case: { $eq: ["$checklist.units.status", "open"] },
3119
+ then: "Open"
3030
3120
  },
3031
3121
  {
3032
3122
  case: { $eq: ["$checklist.units.status", "completed"] },
3033
3123
  then: "Completed"
3124
+ },
3125
+ {
3126
+ case: { $eq: ["$checklist.units.status", "closed"] },
3127
+ then: "Closed"
3034
3128
  }
3035
3129
  ],
3036
3130
  default: "$checklist.units.status"
@@ -3173,16 +3267,21 @@ function useAreaChecklistRepo() {
3173
3267
  name: "$checklist.units.name",
3174
3268
  approve: "$checklist.units.approve",
3175
3269
  reject: "$checklist.units.reject",
3270
+ timestamp: "$checklist.units.timestamp",
3176
3271
  status: {
3177
3272
  $switch: {
3178
3273
  branches: [
3179
3274
  {
3180
- case: { $eq: ["$checklist.units.status", "ready"] },
3181
- then: "Ready"
3275
+ case: { $eq: ["$checklist.units.status", "open"] },
3276
+ then: "Open"
3182
3277
  },
3183
3278
  {
3184
3279
  case: { $eq: ["$checklist.units.status", "completed"] },
3185
3280
  then: "Completed"
3281
+ },
3282
+ {
3283
+ case: { $eq: ["$checklist.units.status", "closed"] },
3284
+ then: "Closed"
3186
3285
  }
3187
3286
  ],
3188
3287
  default: "$checklist.units.status"
@@ -3203,6 +3302,7 @@ function useAreaChecklistRepo() {
3203
3302
  name: "$name",
3204
3303
  approve: "$approve",
3205
3304
  reject: "$reject",
3305
+ timestamp: "$timestamp",
3206
3306
  status: "$status",
3207
3307
  remarks: "$remarks",
3208
3308
  completedByName: "$completedByName"
@@ -3315,7 +3415,7 @@ function useAreaChecklistRepo() {
3315
3415
  } else if (value.reject === true) {
3316
3416
  updateValue["checklist.$[checklist].units.$[unit].approve"] = false;
3317
3417
  updateValue["checklist.$[checklist].units.$[unit].reject"] = true;
3318
- updateValue["checklist.$[checklist].units.$[unit].status"] = "ready";
3418
+ updateValue["checklist.$[checklist].units.$[unit].status"] = "open";
3319
3419
  }
3320
3420
  if (value.remarks) {
3321
3421
  updateValue["checklist.$[checklist].units.$[unit].remarks"] = value.remarks;
@@ -3441,6 +3541,42 @@ function useAreaChecklistRepo() {
3441
3541
  return 0;
3442
3542
  }
3443
3543
  }
3544
+ async function closeExpiredAreaChecklists() {
3545
+ try {
3546
+ const expiryDate = new Date(Date.now() - 24 * 60 * 60 * 1e3);
3547
+ const result = await collection.updateMany(
3548
+ {
3549
+ status: { $in: ["open", "ongoing", "completed"] },
3550
+ createdAt: { $lte: expiryDate }
3551
+ },
3552
+ {
3553
+ $set: {
3554
+ status: "closed",
3555
+ updatedAt: /* @__PURE__ */ new Date(),
3556
+ "checklist.$[].units.$[unit].status": "closed"
3557
+ }
3558
+ },
3559
+ {
3560
+ arrayFilters: [{ "unit.status": { $in: ["open", "completed"] } }]
3561
+ }
3562
+ );
3563
+ if (result.modifiedCount > 0) {
3564
+ delNamespace().catch((err) => {
3565
+ import_node_server_utils17.logger.error(
3566
+ "Failed to invalidate cache after closing expired area checklists",
3567
+ err
3568
+ );
3569
+ });
3570
+ import_node_server_utils17.logger.info(
3571
+ `Closed ${result.modifiedCount} expired area checklists and their units`
3572
+ );
3573
+ }
3574
+ return result.modifiedCount;
3575
+ } catch (error) {
3576
+ import_node_server_utils17.logger.error("Failed to close expired area checklists", error);
3577
+ throw error;
3578
+ }
3579
+ }
3444
3580
  return {
3445
3581
  createIndex,
3446
3582
  createTextIndex,
@@ -3454,7 +3590,8 @@ function useAreaChecklistRepo() {
3454
3590
  updateAreaChecklist,
3455
3591
  updateAreaChecklistStatus,
3456
3592
  updateAreaChecklistUnits,
3457
- getMaxSetNumberForArea
3593
+ getMaxSetNumberForArea,
3594
+ closeExpiredAreaChecklists
3458
3595
  };
3459
3596
  }
3460
3597
 
@@ -3550,12 +3687,12 @@ function useAreaChecklistService() {
3550
3687
  },
3551
3688
  session
3552
3689
  );
3553
- let areaStatus = "ready";
3690
+ let areaStatus = "open";
3554
3691
  if (allUnitsResult && allUnitsResult.items && allUnitsResult.items.length > 0) {
3555
3692
  const sets = allUnitsResult.items;
3556
3693
  const allUnits = sets.flatMap((set2) => set2.units || []);
3557
- const readyCount = allUnits.filter(
3558
- (unit) => unit.status === "Ready"
3694
+ const openCount = allUnits.filter(
3695
+ (unit) => unit.status === "Open"
3559
3696
  ).length;
3560
3697
  const completedCount = allUnits.filter(
3561
3698
  (unit) => unit.status === "Completed"
@@ -3563,8 +3700,8 @@ function useAreaChecklistService() {
3563
3700
  const totalCount = allUnits.length;
3564
3701
  if (completedCount === totalCount) {
3565
3702
  areaStatus = "completed";
3566
- } else if (readyCount === totalCount) {
3567
- areaStatus = "ready";
3703
+ } else if (openCount === totalCount) {
3704
+ areaStatus = "open";
3568
3705
  } else {
3569
3706
  areaStatus = "ongoing";
3570
3707
  }
@@ -3583,18 +3720,18 @@ function useAreaChecklistService() {
3583
3720
  );
3584
3721
  if (allAreasResult && allAreasResult.items && allAreasResult.items.length > 0) {
3585
3722
  const areas = allAreasResult.items;
3586
- const readyAreasCount = areas.filter(
3587
- (area) => area.status === "Ready"
3723
+ const openAreasCount = areas.filter(
3724
+ (area) => area.status === "Open"
3588
3725
  ).length;
3589
3726
  const completedAreasCount = areas.filter(
3590
3727
  (area) => area.status === "Completed"
3591
3728
  ).length;
3592
3729
  const totalAreasCount = areas.length;
3593
- let parentStatus = "ready";
3730
+ let parentStatus = "open";
3594
3731
  if (completedAreasCount === totalAreasCount) {
3595
3732
  parentStatus = "completed";
3596
- } else if (readyAreasCount === totalAreasCount) {
3597
- parentStatus = "ready";
3733
+ } else if (openAreasCount === totalAreasCount) {
3734
+ parentStatus = "open";
3598
3735
  } else {
3599
3736
  parentStatus = "ongoing";
3600
3737
  }
@@ -3605,7 +3742,7 @@ function useAreaChecklistService() {
3605
3742
  );
3606
3743
  } else {
3607
3744
  import_node_server_utils18.logger.info(
3608
- "No area checklists found, keeping parent status as ready"
3745
+ "No area checklists found, keeping parent status as open"
3609
3746
  );
3610
3747
  }
3611
3748
  }
@@ -5163,7 +5300,7 @@ function useCheckOutItemService() {
5163
5300
  const session = import_node_server_utils30.useAtlas.getClient()?.startSession();
5164
5301
  try {
5165
5302
  session?.startTransaction();
5166
- const { site, attachment, createdBy, items } = value;
5303
+ const { site, createdBy, items } = value;
5167
5304
  const createdByData = await getUserById(createdBy);
5168
5305
  const createdCheckOutItemIds = [];
5169
5306
  for (const item of items) {
@@ -5174,7 +5311,7 @@ function useCheckOutItemService() {
5174
5311
  supply: item.supply,
5175
5312
  supplyName: supplyData?.name || "",
5176
5313
  qty: item.qty,
5177
- attachment,
5314
+ attachment: item.attachment,
5178
5315
  createdBy,
5179
5316
  createdByName: createdByData?.name || ""
5180
5317
  },
@@ -5265,12 +5402,12 @@ function useCheckOutItemController() {
5265
5402
  };
5266
5403
  const validation = import_joi15.default.object({
5267
5404
  site: import_joi15.default.string().hex().required(),
5268
- attachment: import_joi15.default.array().items(import_joi15.default.string()).optional().allow(null),
5269
5405
  createdBy: import_joi15.default.string().hex().required(),
5270
5406
  items: import_joi15.default.array().items(
5271
5407
  import_joi15.default.object({
5272
5408
  supply: import_joi15.default.string().hex().required(),
5273
- qty: import_joi15.default.number().min(0).required()
5409
+ qty: import_joi15.default.number().min(0).required(),
5410
+ attachment: import_joi15.default.array().items(import_joi15.default.string()).optional().allow(null)
5274
5411
  })
5275
5412
  ).min(1).required()
5276
5413
  });
@@ -5358,8 +5495,9 @@ var scheduleTaskSchema = import_joi16.default.object({
5358
5495
  site: import_joi16.default.string().hex().required(),
5359
5496
  title: import_joi16.default.string().required(),
5360
5497
  time: import_joi16.default.string().pattern(/^([0-1]\d|2[0-3]):([0-5]\d)$/).required(),
5361
- startDate: import_joi16.default.string().pattern(/^\d{4}-\d{2}-\d{2}$/).required(),
5362
- endDate: import_joi16.default.string().pattern(/^\d{4}-\d{2}-\d{2}$/).optional().allow("", null),
5498
+ dates: import_joi16.default.array().min(1).items(
5499
+ import_joi16.default.string().pattern(/^\d{4}-\d{2}-\d{2}$/).required()
5500
+ ).required(),
5363
5501
  description: import_joi16.default.string().optional().allow("", null),
5364
5502
  areas: import_joi16.default.array().min(1).items(
5365
5503
  import_joi16.default.object({
@@ -5405,8 +5543,7 @@ function MScheduleTask(value) {
5405
5543
  site: value.site,
5406
5544
  title: value.title,
5407
5545
  time: value.time,
5408
- startDate: value.startDate,
5409
- endDate: value.endDate,
5546
+ dates: value.dates,
5410
5547
  description: value.description,
5411
5548
  areas: value.areas,
5412
5549
  status: "active",
@@ -5619,8 +5756,7 @@ function useScheduleTaskRepository() {
5619
5756
  $project: {
5620
5757
  title: 1,
5621
5758
  time: 1,
5622
- startDate: 1,
5623
- endDate: 1,
5759
+ dates: 1,
5624
5760
  description: 1,
5625
5761
  areas: 1,
5626
5762
  status: 1,
@@ -5717,29 +5853,24 @@ function useScheduleTaskService() {
5717
5853
  timeZone: "Asia/Singapore"
5718
5854
  });
5719
5855
  const [currentHour, currentMinute] = timeString.split(":").map(Number);
5720
- const currentDateString = now.toLocaleDateString("en-US", {
5721
- timeZone: "Asia/Singapore"
5722
- });
5723
5856
  import_node_server_utils34.logger.info(
5724
- `Checking schedule ${schedule._id}: Current time ${currentHour}:${currentMinute}, Current date ${currentDateString}, Schedule time ${schedule.time}, Start date ${schedule.startDate}, End date ${schedule.endDate}`
5857
+ `Checking schedule ${schedule._id}: Current time ${currentHour}:${currentMinute}, Schedule time ${schedule.time}, Dates ${JSON.stringify(schedule.dates)}`
5725
5858
  );
5726
- const startDate = /* @__PURE__ */ new Date(schedule.startDate + "T00:00:00");
5727
- const currentDateOnly = /* @__PURE__ */ new Date(currentDateString + "T00:00:00");
5728
- if (currentDateOnly < startDate) {
5859
+ const currentDateFormatted = now.toLocaleDateString("en-CA", {
5860
+ timeZone: "Asia/Singapore"
5861
+ });
5862
+ if (!schedule.dates || !Array.isArray(schedule.dates) || schedule.dates.length === 0) {
5863
+ import_node_server_utils34.logger.info(`Schedule ${schedule._id}: No dates configured, skipping`);
5864
+ return false;
5865
+ }
5866
+ if (!schedule.dates.includes(currentDateFormatted)) {
5729
5867
  import_node_server_utils34.logger.info(
5730
- `Schedule ${schedule._id}: Current date ${currentDateString} is before start date ${schedule.startDate}`
5868
+ `Schedule ${schedule._id}: Current date ${currentDateFormatted} is not in scheduled dates [${schedule.dates.join(
5869
+ ", "
5870
+ )}]`
5731
5871
  );
5732
5872
  return false;
5733
5873
  }
5734
- if (schedule.endDate) {
5735
- const endDate = /* @__PURE__ */ new Date(schedule.endDate + "T00:00:00");
5736
- if (currentDateOnly > endDate) {
5737
- import_node_server_utils34.logger.info(
5738
- `Schedule ${schedule._id}: Current date ${currentDateString} is after end date ${schedule.endDate}`
5739
- );
5740
- return false;
5741
- }
5742
- }
5743
5874
  const [scheduleHour, scheduleMinute] = schedule.time.split(":").map(Number);
5744
5875
  const timeMatches = currentHour === scheduleHour && currentMinute === scheduleMinute;
5745
5876
  if (!timeMatches) {
@@ -5775,7 +5906,9 @@ function useScheduleTaskService() {
5775
5906
  for (const scheduleTask of scheduleTasks) {
5776
5907
  try {
5777
5908
  import_node_server_utils34.logger.info(
5778
- `Checking schedule ${scheduleTask._id} - ${scheduleTask.title}: time=${scheduleTask.time}, startDate=${scheduleTask.startDate}, endDate=${scheduleTask.endDate}`
5909
+ `Checking schedule ${scheduleTask._id} - ${scheduleTask.title}: time=${scheduleTask.time}, dates=${JSON.stringify(
5910
+ scheduleTask.dates
5911
+ )}`
5779
5912
  );
5780
5913
  const shouldRun = checkScheduleConditions(scheduleTask, currentDate);
5781
5914
  if (!shouldRun) {
@@ -6085,8 +6218,9 @@ function useScheduleTaskController() {
6085
6218
  id: import_joi17.default.string().hex().required(),
6086
6219
  title: import_joi17.default.string().optional().allow("", null),
6087
6220
  time: import_joi17.default.string().pattern(/^([0-1]\d|2[0-3]):([0-5]\d)$/).optional().allow("", null),
6088
- startDate: import_joi17.default.string().pattern(/^\d{4}-\d{2}-\d{2}$/).optional().allow("", null),
6089
- endDate: import_joi17.default.string().pattern(/^\d{4}-\d{2}-\d{2}$/).optional().allow("", null),
6221
+ dates: import_joi17.default.array().min(1).items(
6222
+ import_joi17.default.string().pattern(/^\d{4}-\d{2}-\d{2}$/).required()
6223
+ ).optional(),
6090
6224
  description: import_joi17.default.string().optional().allow("", null),
6091
6225
  areas: import_joi17.default.array().min(1).items(
6092
6226
  import_joi17.default.object({
@@ -6200,7 +6334,7 @@ function useQRService() {
6200
6334
  throw error;
6201
6335
  }
6202
6336
  }
6203
- async function generateQRPDF(qrUrl, title) {
6337
+ async function generateQRPDF(qrUrl, title, subtitle) {
6204
6338
  try {
6205
6339
  const qrDataUrl = await generateQRDataUrl(qrUrl);
6206
6340
  const browser = await (0, import_puppeteer2.launch)({
@@ -6214,6 +6348,7 @@ function useQRService() {
6214
6348
  height: 1100
6215
6349
  });
6216
6350
  const escapedTitle = (title || "Cleaning Schedule QR Code").replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
6351
+ const escapedSubtitle = subtitle ? subtitle.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;") : "";
6217
6352
  const html = `
6218
6353
  <!DOCTYPE html>
6219
6354
  <html>
@@ -6241,10 +6376,16 @@ function useQRService() {
6241
6376
  padding: 0;
6242
6377
  }
6243
6378
  h1 {
6244
- font-size: 28px;
6379
+ font-size: 38px;
6245
6380
  color: #333;
6246
- margin-bottom: 20px;
6247
- font-weight: 600;
6381
+ margin-bottom: 8px;
6382
+ font-weight: 700;
6383
+ }
6384
+ .subtitle {
6385
+ font-size: 26px;
6386
+ color: #555;
6387
+ margin-bottom: 12px;
6388
+ font-weight: 500;
6248
6389
  }
6249
6390
  .qr-wrapper {
6250
6391
  display: inline-block;
@@ -6279,6 +6420,7 @@ function useQRService() {
6279
6420
  <body>
6280
6421
  <div class="qr-container">
6281
6422
  <h1>${escapedTitle}</h1>
6423
+ ${escapedSubtitle ? `<p class="subtitle">${escapedSubtitle}</p>` : ""}
6282
6424
  <div class="qr-wrapper">
6283
6425
  <img id="qr-image" src="${qrDataUrl}" alt="QR Code" />
6284
6426
  </div>
@@ -6339,6 +6481,7 @@ function useQRController() {
6339
6481
  url: import_joi18.default.string().uri().required(),
6340
6482
  filename: import_joi18.default.string().optional().allow("", null),
6341
6483
  title: import_joi18.default.string().optional().allow("", null),
6484
+ subtitle: import_joi18.default.string().optional().allow("", null),
6342
6485
  download: import_joi18.default.boolean().optional().default(false)
6343
6486
  });
6344
6487
  const query = { ...req.query };
@@ -6349,9 +6492,9 @@ function useQRController() {
6349
6492
  return;
6350
6493
  }
6351
6494
  try {
6352
- const { url, filename, title, download } = value;
6495
+ const { url, filename, title, subtitle, download } = value;
6353
6496
  if (download) {
6354
- const pdfBuffer = await _generateQRPDF(url, title);
6497
+ const pdfBuffer = await _generateQRPDF(url, title, subtitle);
6355
6498
  if (!pdfBuffer || pdfBuffer.length === 0) {
6356
6499
  throw new Error("Generated QR PDF is empty or invalid.");
6357
6500
  }