@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.mjs CHANGED
@@ -1,6 +1,11 @@
1
1
  // src/models/hygiene-base.model.ts
2
2
  var allowedTypes = ["common", "toilet"];
3
- var allowedStatus = ["ready", "ongoing", "completed"];
3
+ var allowedStatus = [
4
+ "open",
5
+ "ongoing",
6
+ "completed",
7
+ "closed"
8
+ ];
4
9
  var allowedPeriods = ["today", "thisWeek", "thisMonth"];
5
10
 
6
11
  // src/repositories/hygiene-dashboard.repository.ts
@@ -1310,10 +1315,14 @@ function useAreaService() {
1310
1315
  try {
1311
1316
  dataArray = JSON.parse(dataJson);
1312
1317
  } catch (error) {
1313
- throw new BadRequestError7("Invalid JSON format for data in excel");
1318
+ throw new BadRequestError7(
1319
+ "We couldn't read the uploaded file. Please make sure you're uploading the correct file and try again."
1320
+ );
1314
1321
  }
1315
1322
  if (!dataArray || dataArray.length === 0) {
1316
- throw new NotFoundError2("No data found in the uploaded file");
1323
+ throw new NotFoundError2(
1324
+ "The uploaded file is empty. Please make sure your file contains data and try again."
1325
+ );
1317
1326
  }
1318
1327
  let availableUnits = [];
1319
1328
  try {
@@ -1414,33 +1423,35 @@ function useAreaService() {
1414
1423
  }
1415
1424
  }
1416
1425
  }
1417
- let message = `Upload completed: ${insertedAreaIds.length} areas successfully created`;
1426
+ let message = `Upload complete! ${insertedAreaIds.length} ${insertedAreaIds.length === 1 ? "area was" : "areas were"} successfully added`;
1418
1427
  if (duplicateAreas.length > 0) {
1419
- message += `, ${duplicateAreas.length} areas skipped (already exist)`;
1428
+ message += `, ${duplicateAreas.length} ${duplicateAreas.length === 1 ? "area was" : "areas were"} skipped because they already exist`;
1420
1429
  }
1421
1430
  if (failedAreas.length > 0) {
1422
- message += `, ${failedAreas.length} areas failed`;
1431
+ message += `, ${failedAreas.length} ${failedAreas.length === 1 ? "area" : "areas"} could not be saved`;
1423
1432
  }
1424
1433
  if (skippedRows.length > 0) {
1425
- message += `, ${skippedRows.length} rows skipped (invalid data)`;
1434
+ message += `, ${skippedRows.length} ${skippedRows.length === 1 ? "row was" : "rows were"} skipped due to missing information`;
1426
1435
  }
1427
1436
  logger8.info(message);
1428
1437
  if (insertedAreaIds.length === 0) {
1429
1438
  if (duplicateAreas.length > 0 && failedAreas.length === 0 && skippedRows.length === 0) {
1430
1439
  return {
1431
- message: `No new areas were created. All ${duplicateAreas.length} areas already exist in the system: ${duplicateAreas.join(", ")}`
1440
+ message: `No new areas were added. All ${duplicateAreas.length} ${duplicateAreas.length === 1 ? "area" : "areas"} in your file already exist in the system: ${duplicateAreas.join(
1441
+ ", "
1442
+ )}.`
1432
1443
  };
1433
1444
  } else if (failedAreas.length > 0) {
1434
1445
  throw new BadRequestError7(
1435
- `No areas were created. ${failedAreas.length} areas failed due to errors. Please check your data format and ensure area names are valid.`
1446
+ `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.`
1436
1447
  );
1437
1448
  } else if (skippedRows.length > 0 && duplicateAreas.length === 0) {
1438
1449
  throw new BadRequestError7(
1439
- `No areas were created. All ${skippedRows.length} rows contained invalid or missing data.`
1450
+ `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.`
1440
1451
  );
1441
1452
  } else {
1442
1453
  return {
1443
- message: `No new areas were created. ${duplicateAreas.length} areas already exist, ${skippedRows.length} rows had invalid data.`
1454
+ 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` : ""}.`
1444
1455
  };
1445
1456
  }
1446
1457
  }
@@ -1451,11 +1462,11 @@ function useAreaService() {
1451
1462
  throw error;
1452
1463
  } else if (error.message.includes("validation")) {
1453
1464
  throw new BadRequestError7(
1454
- "Upload failed due to invalid data format. Please check that all required fields are properly filled."
1465
+ "Upload failed because some required information is missing or incorrect. Please review your file and make sure all fields are filled in properly."
1455
1466
  );
1456
1467
  } else {
1457
1468
  throw new BadRequestError7(
1458
- `Upload failed: ${error.message || "Please check your data format and try again."}`
1469
+ "Something went wrong while uploading. Please check your file and try again."
1459
1470
  );
1460
1471
  }
