@7365admin1/module-hygiene 4.8.0 → 4.9.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: "",
@@ -2751,11 +2814,15 @@ function useAreaChecklistRepo() {
2751
2814
  status: {
2752
2815
  $switch: {
2753
2816
  branches: [
2754
- { case: { $eq: ["$status", "ready"] }, then: "Ready" },
2817
+ { case: { $eq: ["$status", "open"] }, then: "Open" },
2755
2818
  { case: { $eq: ["$status", "ongoing"] }, then: "Ongoing" },
2756
2819
  {
2757
2820
  case: { $eq: ["$status", "completed"] },
2758
2821
  then: "Completed"
2822
+ },
2823
+ {
2824
+ case: { $eq: ["$status", "closed"] },
2825
+ then: "Closed"
2759
2826
  }
2760
2827
  ],
2761
2828
  default: "$status"
@@ -2857,11 +2924,15 @@ function useAreaChecklistRepo() {
2857
2924
  status: {
2858
2925
  $switch: {
2859
2926
  branches: [
2860
- { case: { $eq: ["$status", "ready"] }, then: "Ready" },
2927
+ { case: { $eq: ["$status", "open"] }, then: "Open" },
2861
2928
  { case: { $eq: ["$status", "ongoing"] }, then: "Ongoing" },
2862
2929
  {
2863
2930
  case: { $eq: ["$status", "completed"] },
2864
2931
  then: "Completed"
2932
+ },
2933
+ {
2934
+ case: { $eq: ["$status", "closed"] },
2935
+ then: "Closed"
2865
2936
  }
2866
2937
  ],
2867
2938
  default: "$status"
@@ -2926,11 +2997,15 @@ function useAreaChecklistRepo() {
2926
2997
  status: {
2927
2998
  $switch: {
2928
2999
  branches: [
2929
- { case: { $eq: ["$status", "ready"] }, then: "Ready" },
3000
+ { case: { $eq: ["$status", "open"] }, then: "Open" },
2930
3001
  { case: { $eq: ["$status", "ongoing"] }, then: "Ongoing" },
2931
3002
  {
2932
3003
  case: { $eq: ["$status", "completed"] },
2933
3004
  then: "Completed"
3005
+ },
3006
+ {
3007
+ case: { $eq: ["$status", "closed"] },
3008
+ then: "Closed"
2934
3009
  }
2935
3010
  ],
2936
3011
  default: "$status"
@@ -2990,12 +3065,16 @@ function useAreaChecklistRepo() {
2990
3065
  $switch: {
2991
3066
  branches: [
2992
3067
  {
2993
- case: { $eq: ["$checklist.units.status", "ready"] },
2994
- then: "Ready"
3068
+ case: { $eq: ["$checklist.units.status", "open"] },
3069
+ then: "Open"
2995
3070
  },
2996
3071
  {
2997
3072
  case: { $eq: ["$checklist.units.status", "completed"] },
2998
3073
  then: "Completed"
3074
+ },
3075
+ {
3076
+ case: { $eq: ["$checklist.units.status", "closed"] },
3077
+ then: "Closed"
2999
3078
  }
3000
3079
  ],
3001
3080
  default: "$checklist.units.status"
@@ -3142,12 +3221,16 @@ function useAreaChecklistRepo() {
3142
3221
  $switch: {
3143
3222
  branches: [
3144
3223
  {
3145
- case: { $eq: ["$checklist.units.status", "ready"] },
3146
- then: "Ready"
3224
+ case: { $eq: ["$checklist.units.status", "open"] },
3225
+ then: "Open"
3147
3226
  },
3148
3227
  {
3149
3228
  case: { $eq: ["$checklist.units.status", "completed"] },
3150
3229
  then: "Completed"
3230
+ },
3231
+ {
3232
+ case: { $eq: ["$checklist.units.status", "closed"] },
3233
+ then: "Closed"
3151
3234
  }
3152
3235
  ],
3153
3236
  default: "$checklist.units.status"
@@ -3280,7 +3363,7 @@ function useAreaChecklistRepo() {
3280
3363
  } else if (value.reject === true) {
3281
3364
  updateValue["checklist.$[checklist].units.$[unit].approve"] = false;
3282
3365
  updateValue["checklist.$[checklist].units.$[unit].reject"] = true;
3283
- updateValue["checklist.$[checklist].units.$[unit].status"] = "ready";
3366
+ updateValue["checklist.$[checklist].units.$[unit].status"] = "open";
3284
3367
  }
3285
3368
  if (value.remarks) {
3286
3369
  updateValue["checklist.$[checklist].units.$[unit].remarks"] = value.remarks;
@@ -3406,6 +3489,42 @@ function useAreaChecklistRepo() {
3406
3489
  return 0;
3407
3490
  }
3408
3491
  }
3492
+ async function closeExpiredAreaChecklists() {
3493
+ try {
3494
+ const expiryDate = new Date(Date.now() - 24 * 60 * 60 * 1e3);
3495
+ const result = await collection.updateMany(
3496
+ {
3497
+ status: { $in: ["open", "ongoing", "completed"] },
3498
+ createdAt: { $lte: expiryDate }
3499
+ },
3500
+ {
3501
+ $set: {
3502
+ status: "closed",
3503
+ updatedAt: /* @__PURE__ */ new Date(),
3504
+ "checklist.$[].units.$[unit].status": "closed"
3505
+ }
3506
+ },
3507
+ {
3508
+ arrayFilters: [{ "unit.status": { $in: ["open", "completed"] } }]
3509
+ }
3510
+ );
3511
+ if (result.modifiedCount > 0) {
3512
+ delNamespace().catch((err) => {
3513
+ logger17.error(
3514
+ "Failed to invalidate cache after closing expired area checklists",
3515
+ err
3516
+ );
3517
+ });
3518
+ logger17.info(
3519
+ `Closed ${result.modifiedCount} expired area checklists and their units`
3520
+ );
3521
+ }
3522
+ return result.modifiedCount;
3523
+ } catch (error) {
3524
+ logger17.error("Failed to close expired area checklists", error);
3525
+ throw error;
3526
+ }
3527
+ }
3409
3528
  return {
3410
3529
  createIndex,
3411
3530
  createTextIndex,
@@ -3419,7 +3538,8 @@ function useAreaChecklistRepo() {
3419
3538
  updateAreaChecklist,
3420
3539
  updateAreaChecklistStatus,
3421
3540
  updateAreaChecklistUnits,
3422
- getMaxSetNumberForArea
3541
+ getMaxSetNumberForArea,
3542
+ closeExpiredAreaChecklists
3423
3543
  };
3424
3544
  }
3425
3545
 
@@ -3515,12 +3635,12 @@ function useAreaChecklistService() {
3515
3635
  },
3516
3636
  session
3517
3637
  );
3518
- let areaStatus = "ready";
3638
+ let areaStatus = "open";
3519
3639
  if (allUnitsResult && allUnitsResult.items && allUnitsResult.items.length > 0) {
3520
3640
  const sets = allUnitsResult.items;
3521
3641
  const allUnits = sets.flatMap((set2) => set2.units || []);
3522
- const readyCount = allUnits.filter(
3523
- (unit) => unit.status === "Ready"
3642
+ const openCount = allUnits.filter(
3643
+ (unit) => unit.status === "Open"
3524
3644
  ).length;
3525
3645
  const completedCount = allUnits.filter(
3526
3646
  (unit) => unit.status === "Completed"
@@ -3528,8 +3648,8 @@ function useAreaChecklistService() {
3528
3648
  const totalCount = allUnits.length;
3529
3649
  if (completedCount === totalCount) {
3530
3650
  areaStatus = "completed";
3531
- } else if (readyCount === totalCount) {
3532
- areaStatus = "ready";
3651
+ } else if (openCount === totalCount) {
3652
+ areaStatus = "open";
3533
3653
  } else {
3534
3654
  areaStatus = "ongoing";
3535
3655
  }
@@ -3548,18 +3668,18 @@ function useAreaChecklistService() {
3548
3668
  );
3549
3669
  if (allAreasResult && allAreasResult.items && allAreasResult.items.length > 0) {
3550
3670
  const areas = allAreasResult.items;
3551
- const readyAreasCount = areas.filter(
3552
- (area) => area.status === "Ready"
3671
+ const openAreasCount = areas.filter(
3672
+ (area) => area.status === "Open"
3553
3673
  ).length;
3554
3674
  const completedAreasCount = areas.filter(
3555
3675
  (area) => area.status === "Completed"
3556
3676
  ).length;
3557
3677
  const totalAreasCount = areas.length;
3558
- let parentStatus = "ready";
3678
+ let parentStatus = "open";
3559
3679
  if (completedAreasCount === totalAreasCount) {
3560
3680
  parentStatus = "completed";
3561
- } else if (readyAreasCount === totalAreasCount) {
3562
- parentStatus = "ready";
3681
+ } else if (openAreasCount === totalAreasCount) {
3682
+ parentStatus = "open";
3563
3683
  } else {
3564
3684
  parentStatus = "ongoing";
3565
3685
  }
@@ -3570,7 +3690,7 @@ function useAreaChecklistService() {
3570
3690
  );
3571
3691
  } else {
3572
3692
  logger18.info(
3573
- "No area checklists found, keeping parent status as ready"
3693
+ "No area checklists found, keeping parent status as open"
3574
3694
  );
3575
3695
  }
3576
3696
  }
@@ -5158,7 +5278,7 @@ function useCheckOutItemService() {
5158
5278
  const session = useAtlas13.getClient()?.startSession();
5159
5279
  try {
5160
5280
  session?.startTransaction();
5161
- const { site, attachment, createdBy, items } = value;
5281
+ const { site, createdBy, items } = value;
5162
5282
  const createdByData = await getUserById(createdBy);
5163
5283
  const createdCheckOutItemIds = [];
5164
5284
  for (const item of items) {
@@ -5169,7 +5289,7 @@ function useCheckOutItemService() {
5169
5289
  supply: item.supply,
5170
5290
  supplyName: supplyData?.name || "",
5171
5291
  qty: item.qty,
5172
- attachment,
5292
+ attachment: item.attachment,
5173
5293
  createdBy,
5174
5294
  createdByName: createdByData?.name || ""
5175
5295
  },
@@ -5260,12 +5380,12 @@ function useCheckOutItemController() {
5260
5380
  };
5261
5381
  const validation = Joi15.object({
5262
5382
  site: Joi15.string().hex().required(),
5263
- attachment: Joi15.array().items(Joi15.string()).optional().allow(null),
5264
5383
  createdBy: Joi15.string().hex().required(),
5265
5384
  items: Joi15.array().items(
5266
5385
  Joi15.object({
5267
5386
  supply: Joi15.string().hex().required(),
5268
- qty: Joi15.number().min(0).required()
5387
+ qty: Joi15.number().min(0).required(),
5388
+ attachment: Joi15.array().items(Joi15.string()).optional().allow(null)
5269
5389
  })
5270
5390
  ).min(1).required()
5271
5391
  });
@@ -5353,8 +5473,9 @@ var scheduleTaskSchema = Joi16.object({
5353
5473
  site: Joi16.string().hex().required(),
5354
5474
  title: Joi16.string().required(),
5355
5475
  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),
5476
+ dates: Joi16.array().min(1).items(
5477
+ Joi16.string().pattern(/^\d{4}-\d{2}-\d{2}$/).required()
5478
+ ).required(),
5358
5479
  description: Joi16.string().optional().allow("", null),
5359
5480
  areas: Joi16.array().min(1).items(
5360
5481
  Joi16.object({
@@ -5400,8 +5521,7 @@ function MScheduleTask(value) {
5400
5521
  site: value.site,
5401
5522
  title: value.title,
5402
5523
  time: value.time,
5403
- startDate: value.startDate,
5404
- endDate: value.endDate,
5524
+ dates: value.dates,
5405
5525
  description: value.description,
5406
5526
  areas: value.areas,
5407
5527
  status: "active",
@@ -5623,8 +5743,7 @@ function useScheduleTaskRepository() {
5623
5743
  $project: {
5624
5744
  title: 1,
5625
5745
  time: 1,
5626
- startDate: 1,
5627
- endDate: 1,
5746
+ dates: 1,
5628
5747
  description: 1,
5629
5748
  areas: 1,
5630
5749
  status: 1,
@@ -5721,29 +5840,24 @@ function useScheduleTaskService() {
5721
5840
  timeZone: "Asia/Singapore"
5722
5841
  });
5723
5842
  const [currentHour, currentMinute] = timeString.split(":").map(Number);
5724
- const currentDateString = now.toLocaleDateString("en-US", {
5725
- timeZone: "Asia/Singapore"
5726
- });
5727
5843
  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}`
5844
+ `Checking schedule ${schedule._id}: Current time ${currentHour}:${currentMinute}, Schedule time ${schedule.time}, Dates ${JSON.stringify(schedule.dates)}`
5729
5845
  );
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) {
5846
+ const currentDateFormatted = now.toLocaleDateString("en-CA", {
5847
+ timeZone: "Asia/Singapore"
5848
+ });
5849
+ if (!schedule.dates || !Array.isArray(schedule.dates) || schedule.dates.length === 0) {
5850
+ logger32.info(`Schedule ${schedule._id}: No dates configured, skipping`);
5851
+ return false;
5852
+ }
5853
+ if (!schedule.dates.includes(currentDateFormatted)) {
5733
5854
  logger32.info(
5734
- `Schedule ${schedule._id}: Current date ${currentDateString} is before start date ${schedule.startDate}`
5855
+ `Schedule ${schedule._id}: Current date ${currentDateFormatted} is not in scheduled dates [${schedule.dates.join(
5856
+ ", "
5857
+ )}]`
5735
5858
  );
5736
5859
  return false;
5737
5860
  }
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
5861
  const [scheduleHour, scheduleMinute] = schedule.time.split(":").map(Number);
5748
5862
  const timeMatches = currentHour === scheduleHour && currentMinute === scheduleMinute;
5749
5863
  if (!timeMatches) {
@@ -5779,7 +5893,9 @@ function useScheduleTaskService() {
5779
5893
  for (const scheduleTask of scheduleTasks) {
5780
5894
  try {
5781
5895
  logger32.info(
5782
- `Checking schedule ${scheduleTask._id} - ${scheduleTask.title}: time=${scheduleTask.time}, startDate=${scheduleTask.startDate}, endDate=${scheduleTask.endDate}`
5896
+ `Checking schedule ${scheduleTask._id} - ${scheduleTask.title}: time=${scheduleTask.time}, dates=${JSON.stringify(
5897
+ scheduleTask.dates
5898
+ )}`
5783
5899
  );
5784
5900
  const shouldRun = checkScheduleConditions(scheduleTask, currentDate);
5785
5901
  if (!shouldRun) {
@@ -6089,8 +6205,9 @@ function useScheduleTaskController() {
6089
6205
  id: Joi17.string().hex().required(),
6090
6206
  title: Joi17.string().optional().allow("", null),
6091
6207
  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),
6208
+ dates: Joi17.array().min(1).items(
6209
+ Joi17.string().pattern(/^\d{4}-\d{2}-\d{2}$/).required()
6210
+ ).optional(),
6094
6211
  description: Joi17.string().optional().allow("", null),
6095
6212
  areas: Joi17.array().min(1).items(
6096
6213
  Joi17.object({
@@ -6204,7 +6321,7 @@ function useQRService() {
6204
6321
  throw error;
6205
6322
  }
6206
6323
  }
6207
- async function generateQRPDF(qrUrl, title) {
6324
+ async function generateQRPDF(qrUrl, title, subtitle) {
6208
6325
  try {
6209
6326
  const qrDataUrl = await generateQRDataUrl(qrUrl);
6210
6327
  const browser = await launch2({
@@ -6218,6 +6335,7 @@ function useQRService() {
6218
6335
  height: 1100
6219
6336
  });
6220
6337
  const escapedTitle = (title || "Cleaning Schedule QR Code").replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
6338
+ const escapedSubtitle = subtitle ? subtitle.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;") : "";
6221
6339
  const html = `
6222
6340
  <!DOCTYPE html>
6223
6341
  <html>
@@ -6245,10 +6363,16 @@ function useQRService() {
6245
6363
  padding: 0;
6246
6364
  }
6247
6365
  h1 {
6248
- font-size: 28px;
6366
+ font-size: 38px;
6249
6367
  color: #333;
6250
- margin-bottom: 20px;
6251
- font-weight: 600;
6368
+ margin-bottom: 8px;
6369
+ font-weight: 700;
6370
+ }
6371
+ .subtitle {
6372
+ font-size: 26px;
6373
+ color: #555;
6374
+ margin-bottom: 12px;
6375
+ font-weight: 500;
6252
6376
  }
6253
6377
  .qr-wrapper {
6254
6378
  display: inline-block;
@@ -6283,6 +6407,7 @@ function useQRService() {
6283
6407
  <body>
6284
6408
  <div class="qr-container">
6285
6409
  <h1>${escapedTitle}</h1>
6410
+ ${escapedSubtitle ? `<p class="subtitle">${escapedSubtitle}</p>` : ""}
6286
6411
  <div class="qr-wrapper">
6287
6412
  <img id="qr-image" src="${qrDataUrl}" alt="QR Code" />
6288
6413
  </div>
@@ -6343,6 +6468,7 @@ function useQRController() {
6343
6468
  url: Joi18.string().uri().required(),
6344
6469
  filename: Joi18.string().optional().allow("", null),
6345
6470
  title: Joi18.string().optional().allow("", null),
6471
+ subtitle: Joi18.string().optional().allow("", null),
6346
6472
  download: Joi18.boolean().optional().default(false)
6347
6473
  });
6348
6474
  const query = { ...req.query };
@@ -6353,9 +6479,9 @@ function useQRController() {
6353
6479
  return;
6354
6480
  }
6355
6481
  try {
6356
- const { url, filename, title, download } = value;
6482
+ const { url, filename, title, subtitle, download } = value;
6357
6483
  if (download) {
6358
- const pdfBuffer = await _generateQRPDF(url, title);
6484
+ const pdfBuffer = await _generateQRPDF(url, title, subtitle);
6359
6485
  if (!pdfBuffer || pdfBuffer.length === 0) {
6360
6486
  throw new Error("Generated QR PDF is empty or invalid.");
6361
6487
  }