@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/CHANGELOG.md +12 -0
- package/dist/index.d.ts +10 -10
- package/dist/index.js +238 -95
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +238 -95
- package/dist/index.mjs.map +1 -1
- package/package.json +2 -2
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 = [
|
|
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(
|
|
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(
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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(
|
|
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(
|
|
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(
|
|
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: "
|
|
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", "
|
|
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 = ["
|
|
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: "
|
|
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: "
|
|
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", "
|
|
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", "
|
|
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", "
|
|
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", "
|
|
2994
|
-
then: "
|
|
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", "
|
|
3146
|
-
then: "
|
|
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"] = "
|
|
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 = "
|
|
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
|
|
3523
|
-
(unit) => unit.status === "
|
|
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 (
|
|
3532
|
-
areaStatus = "
|
|
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
|
|
3552
|
-
(area) => area.status === "
|
|
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 = "
|
|
3695
|
+
let parentStatus = "open";
|
|
3559
3696
|
if (completedAreasCount === totalAreasCount) {
|
|
3560
3697
|
parentStatus = "completed";
|
|
3561
|
-
} else if (
|
|
3562
|
-
parentStatus = "
|
|
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
|
|
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,
|
|
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
|
-
|
|
5357
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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},
|
|
5861
|
+
`Checking schedule ${schedule._id}: Current time ${currentHour}:${currentMinute}, Schedule time ${schedule.time}, Dates ${JSON.stringify(schedule.dates)}`
|
|
5729
5862
|
);
|
|
5730
|
-
const
|
|
5731
|
-
|
|
5732
|
-
|
|
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 ${
|
|
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},
|
|
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
|
-
|
|
6093
|
-
|
|
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, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
6355
|
+
const escapedSubtitle = subtitle ? subtitle.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """) : "";
|
|
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:
|
|
6383
|
+
font-size: 38px;
|
|
6249
6384
|
color: #333;
|
|
6250
|
-
margin-bottom:
|
|
6251
|
-
font-weight:
|
|
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
|
}
|