1461
1472
  }
@@ -1464,11 +1475,15 @@ function useAreaService() {
1464
1475
  try {
1465
1476
  const areas = await getAreasForChecklist(site);
1466
1477
  if (!areas || !Array.isArray(areas) || areas.length === 0) {
1467
- throw new BadRequestError7("No data found to export");
1478
+ throw new BadRequestError7(
1479
+ "There are no areas to export yet. Please add some areas first, then try again."
1480
+ );
1468
1481
  }
1469
1482
  const excelBuffer = await generateAreaExcel(areas);
1470
1483
  if (!excelBuffer || excelBuffer.length === 0) {
1471
- throw new Error("Generated Excel file is empty or invalid.");
1484
+ throw new Error(
1485
+ "Something went wrong while preparing your export file. Please try again."
1486
+ );
1472
1487
  }
1473
1488
  return excelBuffer;
1474
1489
  } catch (error) {
@@ -1773,10 +1788,14 @@ function useUnitService() {
1773
1788
  try {
1774
1789
  dataArray = JSON.parse(dataJson);
1775
1790
  } catch (error) {
1776
- throw new BadRequestError9("Invalid JSON format for data in excel");
1791
+ throw new BadRequestError9(
1792
+ "We couldn't read the uploaded file. Please make sure you're uploading the correct file and try again."
1793
+ );
1777
1794
  }
1778
1795
  if (!dataArray || dataArray.length === 0) {
1779
- throw new NotFoundError3("No data found in the uploaded file");
1796
+ throw new NotFoundError3(
1797
+ "The uploaded file is empty. Please make sure your file contains data and try again."
1798
+ );
1780
1799
  }
1781
1800
  const insertedUnitIds = [];
1782
1801
  const duplicateUnits = [];
@@ -1817,29 +1836,31 @@ function useUnitService() {
1817
1836
  }
1818
1837
  }
1819
1838
  }
1820
- let message = `Upload completed: ${insertedUnitIds.length} units successfully created`;
1839
+ let message = `Upload complete! ${insertedUnitIds.length} ${insertedUnitIds.length === 1 ? "unit was" : "units were"} successfully added`;
1821
1840
  if (duplicateUnits.length > 0) {
1822
- message += `, ${duplicateUnits.length} units skipped (already exist)`;
1841
+ message += `, ${duplicateUnits.length} ${duplicateUnits.length === 1 ? "unit was" : "units were"} skipped because they already exist`;
1823
1842
  }
1824
1843
  if (failedUnits.length > 0) {
1825
- message += `, ${failedUnits.length} units failed`;
1844
+ message += `, ${failedUnits.length} ${failedUnits.length === 1 ? "unit" : "units"} could not be saved`;
1826
1845
  }
1827
1846
  if (skippedRows.length > 0) {
1828
- message += `, ${skippedRows.length} rows skipped (invalid data)`;
1847
+ message += `, ${skippedRows.length} ${skippedRows.length === 1 ? "row was" : "rows were"} skipped due to missing information`;
1829
1848
  }
1830
1849
  logger11.info(message);
1831
1850
  if (insertedUnitIds.length === 0) {
1832
1851
  if (duplicateUnits.length > 0 && failedUnits.length === 0) {
1833
1852
  throw new BadRequestError9(
1834
- `No new units were created. All ${duplicateUnits.length} units already exist in the system: ${duplicateUnits.join(", ")}`
1853
+ `No new units were added. All ${duplicateUnits.length} ${duplicateUnits.length === 1 ? "unit" : "units"} in your file already exist in the system: ${duplicateUnits.join(
1854
+ ", "
1855
+ )}.`
1835
1856
  );
1836
1857
  } else if (failedUnits.length > 0) {
1837
1858
  throw new BadRequestError9(
1838
- `No units were created. Please check your data format and ensure unit names are valid.`
1859
+ `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.`
1839
1860
  );
1840
1861
  } else if (skippedRows.length > 0) {
1841
1862
  throw new BadRequestError9(
1842
- `No units were created. All rows contained invalid or missing unit names.`
1863
+ `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.`
1843
1864
  );
1844
1865
  }
1845
1866
  }
@@ -1850,15 +1871,15 @@ function useUnitService() {
1850
1871
  throw error;
1851
1872
  } else if (error.message.includes("duplicate")) {
1852
1873
  throw new BadRequestError9(
1853
- "Upload failed due to duplicate unit names. Please ensure all unit names are unique."
1874
+ "Upload failed because some unit names are already taken. Please make sure each unit has a unique name and try again."
1854
1875
  );
1855
1876
  } else if (error.message.includes("validation")) {
1856
1877
  throw new BadRequestError9(
1857
- "Upload failed due to invalid data format. Please check that all required fields are properly filled."
1878
+ "Upload failed because some required information is missing or incorrect. Please review your file and make sure all fields are filled in properly."
1858
1879
  );
1859
1880
  } else {
1860
1881
  throw new BadRequestError9(
1861
- `Upload failed: ${error.message || "Please check your data format and try again."}`
1882
+ "Something went wrong while uploading. Please check your file and try again."
1862
1883
  );
1863
1884
  }
1864
1885
  }
@@ -1890,7 +1911,9 @@ function useUnitService() {
1890
1911
  session?.startTransaction();
1891
1912
  const isExistingArea = await verifyAreaByUnitId(_id);
1892
1913
  if (isExistingArea) {
1893
- throw new BadRequestError9("Failed to delete unit, unit is in use.");
1914
+ throw new BadRequestError9(
1915
+ "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."
1916
+ );
1894
1917
  }
1895
1918
  const result = await _deleteUnit(_id, session);
1896
1919
  await session?.commitTransaction();
@@ -1916,11 +1939,15 @@ function useUnitService() {
1916
1939
  site
1917
1940
  });
1918
1941
  if (!data || !data.items || data.items.length === 0) {
1919
- throw new BadRequestError9("No data found to export");
1942
+ throw new BadRequestError9(
1943
+ "There are no units to export yet. Please add some units first, then try again."
1944
+ );
1920
1945
  }
1921
1946
  const excelBuffer = await _generateUnitExcel(data.items);
1922
1947
  if (!excelBuffer || excelBuffer.length === 0) {
1923
- throw new Error("Generated Excel file is empty or invalid.");
1948
+ throw new Error(
1949
+ "Something went wrong while preparing your export file. Please try again."
1950
+ );
1924
1951
  }
1925
1952
  return excelBuffer;
1926
1953
  } catch (error) {
@@ -2127,7 +2154,7 @@ function MParentChecklist(value) {
2127
2154
  }
2128
2155
  return {
2129
2156
  site: value.site,
2130
- status: "ready",
2157
+ status: "open",
2131
2158
  createdAt: value.createdAt ?? /* @__PURE__ */ new Date(),
2132
2159
  updatedAt: value.updatedAt ?? ""
2133
2160
  };
@@ -2325,18 +2352,23 @@ function useParentChecklistRepo() {
2325
2352
  status: {
2326
2353
  $switch: {
2327
2354
  branches: [
2328
- { case: { $eq: ["$status", "ready"] }, then: "Ready" },
2355
+ { case: { $eq: ["$status", "open"] }, then: "Open" },
2329
2356
  {
2330
2357
  case: { $eq: ["$status", "ongoing"] },
2331
2358
  then: "Ongoing"
2332
2359
  },
2333
- { case: { $eq: ["$status", "completed"] }, then: "Completed" }
2360
+ { case: { $eq: ["$status", "completed"] }, then: "Completed" },
2361
+ {
2362
+ case: { $eq: ["$status", "closed"] },
2363
+ then: "Closed"
2364
+ }
2334
2365
  ],
2335
2366
  default: "$status"
2336
2367
  }
2337
2368
  },
2338
2369
  completedAt: 1,
2339
- createdAt: 1
2370
+ createdAt: 1,
2371
+ closeIn: { $add: ["$createdAt", 24 * 60 * 60 * 1e3] }
2340
2372
  }
2341
2373
  });
2342
2374
  pipeline.push(
@@ -2424,11 +2456,42 @@ function useParentChecklistRepo() {
2424
2456
  throw error;
2425
2457
  }
2426
2458
  }
2459
+ async function closeExpiredParentChecklists() {
2460
+ try {
2461
+ const expiryDate = new Date(Date.now() - 24 * 60 * 60 * 1e3);
2462
+ const result = await collection.updateMany(
2463
+ {
2464
+ status: { $in: ["open", "ongoing", "completed"] },
2465
+ createdAt: { $lte: expiryDate }
2466
+ },
2467
+ {
2468
+ $set: {
2469
+ status: "closed",
2470
+ updatedAt: /* @__PURE__ */ new Date()
2471
+ }
2472
+ }
2473
+ );
2474
+ if (result.modifiedCount > 0) {
2475
+ delNamespace().catch((err) => {
2476
+ logger14.error(
2477
+ "Failed to invalidate cache after closing expired parent checklists",
2478
+ err
2479
+ );
2480
+ });
2481
+ logger14.info(`Closed ${result.modifiedCount} expired parent checklists`);
2482
+ }
2483
+ return result.modifiedCount;
2484
+ } catch (error) {
2485
+ logger14.error("Failed to close expired parent checklists", error);
2486
+ throw error;
2487
+ }
2488
+ }
2427
2489
  return {
2428
2490
  createIndex,
2429
2491
  createParentChecklist,
2430
2492
  getAllParentChecklist,
2431
- updateParentChecklistStatuses
2493
+ updateParentChecklistStatuses,
2494
+ closeExpiredParentChecklists
2432
2495
  };
2433
2496
  }
2434
2497
 
@@ -2510,7 +2573,7 @@ function useParentChecklistController() {
2510
2573
  import Joi8 from "joi";
2511
2574
  import { ObjectId as ObjectId8 } from "mongodb";
2512
2575
  import { BadRequestError as BadRequestError14, logger as logger16 } from "@7365admin1/node-server-utils";
2513
- var allowedChecklistStatus = ["ready", "completed"];
2576
+ var allowedChecklistStatus = ["open", "completed", "closed"];
2514
2577
  var areaChecklistSchema = Joi8.object({
2515
2578
  schedule: Joi8.string().hex().required(),
2516
2579
  area: Joi8.string().hex().required(),
@@ -2558,7 +2621,7 @@ function MAreaChecklist(value) {
2558
2621
  name: unit.name,
2559
2622
  approve: false,
2560
2623
  reject: false,
2561
- status: "ready",
2624
+ status: "open",
2562
2625
  remarks: "",
2563
2626
  completedBy: "",
2564
2627
  timestamp: ""
@@ -2579,7 +2642,7 @@ function MAreaChecklist(value) {
2579
2642
  name: value.name,
2580
2643
  type: value.type,
2581
2644
  checklist: value.checklist || [],
2582
- status: "ready",
2645
+ status: "open",
2583
2646
  createdBy: value.createdBy,
2584
2647
  createdAt: /* @__PURE__ */ new Date(),
2585
2648
  completedAt: "",
@@ -2745,17 +2808,37 @@ function useAreaChecklistRepo() {
2745
2808
  preserveNullAndEmptyArrays: true
2746
2809
  }
2747
2810
  },
2811
+ {
2812
+ $addFields: {
2813
+ statusOrder: {
2814
+ $switch: {
2815
+ branches: [
2816
+ { case: { $eq: ["$status", "open"] }, then: 0 },
2817
+ { case: { $eq: ["$status", "ongoing"] }, then: 1 },
2818
+ { case: { $eq: ["$status", "completed"] }, then: 3 },
2819
+ { case: { $eq: ["$status", "closed"] }, then: 4 }
2820
+ ],
2821
+ default: 2
2822
+ }
2823
+ }
2824
+ }
2825
+ },
2826
+ { $sort: { statusOrder: 1, _id: -1 } },
2748
2827
  {
2749
2828
  $project: {
2750
2829
  name: 1,
2751
2830
  status: {
2752
2831
  $switch: {
2753
2832
  branches: [
2754
- { case: { $eq: ["$status", "ready"] }, then: "Ready" },
2833
+ { case: { $eq: ["$status", "open"] }, then: "Open" },
2755
2834
  { case: { $eq: ["$status", "ongoing"] }, then: "Ongoing" },
2756
2835
  {
2757
2836
  case: { $eq: ["$status", "completed"] },
2758
2837
  then: "Completed"
2838
+ },
2839
+ {
2840
+ case: { $eq: ["$status", "closed"] },
2841
+ then: "Closed"
2759
2842
  }
2760
2843
  ],
2761
2844
  default: "$status"
@@ -2780,7 +2863,6 @@ function useAreaChecklistRepo() {
2780
2863
  createdByName: "$createdByDoc.name"
2781
2864
  }
2782
2865
  },
2783
- { $sort: { _id: -1 } },
2784
2866
  { $skip: page * limit },
2785
2867
  { $limit: limit }
2786
2868
  ];
@@ -2857,11 +2939,15 @@ function useAreaChecklistRepo() {
2857
2939
  status: {
2858
2940
  $switch: {
2859
2941
  branches: [
2860
- { case: { $eq: ["$status", "ready"] }, then: "Ready" },
2942
+ { case: { $eq: ["$status", "open"] }, then: "Open" },
2861
2943
  { case: { $eq: ["$status", "ongoing"] }, then: "Ongoing" },
2862
2944
  {
2863
2945
  case: { $eq: ["$status", "completed"] },
2864
2946
  then: "Completed"
2947
+ },
2948
+ {
2949
+ case: { $eq: ["$status", "closed"] },
2950
+ then: "Closed"
2865
2951
  }
2866
2952
  ],
2867
2953
  default: "$status"
@@ -2926,11 +3012,15 @@ function useAreaChecklistRepo() {
2926
3012
  status: {
2927
3013
  $switch: {
2928
3014
  branches: [
2929
- { case: { $eq: ["$status", "ready"] }, then: "Ready" },
3015
+ { case: { $eq: ["$status", "open"] }, then: "Open" },
2930
3016
  { case: { $eq: ["$status", "ongoing"] }, then: "Ongoing" },
2931
3017
  {
2932
3018
  case: { $eq: ["$status", "completed"] },
2933
3019
  then: "Completed"
3020
+ },
3021
+ {
3022
+ case: { $eq: ["$status", "closed"] },
3023
+ then: "Closed"
2934
3024
  }
2935
3025
  ],
2936
3026
  default: "$status"
@@ -2990,12 +3080,16 @@ function useAreaChecklistRepo() {
2990
3080
  $switch: {
2991
3081
  branches: [
2992
3082
  {
2993
- case: { $eq: ["$checklist.units.status", "ready"] },
2994
- then: "Ready"
3083
+ case: { $eq: ["$checklist.units.status", "open"] },
3084
+ then: "Open"
2995
3085
  },
2996
3086
  {
2997
3087
  case: { $eq: ["$checklist.units.status", "completed"] },
2998
3088
  then: "Completed"
3089
+ },
3090
+ {
3091
+ case: { $eq: ["$checklist.units.status", "closed"] },
3092
+ then: "Closed"
2999
3093
  }
3000
3094
  ],
3001
3095
  default: "$checklist.units.status"
@@ -3138,16 +3232,21 @@ function useAreaChecklistRepo() {
3138
3232
  name: "$checklist.units.name",
3139
3233
  approve: "$checklist.units.approve",
3140
3234
  reject: "$checklist.units.reject",
3235
+ timestamp: "$checklist.units.timestamp",
3141
3236
  status: {
3142
3237
  $switch: {
3143
3238
  branches: [
3144
3239
  {
3145
- case: { $eq: ["$checklist.units.status", "ready"] },
3146
- then: "Ready"
3240
+ case: { $eq: ["$checklist.units.status", "open"] },
3241
+ then: "Open"
3147
3242
  },
3148
3243
  {
3149
3244
  case: { $eq: ["$checklist.units.status", "completed"] },
3150
3245
  then: "Completed"
3246
+ },
3247
+ {
3248
+ case: { $eq: ["$checklist.units.status", "closed"] },
3249
+ then: "Closed"
3151
3250
  }
3152
3251
  ],
3153
3252
  default: "$checklist.units.status"
@@ -3168,6 +3267,7 @@ function useAreaChecklistRepo() {
3168
3267
  name: "$name",
3169
3268
  approve: "$approve",
3170
3269
  reject: "$reject",
3270
+ timestamp: "$timestamp",
3171
3271
  status: "$status",
3172
3272
  remarks: "$remarks",
3173
3273
  completedByName: "$completedByName"
@@ -3280,7 +3380,7 @@ function useAreaChecklistRepo() {
3280
3380
  } else if (value.reject === true) {
3281
3381
  updateValue["checklist.$[checklist].units.$[unit].approve"] = false;
3282
3382
  updateValue["checklist.$[checklist].units.$[unit].reject"] = true;
3283
- updateValue["checklist.$[checklist].units.$[unit].status"] = "ready";
3383
+ updateValue["checklist.$[checklist].units.$[unit].status"] = "open";
3284
3384
  }
3285
3385
  if (value.remarks) {
3286
3386
  updateValue["checklist.$[checklist].units.$[unit].remarks"] = value.remarks;
@@ -3406,6 +3506,42 @@ function useAreaChecklistRepo() {
3406
3506
  return 0;
3407
3507
  }
3408
3508
  }
3509
+ async function closeExpiredAreaChecklists() {
3510
+ try {
3511
+ const expiryDate = new Date(Date.now() - 24 * 60 * 60 * 1e3);
3512
+ const result = await collection.updateMany(
3513
+ {
3514
+ status: { $in: ["open", "ongoing", "completed"] },
3515
+ createdAt: { $lte: expiryDate }
3516
+ },
3517
+ {
3518
+ $set: {
3519
+ status: "closed",
3520
+ updatedAt: /* @__PURE__ */ new Date(),
3521
+ "checklist.$[].units.$[unit].status": "closed"
3522
+ }
3523
+ },
3524
+ {
3525
+ arrayFilters: [{ "unit.status": { $in: ["open", "completed"] } }]
3526
+ }
3527
+ );
3528
+ if (result.modifiedCount > 0) {
3529
+ delNamespace().catch((err) => {
3530
+ logger17.error(
3531
+ "Failed to invalidate cache after closing expired area checklists",
3532
+ err
3533
+ );
3534
+ });
3535
+ logger17.info(
3536
+ `Closed ${result.modifiedCount} expired area checklists and their units`
3537
+ );
3538
+ }
3539
+ return result.modifiedCount;
3540
+ } catch (error) {
3541
+ logger17.error("Failed to close expired area checklists", error);
3542
+ throw error;
3543
+ }
3544
+ }
3409
3545
  return {
3410
3546
  createIndex,
3411
3547
  createTextIndex,
@@ -3419,7 +3555,8 @@ function useAreaChecklistRepo() {
3419
3555
  updateAreaChecklist,
3420
3556
  updateAreaChecklistStatus,
3421
3557
  updateAreaChecklistUnits,
3422
- getMaxSetNumberForArea
3558
+ getMaxSetNumberForArea,
3559
+ closeExpiredAreaChecklists
3423
3560
  };
3424
3561
  }
3425
3562
 
@@ -3515,12 +3652,12 @@ function useAreaChecklistService() {
3515
3652
  },
3516
3653
  session
3517
3654
  );
3518
- let areaStatus = "ready";
3655
+ let areaStatus = "open";
3519
3656
  if (allUnitsResult && allUnitsResult.items && allUnitsResult.items.length > 0) {
3520
3657
  const sets = allUnitsResult.items;
3521
3658
  const allUnits = sets.flatMap((set2) => set2.units || []);
3522
- const readyCount = allUnits.filter(
3523
- (unit) => unit.status === "Ready"
3659
+ const openCount = allUnits.filter(
3660
+ (unit) => unit.status === "Open"
3524
3661
  ).length;
3525
3662
  const completedCount = allUnits.filter(
3526
3663
  (unit) => unit.status === "Completed"
@@ -3528,8 +3665,8 @@ function useAreaChecklistService() {
3528
3665
  const totalCount = allUnits.length;
3529
3666
  if (completedCount === totalCount) {
3530
3667
  areaStatus = "completed";
3531
- } else if (readyCount === totalCount) {
3532
- areaStatus = "ready";
3668
+ } else if (openCount === totalCount) {
3669
+ areaStatus = "open";
3533
3670
  } else {
3534
3671
  areaStatus = "ongoing";
3535
3672
  }
@@ -3548,18 +3685,18 @@ function useAreaChecklistService() {
3548
3685
  );
3549
3686
  if (allAreasResult && allAreasResult.items && allAreasResult.items.length > 0) {
3550
3687
  const areas = allAreasResult.items;
3551
- const readyAreasCount = areas.filter(
3552
- (area) => area.status === "Ready"
3688
+ const openAreasCount = areas.filter(
3689
+ (area) => area.status === "Open"
3553
3690
  ).length;
3554
3691
  const completedAreasCount = areas.filter(
3555
3692
  (area) => area.status === "Completed"
3556
3693
  ).length;
3557
3694
  const totalAreasCount = areas.length;
3558
- let parentStatus = "ready";
3695
+ let parentStatus = "open";
3559
3696
  if (completedAreasCount === totalAreasCount) {
3560
3697
  parentStatus = "completed";
3561
- } else if (readyAreasCount === totalAreasCount) {
3562
- parentStatus = "ready";
3698
+ } else if (openAreasCount === totalAreasCount) {
3699
+ parentStatus = "open";
3563
3700
  } else {
3564
3701
  parentStatus = "ongoing";
3565
3702
  }
@@ -3570,7 +3707,7 @@ function useAreaChecklistService() {
3570
3707
  );
3571
3708
  } else {
3572
3709
  logger18.info(
3573
- "No area checklists found, keeping parent status as ready"
3710
+ "No area checklists found, keeping parent status as open"
3574
3711
  );
3575
3712
  }
3576
3713
  }
@@ -5158,7 +5295,7 @@ function useCheckOutItemService() {
5158
5295
  const session = useAtlas13.getClient()?.startSession();
5159
5296
  try {
5160
5297
  session?.startTransaction();
5161
- const { site, attachment, createdBy, items } = value;
5298
+ const { site, createdBy, items } = value;
5162
5299
  const createdByData = await getUserById(createdBy);
5163
5300
  const createdCheckOutItemIds = [];
5164
5301
  for (const item of items) {
@@ -5169,7 +5306,7 @@ function useCheckOutItemService() {
5169
5306
  supply: item.supply,
5170
5307
  supplyName: supplyData?.name || "",
5171
5308
  qty: item.qty,
5172
- attachment,
5309
+ attachment: item.attachment,
5173
5310
  createdBy,
5174
5311
  createdByName: createdByData?.name || ""
5175
5312
  },
@@ -5260,12 +5397,12 @@ function useCheckOutItemController() {
5260
5397
  };
5261
5398
  const validation = Joi15.object({
5262
5399
  site: Joi15.string().hex().required(),
5263
- attachment: Joi15.array().items(Joi15.string()).optional().allow(null),
5264
5400
  createdBy: Joi15.string().hex().required(),
5265
5401
  items: Joi15.array().items(
5266
5402
  Joi15.object({
5267
5403
  supply: Joi15.string().hex().required(),
5268
- qty: Joi15.number().min(0).required()
5404
+ qty: Joi15.number().min(0).required(),
5405
+ attachment: Joi15.array().items(Joi15.string()).optional().allow(null)
5269
5406
  })
5270
5407
  ).min(1).required()
5271
5408
  });
@@ -5353,8 +5490,9 @@ var scheduleTaskSchema = Joi16.object({
5353
5490
  site: Joi16.string().hex().required(),
5354
5491
  title: Joi16.string().required(),
5355
5492
  time: Joi16.string().pattern(/^([0-1]\d|2[0-3]):([0-5]\d)$/).required(),
5356
- startDate: Joi16.string().pattern(/^\d{4}-\d{2}-\d{2}$/).required(),
5357
- endDate: Joi16.string().pattern(/^\d{4}-\d{2}-\d{2}$/).optional().allow("", null),
5493
+ dates: Joi16.array().min(1).items(
5494
+ Joi16.string().pattern(/^\d{4}-\d{2}-\d{2}$/).required()
5495
+ ).required(),
5358
5496
  description: Joi16.string().optional().allow("", null),
5359
5497
  areas: Joi16.array().min(1).items(
5360
5498
  Joi16.object({
@@ -5400,8 +5538,7 @@ function MScheduleTask(value) {
5400
5538
  site: value.site,
5401
5539
  title: value.title,
5402
5540
  time: value.time,
5403
- startDate: value.startDate,
5404
- endDate: value.endDate,
5541
+ dates: value.dates,
5405
5542
  description: value.description,
5406
5543
  areas: value.areas,
5407
5544
  status: "active",
@@ -5623,8 +5760,7 @@ function useScheduleTaskRepository() {
5623
5760
  $project: {
5624
5761
  title: 1,
5625
5762
  time: 1,
5626
- startDate: 1,
5627
- endDate: 1,
5763
+ dates: 1,
5628
5764
  description: 1,
5629
5765
  areas: 1,
5630
5766
  status: 1,
@@ -5721,29 +5857,24 @@ function useScheduleTaskService() {
5721
5857
  timeZone: "Asia/Singapore"
5722
5858
  });
5723
5859
  const [currentHour, currentMinute] = timeString.split(":").map(Number);
5724
- const currentDateString = now.toLocaleDateString("en-US", {
5725
- timeZone: "Asia/Singapore"
5726
- });
5727
5860
  logger32.info(
5728
- `Checking schedule ${schedule._id}: Current time ${currentHour}:${currentMinute}, Current date ${currentDateString}, Schedule time ${schedule.time}, Start date ${schedule.startDate}, End date ${schedule.endDate}`
5861
+ `Checking schedule ${schedule._id}: Current time ${currentHour}:${currentMinute}, Schedule time ${schedule.time}, Dates ${JSON.stringify(schedule.dates)}`
5729
5862
  );
5730
- const startDate = /* @__PURE__ */ new Date(schedule.startDate + "T00:00:00");
5731
- const currentDateOnly = /* @__PURE__ */ new Date(currentDateString + "T00:00:00");
5732
- if (currentDateOnly < startDate) {
5863
+ const currentDateFormatted = now.toLocaleDateString("en-CA", {
5864
+ timeZone: "Asia/Singapore"
5865
+ });
5866
+ if (!schedule.dates || !Array.isArray(schedule.dates) || schedule.dates.length === 0) {
5867
+ logger32.info(`Schedule ${schedule._id}: No dates configured, skipping`);
5868
+ return false;
5869
+ }
5870
+ if (!schedule.dates.includes(currentDateFormatted)) {
5733
5871
  logger32.info(
5734
- `Schedule ${schedule._id}: Current date ${currentDateString} is before start date ${schedule.startDate}`
5872
+ `Schedule ${schedule._id}: Current date ${currentDateFormatted} is not in scheduled dates [${schedule.dates.join(
5873
+ ", "
5874
+ )}]`
5735
5875
  );
5736
5876
  return false;
5737
5877
  }
5738
- if (schedule.endDate) {
5739
- const endDate = /* @__PURE__ */ new Date(schedule.endDate + "T00:00:00");
5740
- if (currentDateOnly > endDate) {
5741
- logger32.info(
5742
- `Schedule ${schedule._id}: Current date ${currentDateString} is after end date ${schedule.endDate}`
5743
- );
5744
- return false;
5745
- }
5746
- }
5747
5878
  const [scheduleHour, scheduleMinute] = schedule.time.split(":").map(Number);
5748
5879
  const timeMatches = currentHour === scheduleHour && currentMinute === scheduleMinute;
5749
5880
  if (!timeMatches) {
@@ -5779,7 +5910,9 @@ function useScheduleTaskService() {
5779
5910
  for (const scheduleTask of scheduleTasks) {
5780
5911
  try {
5781
5912
  logger32.info(
5782
- `Checking schedule ${scheduleTask._id} - ${scheduleTask.title}: time=${scheduleTask.time}, startDate=${scheduleTask.startDate}, endDate=${scheduleTask.endDate}`
5913
+ `Checking schedule ${scheduleTask._id} - ${scheduleTask.title}: time=${scheduleTask.time}, dates=${JSON.stringify(
5914
+ scheduleTask.dates
5915
+ )}`
5783
5916
  );
5784
5917
  const shouldRun = checkScheduleConditions(scheduleTask, currentDate);
5785
5918
  if (!shouldRun) {
@@ -6089,8 +6222,9 @@ function useScheduleTaskController() {
6089
6222
  id: Joi17.string().hex().required(),
6090
6223
  title: Joi17.string().optional().allow("", null),
6091
6224
  time: Joi17.string().pattern(/^([0-1]\d|2[0-3]):([0-5]\d)$/).optional().allow("", null),
6092
- startDate: Joi17.string().pattern(/^\d{4}-\d{2}-\d{2}$/).optional().allow("", null),
6093
- endDate: Joi17.string().pattern(/^\d{4}-\d{2}-\d{2}$/).optional().allow("", null),
6225
+ dates: Joi17.array().min(1).items(
6226
+ Joi17.string().pattern(/^\d{4}-\d{2}-\d{2}$/).required()
6227
+ ).optional(),
6094
6228
  description: Joi17.string().optional().allow("", null),
6095
6229
  areas: Joi17.array().min(1).items(
6096
6230
  Joi17.object({
@@ -6204,7 +6338,7 @@ function useQRService() {
6204
6338
  throw error;
6205
6339
  }
6206
6340
  }
6207
- async function generateQRPDF(qrUrl, title) {
6341
+ async function generateQRPDF(qrUrl, title, subtitle) {
6208
6342
  try {
6209
6343
  const qrDataUrl = await generateQRDataUrl(qrUrl);
6210
6344
  const browser = await launch2({
@@ -6218,6 +6352,7 @@ function useQRService() {
6218
6352
  height: 1100
6219
6353
  });
6220
6354
  const escapedTitle = (title || "Cleaning Schedule QR Code").replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
6355
+ const escapedSubtitle = subtitle ? subtitle.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;") : "";
6221
6356
  const html = `
6222
6357
  <!DOCTYPE html>
6223
6358
  <html>
@@ -6245,10 +6380,16 @@ function useQRService() {
6245
6380
  padding: 0;
6246
6381
  }
6247
6382
  h1 {
6248
- font-size: 28px;
6383
+ font-size: 38px;
6249
6384
  color: #333;
6250
- margin-bottom: 20px;
6251
- font-weight: 600;
6385
+ margin-bottom: 8px;
6386
+ font-weight: 700;
6387
+ }
6388
+ .subtitle {
6389
+ font-size: 26px;
6390
+ color: #555;
6391
+ margin-bottom: 12px;
6392
+ font-weight: 500;
6252
6393
  }
6253
6394
  .qr-wrapper {
6254
6395
  display: inline-block;
@@ -6283,6 +6424,7 @@ function useQRService() {
6283
6424
  <body>
6284
6425
  <div class="qr-container">
6285
6426
  <h1>${escapedTitle}</h1>
6427
+ ${escapedSubtitle ? `<p class="subtitle">${escapedSubtitle}</p>` : ""}
6286
6428
  <div class="qr-wrapper">
6287
6429
  <img id="qr-image" src="${qrDataUrl}" alt="QR Code" />
6288
6430
  </div>
@@ -6343,6 +6485,7 @@ function useQRController() {
6343
6485
  url: Joi18.string().uri().required(),
6344
6486
  filename: Joi18.string().optional().allow("", null),
6345
6487
  title: Joi18.string().optional().allow("", null),
6488
+ subtitle: Joi18.string().optional().allow("", null),
6346
6489
  download: Joi18.boolean().optional().default(false)
6347
6490
  });
6348
6491
  const query = { ...req.query };
@@ -6353,9 +6496,9 @@ function useQRController() {
6353
6496
  return;
6354
6497
  }
6355
6498
  try {
6356
- const { url, filename, title, download } = value;
6499
+ const { url, filename, title, subtitle, download } = value;
6357
6500
  if (download) {
6358
- const pdfBuffer = await _generateQRPDF(url, title);
6501
+ const pdfBuffer = await _generateQRPDF(url, title, subtitle);
6359
6502
  if (!pdfBuffer || pdfBuffer.length === 0) {
6360
6503
  throw new Error("Generated QR PDF is empty or invalid.");
6361
6504
  }