@7365admin1/module-hygiene 4.7.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/CHANGELOG.md +12 -0
- package/dist/index.d.ts +11 -10
- package/dist/index.js +786 -375
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +720 -309
- 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: "",
|
|
@@ -2751,11 +2814,15 @@ function useAreaChecklistRepo() {
|
|
|
2751
2814
|
status: {
|
|
2752
2815
|
$switch: {
|
|
2753
2816
|
branches: [
|
|
2754
|
-
{ case: { $eq: ["$status", "
|
|
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", "
|
|
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", "
|
|
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", "
|
|
2994
|
-
then: "
|
|
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"
|
|
@@ -3096,6 +3175,38 @@ function useAreaChecklistRepo() {
|
|
|
3096
3175
|
preserveNullAndEmptyArrays: false
|
|
3097
3176
|
}
|
|
3098
3177
|
},
|
|
3178
|
+
{
|
|
3179
|
+
$addFields: {
|
|
3180
|
+
isCompleted: { $ne: ["$checklist.units.completedBy", ""] }
|
|
3181
|
+
}
|
|
3182
|
+
},
|
|
3183
|
+
{
|
|
3184
|
+
$lookup: {
|
|
3185
|
+
from: "users",
|
|
3186
|
+
let: { completedById: "$checklist.units.completedBy" },
|
|
3187
|
+
pipeline: [
|
|
3188
|
+
{
|
|
3189
|
+
$match: {
|
|
3190
|
+
$expr: {
|
|
3191
|
+
$and: [
|
|
3192
|
+
{ $ne: ["$$completedById", ""] },
|
|
3193
|
+
{ $eq: ["$_id", "$$completedById"] }
|
|
3194
|
+
]
|
|
3195
|
+
}
|
|
3196
|
+
}
|
|
3197
|
+
},
|
|
3198
|
+
{ $project: { name: 1 } }
|
|
3199
|
+
],
|
|
3200
|
+
as: "completedBy"
|
|
3201
|
+
}
|
|
3202
|
+
},
|
|
3203
|
+
{
|
|
3204
|
+
$unwind: {
|
|
3205
|
+
path: "$completedBy",
|
|
3206
|
+
preserveNullAndEmptyArrays: true
|
|
3207
|
+
}
|
|
3208
|
+
},
|
|
3209
|
+
{ $sort: { set: 1, isCompleted: -1, "checklist.units.timestamp": 1 } },
|
|
3099
3210
|
{
|
|
3100
3211
|
$project: {
|
|
3101
3212
|
_id: 0,
|
|
@@ -3110,25 +3221,30 @@ function useAreaChecklistRepo() {
|
|
|
3110
3221
|
$switch: {
|
|
3111
3222
|
branches: [
|
|
3112
3223
|
{
|
|
3113
|
-
case: { $eq: ["$checklist.units.status", "
|
|
3114
|
-
then: "
|
|
3224
|
+
case: { $eq: ["$checklist.units.status", "open"] },
|
|
3225
|
+
then: "Open"
|
|
3115
3226
|
},
|
|
3116
3227
|
{
|
|
3117
3228
|
case: { $eq: ["$checklist.units.status", "completed"] },
|
|
3118
3229
|
then: "Completed"
|
|
3230
|
+
},
|
|
3231
|
+
{
|
|
3232
|
+
case: { $eq: ["$checklist.units.status", "closed"] },
|
|
3233
|
+
then: "Closed"
|
|
3119
3234
|
}
|
|
3120
3235
|
],
|
|
3121
3236
|
default: "$checklist.units.status"
|
|
3122
3237
|
}
|
|
3123
|
-
}
|
|
3238
|
+
},
|
|
3239
|
+
completedByName: "$completedBy.name"
|
|
3124
3240
|
}
|
|
3125
3241
|
},
|
|
3126
|
-
{ $sort: { set: 1, name: 1 } },
|
|
3127
3242
|
{
|
|
3128
3243
|
$group: {
|
|
3129
3244
|
_id: "$set",
|
|
3130
3245
|
remarks: { $first: "$remarks" },
|
|
3131
3246
|
attachment: { $first: "$attachment" },
|
|
3247
|
+
completedByName: { $first: "$completedByName" },
|
|
3132
3248
|
units: {
|
|
3133
3249
|
$push: {
|
|
3134
3250
|
unit: "$unit",
|
|
@@ -3136,7 +3252,8 @@ function useAreaChecklistRepo() {
|
|
|
3136
3252
|
approve: "$approve",
|
|
3137
3253
|
reject: "$reject",
|
|
3138
3254
|
status: "$status",
|
|
3139
|
-
remarks: "$remarks"
|
|
3255
|
+
remarks: "$remarks",
|
|
3256
|
+
completedByName: "$completedByName"
|
|
3140
3257
|
}
|
|
3141
3258
|
}
|
|
3142
3259
|
}
|
|
@@ -3147,6 +3264,7 @@ function useAreaChecklistRepo() {
|
|
|
3147
3264
|
set: "$_id",
|
|
3148
3265
|
remarks: "$remarks",
|
|
3149
3266
|
attachment: "$attachment",
|
|
3267
|
+
completedByName: 1,
|
|
3150
3268
|
units: 1
|
|
3151
3269
|
}
|
|
3152
3270
|
},
|
|
@@ -3245,7 +3363,7 @@ function useAreaChecklistRepo() {
|
|
|
3245
3363
|
} else if (value.reject === true) {
|
|
3246
3364
|
updateValue["checklist.$[checklist].units.$[unit].approve"] = false;
|
|
3247
3365
|
updateValue["checklist.$[checklist].units.$[unit].reject"] = true;
|
|
3248
|
-
updateValue["checklist.$[checklist].units.$[unit].status"] = "
|
|
3366
|
+
updateValue["checklist.$[checklist].units.$[unit].status"] = "open";
|
|
3249
3367
|
}
|
|
3250
3368
|
if (value.remarks) {
|
|
3251
3369
|
updateValue["checklist.$[checklist].units.$[unit].remarks"] = value.remarks;
|
|
@@ -3371,6 +3489,42 @@ function useAreaChecklistRepo() {
|
|
|
3371
3489
|
return 0;
|
|
3372
3490
|
}
|
|
3373
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
|
+
}
|
|
3374
3528
|
return {
|
|
3375
3529
|
createIndex,
|
|
3376
3530
|
createTextIndex,
|
|
@@ -3384,7 +3538,8 @@ function useAreaChecklistRepo() {
|
|
|
3384
3538
|
updateAreaChecklist,
|
|
3385
3539
|
updateAreaChecklistStatus,
|
|
3386
3540
|
updateAreaChecklistUnits,
|
|
3387
|
-
getMaxSetNumberForArea
|
|
3541
|
+
getMaxSetNumberForArea,
|
|
3542
|
+
closeExpiredAreaChecklists
|
|
3388
3543
|
};
|
|
3389
3544
|
}
|
|
3390
3545
|
|
|
@@ -3480,12 +3635,12 @@ function useAreaChecklistService() {
|
|
|
3480
3635
|
},
|
|
3481
3636
|
session
|
|
3482
3637
|
);
|
|
3483
|
-
let areaStatus = "
|
|
3638
|
+
let areaStatus = "open";
|
|
3484
3639
|
if (allUnitsResult && allUnitsResult.items && allUnitsResult.items.length > 0) {
|
|
3485
3640
|
const sets = allUnitsResult.items;
|
|
3486
3641
|
const allUnits = sets.flatMap((set2) => set2.units || []);
|
|
3487
|
-
const
|
|
3488
|
-
(unit) => unit.status === "
|
|
3642
|
+
const openCount = allUnits.filter(
|
|
3643
|
+
(unit) => unit.status === "Open"
|
|
3489
3644
|
).length;
|
|
3490
3645
|
const completedCount = allUnits.filter(
|
|
3491
3646
|
(unit) => unit.status === "Completed"
|
|
@@ -3493,8 +3648,8 @@ function useAreaChecklistService() {
|
|
|
3493
3648
|
const totalCount = allUnits.length;
|
|
3494
3649
|
if (completedCount === totalCount) {
|
|
3495
3650
|
areaStatus = "completed";
|
|
3496
|
-
} else if (
|
|
3497
|
-
areaStatus = "
|
|
3651
|
+
} else if (openCount === totalCount) {
|
|
3652
|
+
areaStatus = "open";
|
|
3498
3653
|
} else {
|
|
3499
3654
|
areaStatus = "ongoing";
|
|
3500
3655
|
}
|
|
@@ -3513,18 +3668,18 @@ function useAreaChecklistService() {
|
|
|
3513
3668
|
);
|
|
3514
3669
|
if (allAreasResult && allAreasResult.items && allAreasResult.items.length > 0) {
|
|
3515
3670
|
const areas = allAreasResult.items;
|
|
3516
|
-
const
|
|
3517
|
-
(area) => area.status === "
|
|
3671
|
+
const openAreasCount = areas.filter(
|
|
3672
|
+
(area) => area.status === "Open"
|
|
3518
3673
|
).length;
|
|
3519
3674
|
const completedAreasCount = areas.filter(
|
|
3520
3675
|
(area) => area.status === "Completed"
|
|
3521
3676
|
).length;
|
|
3522
3677
|
const totalAreasCount = areas.length;
|
|
3523
|
-
let parentStatus = "
|
|
3678
|
+
let parentStatus = "open";
|
|
3524
3679
|
if (completedAreasCount === totalAreasCount) {
|
|
3525
3680
|
parentStatus = "completed";
|
|
3526
|
-
} else if (
|
|
3527
|
-
parentStatus = "
|
|
3681
|
+
} else if (openAreasCount === totalAreasCount) {
|
|
3682
|
+
parentStatus = "open";
|
|
3528
3683
|
} else {
|
|
3529
3684
|
parentStatus = "ongoing";
|
|
3530
3685
|
}
|
|
@@ -3535,7 +3690,7 @@ function useAreaChecklistService() {
|
|
|
3535
3690
|
);
|
|
3536
3691
|
} else {
|
|
3537
3692
|
logger18.info(
|
|
3538
|
-
"No area checklists found, keeping parent status as
|
|
3693
|
+
"No area checklists found, keeping parent status as open"
|
|
3539
3694
|
);
|
|
3540
3695
|
}
|
|
3541
3696
|
}
|
|
@@ -3556,7 +3711,223 @@ function useAreaChecklistService() {
|
|
|
3556
3711
|
|
|
3557
3712
|
// src/controllers/hygiene-area-checklist.controller.ts
|
|
3558
3713
|
import Joi9 from "joi";
|
|
3559
|
-
import { BadRequestError as BadRequestError16, logger as
|
|
3714
|
+
import { BadRequestError as BadRequestError16, InternalServerError as InternalServerError7, logger as logger20 } from "@7365admin1/node-server-utils";
|
|
3715
|
+
|
|
3716
|
+
// src/services/hygiene-checklist-pdf.service.ts
|
|
3717
|
+
import { launch } from "puppeteer";
|
|
3718
|
+
import { InternalServerError as InternalServerError6, useAtlas as useAtlas8 } from "@7365admin1/node-server-utils";
|
|
3719
|
+
import { ObjectId as ObjectId10 } from "mongodb";
|
|
3720
|
+
function escapeHtml(text) {
|
|
3721
|
+
return text.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
3722
|
+
}
|
|
3723
|
+
function buildChecklistHtml(areaChecklists) {
|
|
3724
|
+
const areaSections = areaChecklists.map((area) => {
|
|
3725
|
+
const sortedChecklist = [...area.checklist || []].sort(
|
|
3726
|
+
(a, b) => a.set - b.set
|
|
3727
|
+
);
|
|
3728
|
+
const setRows = sortedChecklist.map((setItem) => {
|
|
3729
|
+
const unitRows = (setItem.units || []).map(
|
|
3730
|
+
(unit) => `
|
|
3731
|
+
<tr>
|
|
3732
|
+
<td class="unit-name">${escapeHtml(unit.name || "")}</td>
|
|
3733
|
+
<td class="icon-cell">${unit.reject ? '<span class="reject-icon">✗</span>' : ""}</td>
|
|
3734
|
+
<td class="icon-cell">${unit.approve ? '<span class="approve-icon">✓</span>' : ""}</td>
|
|
3735
|
+
</tr>`
|
|
3736
|
+
).join("");
|
|
3737
|
+
return `
|
|
3738
|
+
<tr class="set-header-row">
|
|
3739
|
+
<td colspan="3">Set ${setItem.set}</td>
|
|
3740
|
+
</tr>
|
|
3741
|
+
${unitRows}`;
|
|
3742
|
+
}).join("");
|
|
3743
|
+
return `
|
|
3744
|
+
<div class="area-section">
|
|
3745
|
+
<div class="area-header">
|
|
3746
|
+
<span class="area-name">${escapeHtml(area.name || "")}</span>
|
|
3747
|
+
<span class="area-type">${escapeHtml(area.type || "")}</span>
|
|
3748
|
+
</div>
|
|
3749
|
+
<table class="checklist-table">
|
|
3750
|
+
<thead>
|
|
3751
|
+
<tr>
|
|
3752
|
+
<th class="col-unit">Unit</th>
|
|
3753
|
+
<th class="col-icon">✗</th>
|
|
3754
|
+
<th class="col-icon">✓</th>
|
|
3755
|
+
</tr>
|
|
3756
|
+
</thead>
|
|
3757
|
+
<tbody>
|
|
3758
|
+
${setRows}
|
|
3759
|
+
</tbody>
|
|
3760
|
+
</table>
|
|
3761
|
+
</div>`;
|
|
3762
|
+
}).join("");
|
|
3763
|
+
return `<!DOCTYPE html>
|
|
3764
|
+
<html lang="en">
|
|
3765
|
+
<head>
|
|
3766
|
+
<meta charset="UTF-8">
|
|
3767
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
3768
|
+
<title>Checklist</title>
|
|
3769
|
+
<style>
|
|
3770
|
+
* {
|
|
3771
|
+
margin: 0;
|
|
3772
|
+
padding: 0;
|
|
3773
|
+
box-sizing: border-box;
|
|
3774
|
+
}
|
|
3775
|
+
body {
|
|
3776
|
+
font-family: Arial, Helvetica, sans-serif;
|
|
3777
|
+
background: #ffffff;
|
|
3778
|
+
padding: 30px 35px;
|
|
3779
|
+
color: #1a1a1a;
|
|
3780
|
+
font-size: 13px;
|
|
3781
|
+
}
|
|
3782
|
+
.page-header {
|
|
3783
|
+
display: flex;
|
|
3784
|
+
align-items: center;
|
|
3785
|
+
justify-content: flex-start;
|
|
3786
|
+
background: #1a3a6b;
|
|
3787
|
+
margin-bottom: 24px;
|
|
3788
|
+
padding: 12px 16px;
|
|
3789
|
+
border-radius: 4px;
|
|
3790
|
+
}
|
|
3791
|
+
.page-logo {
|
|
3792
|
+
height: 40px;
|
|
3793
|
+
width: auto;
|
|
3794
|
+
object-fit: contain;
|
|
3795
|
+
}
|
|
3796
|
+
.area-section {
|
|
3797
|
+
margin-bottom: 22px;
|
|
3798
|
+
border: 1px solid #dce1e7;
|
|
3799
|
+
border-radius: 4px;
|
|
3800
|
+
overflow: hidden;
|
|
3801
|
+
page-break-inside: avoid;
|
|
3802
|
+
}
|
|
3803
|
+
.area-header {
|
|
3804
|
+
display: flex;
|
|
3805
|
+
align-items: center;
|
|
3806
|
+
justify-content: space-between;
|
|
3807
|
+
background: #2c3e50;
|
|
3808
|
+
color: #ffffff;
|
|
3809
|
+
padding: 9px 12px;
|
|
3810
|
+
}
|
|
3811
|
+
.area-name {
|
|
3812
|
+
font-size: 14px;
|
|
3813
|
+
font-weight: 600;
|
|
3814
|
+
}
|
|
3815
|
+
.area-type {
|
|
3816
|
+
font-size: 11px;
|
|
3817
|
+
text-transform: capitalize;
|
|
3818
|
+
background: rgba(255,255,255,0.2);
|
|
3819
|
+
padding: 2px 8px;
|
|
3820
|
+
border-radius: 10px;
|
|
3821
|
+
}
|
|
3822
|
+
.checklist-table {
|
|
3823
|
+
width: 100%;
|
|
3824
|
+
border-collapse: collapse;
|
|
3825
|
+
}
|
|
3826
|
+
.checklist-table thead tr th {
|
|
3827
|
+
background: #34495e;
|
|
3828
|
+
color: #ffffff;
|
|
3829
|
+
padding: 7px 10px;
|
|
3830
|
+
text-align: left;
|
|
3831
|
+
font-size: 12px;
|
|
3832
|
+
font-weight: 600;
|
|
3833
|
+
letter-spacing: 0.3px;
|
|
3834
|
+
}
|
|
3835
|
+
.checklist-table thead tr th.col-icon {
|
|
3836
|
+
text-align: center;
|
|
3837
|
+
width: 52px;
|
|
3838
|
+
}
|
|
3839
|
+
.checklist-table thead tr th.col-unit {
|
|
3840
|
+
width: auto;
|
|
3841
|
+
}
|
|
3842
|
+
.set-header-row td {
|
|
3843
|
+
background: #ecf0f1;
|
|
3844
|
+
color: #555;
|
|
3845
|
+
font-weight: 700;
|
|
3846
|
+
font-size: 12px;
|
|
3847
|
+
padding: 5px 10px;
|
|
3848
|
+
border-bottom: 1px solid #dce1e7;
|
|
3849
|
+
text-transform: uppercase;
|
|
3850
|
+
letter-spacing: 0.4px;
|
|
3851
|
+
}
|
|
3852
|
+
.checklist-table tbody tr td {
|
|
3853
|
+
padding: 7px 10px;
|
|
3854
|
+
border-bottom: 1px solid #f0f2f4;
|
|
3855
|
+
font-size: 13px;
|
|
3856
|
+
}
|
|
3857
|
+
.checklist-table tbody tr:last-child td {
|
|
3858
|
+
border-bottom: none;
|
|
3859
|
+
}
|
|
3860
|
+
.icon-cell {
|
|
3861
|
+
text-align: center;
|
|
3862
|
+
width: 52px;
|
|
3863
|
+
}
|
|
3864
|
+
.approve-icon {
|
|
3865
|
+
color: #27ae60;
|
|
3866
|
+
font-size: 16px;
|
|
3867
|
+
font-weight: bold;
|
|
3868
|
+
}
|
|
3869
|
+
.reject-icon {
|
|
3870
|
+
color: #e74c3c;
|
|
3871
|
+
font-size: 16px;
|
|
3872
|
+
font-weight: bold;
|
|
3873
|
+
}
|
|
3874
|
+
</style>
|
|
3875
|
+
</head>
|
|
3876
|
+
<body>
|
|
3877
|
+
<div class="page-header">
|
|
3878
|
+
<img class="page-logo" src="https://seven365-storage.sgp1.cdn.digitaloceanspaces.com/seven365-public/images/seven365-logo.png" alt="Seven365 Logo" />
|
|
3879
|
+
</div>
|
|
3880
|
+
${areaSections}
|
|
3881
|
+
</body>
|
|
3882
|
+
</html>`;
|
|
3883
|
+
}
|
|
3884
|
+
function useChecklistPdfService() {
|
|
3885
|
+
async function generateChecklistPdf(scheduleId) {
|
|
3886
|
+
const db = useAtlas8.getDb();
|
|
3887
|
+
if (!db) {
|
|
3888
|
+
throw new InternalServerError6("Unable to connect to server.");
|
|
3889
|
+
}
|
|
3890
|
+
const collection = db.collection("site.cleaning.schedule.areas");
|
|
3891
|
+
let scheduleObjectId;
|
|
3892
|
+
try {
|
|
3893
|
+
scheduleObjectId = new ObjectId10(scheduleId);
|
|
3894
|
+
} catch {
|
|
3895
|
+
throw new InternalServerError6("Invalid schedule ID format.");
|
|
3896
|
+
}
|
|
3897
|
+
const areaChecklists = await collection.find({ schedule: scheduleObjectId }).sort({ createdAt: 1, name: 1 }).toArray();
|
|
3898
|
+
const html = buildChecklistHtml(areaChecklists);
|
|
3899
|
+
const browser = await launch({
|
|
3900
|
+
headless: true,
|
|
3901
|
+
executablePath: process.env.CHROME_BINARY,
|
|
3902
|
+
args: ["--no-sandbox", "--disable-gpu", "--disable-dev-shm-usage"]
|
|
3903
|
+
});
|
|
3904
|
+
try {
|
|
3905
|
+
const page = await browser.newPage();
|
|
3906
|
+
await page.setViewport({ width: 794, height: 1123 });
|
|
3907
|
+
await page.setContent(html, {
|
|
3908
|
+
waitUntil: ["load", "networkidle0"]
|
|
3909
|
+
});
|
|
3910
|
+
const pdfBuffer = await page.pdf({
|
|
3911
|
+
format: "A4",
|
|
3912
|
+
printBackground: true,
|
|
3913
|
+
margin: {
|
|
3914
|
+
top: "15mm",
|
|
3915
|
+
right: "15mm",
|
|
3916
|
+
bottom: "15mm",
|
|
3917
|
+
left: "15mm"
|
|
3918
|
+
}
|
|
3919
|
+
});
|
|
3920
|
+
return pdfBuffer;
|
|
3921
|
+
} finally {
|
|
3922
|
+
await browser.close();
|
|
3923
|
+
}
|
|
3924
|
+
}
|
|
3925
|
+
return {
|
|
3926
|
+
generateChecklistPdf
|
|
3927
|
+
};
|
|
3928
|
+
}
|
|
3929
|
+
|
|
3930
|
+
// src/controllers/hygiene-area-checklist.controller.ts
|
|
3560
3931
|
function useAreaChecklistController() {
|
|
3561
3932
|
const {
|
|
3562
3933
|
getAllAreaChecklist: _getAllAreaChecklist,
|
|
@@ -3568,6 +3939,7 @@ function useAreaChecklistController() {
|
|
|
3568
3939
|
createAreaChecklist: _createAreaChecklist,
|
|
3569
3940
|
updateAreaChecklistUnits: _updateAreaChecklistUnits
|
|
3570
3941
|
} = useAreaChecklistService();
|
|
3942
|
+
const { generateChecklistPdf: _generateChecklistPdf } = useChecklistPdfService();
|
|
3571
3943
|
async function createAreaChecklist(req, res, next) {
|
|
3572
3944
|
const cookies = req.headers.cookie ? req.headers.cookie.split(";").map((cookie) => cookie.trim().split("=")).reduce(
|
|
3573
3945
|
(acc, [key, value2]) => ({ ...acc, [key]: value2 }),
|
|
@@ -3586,7 +3958,7 @@ function useAreaChecklistController() {
|
|
|
3586
3958
|
});
|
|
3587
3959
|
const { error, value } = validation.validate(payload);
|
|
3588
3960
|
if (error) {
|
|
3589
|
-
|
|
3961
|
+
logger20.log({ level: "error", message: error.message });
|
|
3590
3962
|
next(new BadRequestError16(error.message));
|
|
3591
3963
|
return;
|
|
3592
3964
|
}
|
|
@@ -3595,7 +3967,7 @@ function useAreaChecklistController() {
|
|
|
3595
3967
|
res.status(201).json({ message: "Area checklist generated successfully." });
|
|
3596
3968
|
return;
|
|
3597
3969
|
} catch (error2) {
|
|
3598
|
-
|
|
3970
|
+
logger20.log({ level: "error", message: error2.message });
|
|
3599
3971
|
next(error2);
|
|
3600
3972
|
return;
|
|
3601
3973
|
}
|
|
@@ -3612,7 +3984,7 @@ function useAreaChecklistController() {
|
|
|
3612
3984
|
});
|
|
3613
3985
|
const { error } = validation.validate(query);
|
|
3614
3986
|
if (error) {
|
|
3615
|
-
|
|
3987
|
+
logger20.log({ level: "error", message: error.message });
|
|
3616
3988
|
next(new BadRequestError16(error.message));
|
|
3617
3989
|
return;
|
|
3618
3990
|
}
|
|
@@ -3634,7 +4006,7 @@ function useAreaChecklistController() {
|
|
|
3634
4006
|
res.json(data);
|
|
3635
4007
|
return;
|
|
3636
4008
|
} catch (error2) {
|
|
3637
|
-
|
|
4009
|
+
logger20.log({ level: "error", message: error2.message });
|
|
3638
4010
|
next(error2);
|
|
3639
4011
|
return;
|
|
3640
4012
|
}
|
|
@@ -3652,7 +4024,7 @@ function useAreaChecklistController() {
|
|
|
3652
4024
|
});
|
|
3653
4025
|
const { error } = validation.validate(query);
|
|
3654
4026
|
if (error) {
|
|
3655
|
-
|
|
4027
|
+
logger20.log({ level: "error", message: error.message });
|
|
3656
4028
|
next(new BadRequestError16(error.message));
|
|
3657
4029
|
return;
|
|
3658
4030
|
}
|
|
@@ -3676,7 +4048,7 @@ function useAreaChecklistController() {
|
|
|
3676
4048
|
res.json(data);
|
|
3677
4049
|
return;
|
|
3678
4050
|
} catch (error2) {
|
|
3679
|
-
|
|
4051
|
+
logger20.log({ level: "error", message: error2.message });
|
|
3680
4052
|
next(error2);
|
|
3681
4053
|
return;
|
|
3682
4054
|
}
|
|
@@ -3686,7 +4058,7 @@ function useAreaChecklistController() {
|
|
|
3686
4058
|
const _id = req.params.id;
|
|
3687
4059
|
const { error, value } = validation.validate(_id);
|
|
3688
4060
|
if (error) {
|
|
3689
|
-
|
|
4061
|
+
logger20.log({ level: "error", message: error.message });
|
|
3690
4062
|
next(new BadRequestError16(error.message));
|
|
3691
4063
|
return;
|
|
3692
4064
|
}
|
|
@@ -3695,7 +4067,7 @@ function useAreaChecklistController() {
|
|
|
3695
4067
|
res.json(data);
|
|
3696
4068
|
return;
|
|
3697
4069
|
} catch (error2) {
|
|
3698
|
-
|
|
4070
|
+
logger20.log({ level: "error", message: error2.message });
|
|
3699
4071
|
next(error2);
|
|
3700
4072
|
return;
|
|
3701
4073
|
}
|
|
@@ -3710,7 +4082,7 @@ function useAreaChecklistController() {
|
|
|
3710
4082
|
});
|
|
3711
4083
|
const { error } = validation.validate(query);
|
|
3712
4084
|
if (error) {
|
|
3713
|
-
|
|
4085
|
+
logger20.log({ level: "error", message: error.message });
|
|
3714
4086
|
next(new BadRequestError16(error.message));
|
|
3715
4087
|
return;
|
|
3716
4088
|
}
|
|
@@ -3728,7 +4100,7 @@ function useAreaChecklistController() {
|
|
|
3728
4100
|
res.json(data);
|
|
3729
4101
|
return;
|
|
3730
4102
|
} catch (error2) {
|
|
3731
|
-
|
|
4103
|
+
logger20.log({ level: "error", message: error2.message });
|
|
3732
4104
|
next(error2);
|
|
3733
4105
|
return;
|
|
3734
4106
|
}
|
|
@@ -3767,7 +4139,7 @@ function useAreaChecklistController() {
|
|
|
3767
4139
|
});
|
|
3768
4140
|
const { error } = validation.validate(payload);
|
|
3769
4141
|
if (error) {
|
|
3770
|
-
|
|
4142
|
+
logger20.log({ level: "error", message: error.message });
|
|
3771
4143
|
next(new BadRequestError16(error.message));
|
|
3772
4144
|
return;
|
|
3773
4145
|
}
|
|
@@ -3777,7 +4149,39 @@ function useAreaChecklistController() {
|
|
|
3777
4149
|
res.json({ message: "Area checklist updated successfully." });
|
|
3778
4150
|
return;
|
|
3779
4151
|
} catch (error2) {
|
|
3780
|
-
|
|
4152
|
+
logger20.log({ level: "error", message: error2.message });
|
|
4153
|
+
next(error2);
|
|
4154
|
+
return;
|
|
4155
|
+
}
|
|
4156
|
+
}
|
|
4157
|
+
async function downloadChecklistPdf(req, res, next) {
|
|
4158
|
+
const validation = Joi9.string().hex().required();
|
|
4159
|
+
const schedule = req.params.schedule;
|
|
4160
|
+
const { error, value } = validation.validate(schedule);
|
|
4161
|
+
if (error) {
|
|
4162
|
+
logger20.log({ level: "error", message: error.message });
|
|
4163
|
+
next(new BadRequestError16(error.message));
|
|
4164
|
+
return;
|
|
4165
|
+
}
|
|
4166
|
+
try {
|
|
4167
|
+
const pdfBuffer = await _generateChecklistPdf(value);
|
|
4168
|
+
if (!pdfBuffer || pdfBuffer.length === 0) {
|
|
4169
|
+
throw new InternalServerError7("Generated checklist PDF is empty or invalid.");
|
|
4170
|
+
}
|
|
4171
|
+
const date = /* @__PURE__ */ new Date();
|
|
4172
|
+
const formattedDate = `${String(date.getDate()).padStart(2, "0")}_${String(
|
|
4173
|
+
date.getMonth() + 1
|
|
4174
|
+
).padStart(2, "0")}_${date.getFullYear()}`;
|
|
4175
|
+
res.setHeader("Content-Type", "application/pdf");
|
|
4176
|
+
res.setHeader(
|
|
4177
|
+
"Content-Disposition",
|
|
4178
|
+
`attachment; filename="CHECKLIST_${formattedDate}.pdf"`
|
|
4179
|
+
);
|
|
4180
|
+
res.setHeader("Content-Length", pdfBuffer.length);
|
|
4181
|
+
res.end(pdfBuffer);
|
|
4182
|
+
return;
|
|
4183
|
+
} catch (error2) {
|
|
4184
|
+
logger20.log({ level: "error", message: error2.message });
|
|
3781
4185
|
next(error2);
|
|
3782
4186
|
return;
|
|
3783
4187
|
}
|
|
@@ -3788,14 +4192,15 @@ function useAreaChecklistController() {
|
|
|
3788
4192
|
getAreaChecklistHistory,
|
|
3789
4193
|
getAreaChecklistHistoryDetails,
|
|
3790
4194
|
getAreaChecklistUnits,
|
|
3791
|
-
updateAreaChecklistUnits
|
|
4195
|
+
updateAreaChecklistUnits,
|
|
4196
|
+
downloadChecklistPdf
|
|
3792
4197
|
};
|
|
3793
4198
|
}
|
|
3794
4199
|
|
|
3795
4200
|
// src/models/hygiene-supply.model.ts
|
|
3796
4201
|
import Joi10 from "joi";
|
|
3797
|
-
import { ObjectId as
|
|
3798
|
-
import { BadRequestError as BadRequestError17, logger as
|
|
4202
|
+
import { ObjectId as ObjectId11 } from "mongodb";
|
|
4203
|
+
import { BadRequestError as BadRequestError17, logger as logger21 } from "@7365admin1/node-server-utils";
|
|
3799
4204
|
var supplySchema = Joi10.object({
|
|
3800
4205
|
site: Joi10.string().hex().required(),
|
|
3801
4206
|
name: Joi10.string().required(),
|
|
@@ -3804,12 +4209,12 @@ var supplySchema = Joi10.object({
|
|
|
3804
4209
|
function MSupply(value) {
|
|
3805
4210
|
const { error } = supplySchema.validate(value);
|
|
3806
4211
|
if (error) {
|
|
3807
|
-
|
|
4212
|
+
logger21.info(`Hygiene Supply Model: ${error.message}`);
|
|
3808
4213
|
throw new BadRequestError17(error.message);
|
|
3809
4214
|
}
|
|
3810
4215
|
if (value.site) {
|
|
3811
4216
|
try {
|
|
3812
|
-
value.site = new
|
|
4217
|
+
value.site = new ObjectId11(value.site);
|
|
3813
4218
|
} catch (error2) {
|
|
3814
4219
|
throw new BadRequestError17("Invalid site ID format.");
|
|
3815
4220
|
}
|
|
@@ -3827,21 +4232,21 @@ function MSupply(value) {
|
|
|
3827
4232
|
}
|
|
3828
4233
|
|
|
3829
4234
|
// src/repositories/hygiene-supply.repository.ts
|
|
3830
|
-
import { ObjectId as
|
|
4235
|
+
import { ObjectId as ObjectId12 } from "mongodb";
|
|
3831
4236
|
import {
|
|
3832
|
-
useAtlas as
|
|
3833
|
-
InternalServerError as
|
|
4237
|
+
useAtlas as useAtlas9,
|
|
4238
|
+
InternalServerError as InternalServerError8,
|
|
3834
4239
|
paginate as paginate5,
|
|
3835
4240
|
BadRequestError as BadRequestError18,
|
|
3836
4241
|
useCache as useCache6,
|
|
3837
|
-
logger as
|
|
4242
|
+
logger as logger22,
|
|
3838
4243
|
makeCacheKey as makeCacheKey6,
|
|
3839
4244
|
NotFoundError as NotFoundError4
|
|
3840
4245
|
} from "@7365admin1/node-server-utils";
|
|
3841
4246
|
function useSupplyRepository() {
|
|
3842
|
-
const db =
|
|
4247
|
+
const db = useAtlas9.getDb();
|
|
3843
4248
|
if (!db) {
|
|
3844
|
-
throw new
|
|
4249
|
+
throw new InternalServerError8("Unable to connect to server.");
|
|
3845
4250
|
}
|
|
3846
4251
|
const namespace_collection = "site.supplies";
|
|
3847
4252
|
const collection = db.collection(namespace_collection);
|
|
@@ -3850,7 +4255,7 @@ function useSupplyRepository() {
|
|
|
3850
4255
|
try {
|
|
3851
4256
|
await collection.createIndexes([{ key: { site: 1 } }]);
|
|
3852
4257
|
} catch (error) {
|
|
3853
|
-
throw new
|
|
4258
|
+
throw new InternalServerError8(
|
|
3854
4259
|
"Failed to create index on hygiene supply."
|
|
3855
4260
|
);
|
|
3856
4261
|
}
|
|
@@ -3859,7 +4264,7 @@ function useSupplyRepository() {
|
|
|
3859
4264
|
try {
|
|
3860
4265
|
await collection.createIndex({ name: "text" });
|
|
3861
4266
|
} catch (error) {
|
|
3862
|
-
throw new
|
|
4267
|
+
throw new InternalServerError8(
|
|
3863
4268
|
"Failed to create text index on hygiene supply."
|
|
3864
4269
|
);
|
|
3865
4270
|
}
|
|
@@ -3871,7 +4276,7 @@ function useSupplyRepository() {
|
|
|
3871
4276
|
{ unique: true }
|
|
3872
4277
|
);
|
|
3873
4278
|
} catch (error) {
|
|
3874
|
-
throw new
|
|
4279
|
+
throw new InternalServerError8(
|
|
3875
4280
|
"Failed to create unique index on hygiene supply."
|
|
3876
4281
|
);
|
|
3877
4282
|
}
|
|
@@ -3881,9 +4286,9 @@ function useSupplyRepository() {
|
|
|
3881
4286
|
value = MSupply(value);
|
|
3882
4287
|
const res = await collection.insertOne(value, { session });
|
|
3883
4288
|
delNamespace().then(() => {
|
|
3884
|
-
|
|
4289
|
+
logger22.info(`Cache cleared for namespace: ${namespace_collection}`);
|
|
3885
4290
|
}).catch((err) => {
|
|
3886
|
-
|
|
4291
|
+
logger22.error(
|
|
3887
4292
|
`Failed to clear cache for namespace: ${namespace_collection}`,
|
|
3888
4293
|
err
|
|
3889
4294
|
);
|
|
@@ -3912,7 +4317,7 @@ function useSupplyRepository() {
|
|
|
3912
4317
|
limit
|
|
3913
4318
|
};
|
|
3914
4319
|
try {
|
|
3915
|
-
site = new
|
|
4320
|
+
site = new ObjectId12(site);
|
|
3916
4321
|
query.site = site;
|
|
3917
4322
|
cacheOptions.site = site.toString();
|
|
3918
4323
|
} catch (error) {
|
|
@@ -3925,7 +4330,7 @@ function useSupplyRepository() {
|
|
|
3925
4330
|
const cacheKey = makeCacheKey6(namespace_collection, cacheOptions);
|
|
3926
4331
|
const cachedData = await getCache(cacheKey);
|
|
3927
4332
|
if (cachedData) {
|
|
3928
|
-
|
|
4333
|
+
logger22.info(`Cache hit for key: ${cacheKey}`);
|
|
3929
4334
|
return cachedData;
|
|
3930
4335
|
}
|
|
3931
4336
|
try {
|
|
@@ -3945,9 +4350,9 @@ function useSupplyRepository() {
|
|
|
3945
4350
|
const length = await collection.countDocuments(query);
|
|
3946
4351
|
const data = paginate5(items, page, limit, length);
|
|
3947
4352
|
setCache(cacheKey, data, 15 * 60).then(() => {
|
|
3948
|
-
|
|
4353
|
+
logger22.info(`Cache set for key: ${cacheKey}`);
|
|
3949
4354
|
}).catch((err) => {
|
|
3950
|
-
|
|
4355
|
+
logger22.error(`Failed to set cache for key: ${cacheKey}`, err);
|
|
3951
4356
|
});
|
|
3952
4357
|
return data;
|
|
3953
4358
|
} catch (error) {
|
|
@@ -3956,7 +4361,7 @@ function useSupplyRepository() {
|
|
|
3956
4361
|
}
|
|
3957
4362
|
async function getSupplyById(_id, session) {
|
|
3958
4363
|
try {
|
|
3959
|
-
_id = new
|
|
4364
|
+
_id = new ObjectId12(_id);
|
|
3960
4365
|
} catch (error) {
|
|
3961
4366
|
throw new BadRequestError18("Invalid supply ID format.");
|
|
3962
4367
|
}
|
|
@@ -3970,11 +4375,11 @@ function useSupplyRepository() {
|
|
|
3970
4375
|
if (!session) {
|
|
3971
4376
|
const cachedData = await getCache(cacheKey);
|
|
3972
4377
|
if (cachedData) {
|
|
3973
|
-
|
|
4378
|
+
logger22.info(`Cache hit for key: ${cacheKey}`);
|
|
3974
4379
|
return cachedData;
|
|
3975
4380
|
}
|
|
3976
4381
|
} else {
|
|
3977
|
-
|
|
4382
|
+
logger22.info(`Skipping cache during transaction for key: ${cacheKey}`);
|
|
3978
4383
|
}
|
|
3979
4384
|
try {
|
|
3980
4385
|
const data = await collection.aggregate([
|
|
@@ -3991,9 +4396,9 @@ function useSupplyRepository() {
|
|
|
3991
4396
|
throw new NotFoundError4("Supply not found.");
|
|
3992
4397
|
}
|
|
3993
4398
|
setCache(cacheKey, data[0], 15 * 60).then(() => {
|
|
3994
|
-
|
|
4399
|
+
logger22.info(`Cache set for key: ${cacheKey}`);
|
|
3995
4400
|
}).catch((err) => {
|
|
3996
|
-
|
|
4401
|
+
logger22.error(`Failed to set cache for key: ${cacheKey}`, err);
|
|
3997
4402
|
});
|
|
3998
4403
|
return data[0];
|
|
3999
4404
|
} catch (error) {
|
|
@@ -4002,7 +4407,7 @@ function useSupplyRepository() {
|
|
|
4002
4407
|
}
|
|
4003
4408
|
async function updateSupply(_id, value, session) {
|
|
4004
4409
|
try {
|
|
4005
|
-
_id = new
|
|
4410
|
+
_id = new ObjectId12(_id);
|
|
4006
4411
|
} catch (error) {
|
|
4007
4412
|
throw new BadRequestError18("Invalid supply ID format.");
|
|
4008
4413
|
}
|
|
@@ -4014,12 +4419,12 @@ function useSupplyRepository() {
|
|
|
4014
4419
|
{ session }
|
|
4015
4420
|
);
|
|
4016
4421
|
if (res.modifiedCount === 0) {
|
|
4017
|
-
throw new
|
|
4422
|
+
throw new InternalServerError8("Unable to update cleaning supply.");
|
|
4018
4423
|
}
|
|
4019
4424
|
delNamespace().then(() => {
|
|
4020
|
-
|
|
4425
|
+
logger22.info(`Cache cleared for namespace: ${namespace_collection}`);
|
|
4021
4426
|
}).catch((err) => {
|
|
4022
|
-
|
|
4427
|
+
logger22.error(
|
|
4023
4428
|
`Failed to clear cache for namespace: ${namespace_collection}`,
|
|
4024
4429
|
err
|
|
4025
4430
|
);
|
|
@@ -4035,7 +4440,7 @@ function useSupplyRepository() {
|
|
|
4035
4440
|
}
|
|
4036
4441
|
async function deleteSupply(_id, session) {
|
|
4037
4442
|
try {
|
|
4038
|
-
_id = new
|
|
4443
|
+
_id = new ObjectId12(_id);
|
|
4039
4444
|
} catch (error) {
|
|
4040
4445
|
throw new BadRequestError18("Invalid supply ID format.");
|
|
4041
4446
|
}
|
|
@@ -4051,12 +4456,12 @@ function useSupplyRepository() {
|
|
|
4051
4456
|
{ session }
|
|
4052
4457
|
);
|
|
4053
4458
|
if (res.modifiedCount === 0) {
|
|
4054
|
-
throw new
|
|
4459
|
+
throw new InternalServerError8("Unable to delete supply.");
|
|
4055
4460
|
}
|
|
4056
4461
|
delNamespace().then(() => {
|
|
4057
|
-
|
|
4462
|
+
logger22.info(`Cache cleared for namespace: ${namespace_collection}`);
|
|
4058
4463
|
}).catch((err) => {
|
|
4059
|
-
|
|
4464
|
+
logger22.error(
|
|
4060
4465
|
`Failed to clear cache for namespace: ${namespace_collection}`,
|
|
4061
4466
|
err
|
|
4062
4467
|
);
|
|
@@ -4080,7 +4485,7 @@ function useSupplyRepository() {
|
|
|
4080
4485
|
|
|
4081
4486
|
// src/controllers/hygiene-supply.controller.ts
|
|
4082
4487
|
import Joi11 from "joi";
|
|
4083
|
-
import { BadRequestError as BadRequestError19, logger as
|
|
4488
|
+
import { BadRequestError as BadRequestError19, logger as logger23 } from "@7365admin1/node-server-utils";
|
|
4084
4489
|
function useSupplyController() {
|
|
4085
4490
|
const {
|
|
4086
4491
|
createSupply: _createSupply,
|
|
@@ -4093,7 +4498,7 @@ function useSupplyController() {
|
|
|
4093
4498
|
const payload = { ...req.body, ...req.params };
|
|
4094
4499
|
const { error } = supplySchema.validate(payload);
|
|
4095
4500
|
if (error) {
|
|
4096
|
-
|
|
4501
|
+
logger23.log({ level: "error", message: error.message });
|
|
4097
4502
|
next(new BadRequestError19(error.message));
|
|
4098
4503
|
return;
|
|
4099
4504
|
}
|
|
@@ -4102,7 +4507,7 @@ function useSupplyController() {
|
|
|
4102
4507
|
res.status(201).json({ message: "Supply created successfully.", id });
|
|
4103
4508
|
return;
|
|
4104
4509
|
} catch (error2) {
|
|
4105
|
-
|
|
4510
|
+
logger23.log({ level: "error", message: error2.message });
|
|
4106
4511
|
next(error2);
|
|
4107
4512
|
return;
|
|
4108
4513
|
}
|
|
@@ -4117,7 +4522,7 @@ function useSupplyController() {
|
|
|
4117
4522
|
});
|
|
4118
4523
|
const { error } = validation.validate(query);
|
|
4119
4524
|
if (error) {
|
|
4120
|
-
|
|
4525
|
+
logger23.log({ level: "error", message: error.message });
|
|
4121
4526
|
next(new BadRequestError19(error.message));
|
|
4122
4527
|
return;
|
|
4123
4528
|
}
|
|
@@ -4135,7 +4540,7 @@ function useSupplyController() {
|
|
|
4135
4540
|
res.json(data);
|
|
4136
4541
|
return;
|
|
4137
4542
|
} catch (error2) {
|
|
4138
|
-
|
|
4543
|
+
logger23.log({ level: "error", message: error2.message });
|
|
4139
4544
|
next(error2);
|
|
4140
4545
|
return;
|
|
4141
4546
|
}
|
|
@@ -4145,7 +4550,7 @@ function useSupplyController() {
|
|
|
4145
4550
|
const _id = req.params.id;
|
|
4146
4551
|
const { error, value } = validation.validate(_id);
|
|
4147
4552
|
if (error) {
|
|
4148
|
-
|
|
4553
|
+
logger23.log({ level: "error", message: error.message });
|
|
4149
4554
|
next(new BadRequestError19(error.message));
|
|
4150
4555
|
return;
|
|
4151
4556
|
}
|
|
@@ -4154,7 +4559,7 @@ function useSupplyController() {
|
|
|
4154
4559
|
res.json(data);
|
|
4155
4560
|
return;
|
|
4156
4561
|
} catch (error2) {
|
|
4157
|
-
|
|
4562
|
+
logger23.log({ level: "error", message: error2.message });
|
|
4158
4563
|
next(error2);
|
|
4159
4564
|
return;
|
|
4160
4565
|
}
|
|
@@ -4169,7 +4574,7 @@ function useSupplyController() {
|
|
|
4169
4574
|
});
|
|
4170
4575
|
const { error } = validation.validate(payload);
|
|
4171
4576
|
if (error) {
|
|
4172
|
-
|
|
4577
|
+
logger23.log({ level: "error", message: error.message });
|
|
4173
4578
|
next(new BadRequestError19(error.message));
|
|
4174
4579
|
return;
|
|
4175
4580
|
}
|
|
@@ -4179,7 +4584,7 @@ function useSupplyController() {
|
|
|
4179
4584
|
res.json({ message: "Supply updated successfully." });
|
|
4180
4585
|
return;
|
|
4181
4586
|
} catch (error2) {
|
|
4182
|
-
|
|
4587
|
+
logger23.log({ level: "error", message: error2.message });
|
|
4183
4588
|
next(error2);
|
|
4184
4589
|
return;
|
|
4185
4590
|
}
|
|
@@ -4191,7 +4596,7 @@ function useSupplyController() {
|
|
|
4191
4596
|
});
|
|
4192
4597
|
const { error, value } = validation.validate({ id });
|
|
4193
4598
|
if (error) {
|
|
4194
|
-
|
|
4599
|
+
logger23.log({ level: "error", message: error.message });
|
|
4195
4600
|
next(new BadRequestError19(error.message));
|
|
4196
4601
|
return;
|
|
4197
4602
|
}
|
|
@@ -4200,7 +4605,7 @@ function useSupplyController() {
|
|
|
4200
4605
|
res.json({ message: "Supply deleted successfully." });
|
|
4201
4606
|
return;
|
|
4202
4607
|
} catch (error2) {
|
|
4203
|
-
|
|
4608
|
+
logger23.log({ level: "error", message: error2.message });
|
|
4204
4609
|
next(error2);
|
|
4205
4610
|
return;
|
|
4206
4611
|
}
|
|
@@ -4216,8 +4621,8 @@ function useSupplyController() {
|
|
|
4216
4621
|
|
|
4217
4622
|
// src/models/hygiene-stock.model.ts
|
|
4218
4623
|
import Joi12 from "joi";
|
|
4219
|
-
import { ObjectId as
|
|
4220
|
-
import { BadRequestError as BadRequestError20, logger as
|
|
4624
|
+
import { ObjectId as ObjectId13 } from "mongodb";
|
|
4625
|
+
import { BadRequestError as BadRequestError20, logger as logger24 } from "@7365admin1/node-server-utils";
|
|
4221
4626
|
var stockSchema = Joi12.object({
|
|
4222
4627
|
site: Joi12.string().hex().required(),
|
|
4223
4628
|
supply: Joi12.string().hex().required(),
|
|
@@ -4229,19 +4634,19 @@ var stockSchema = Joi12.object({
|
|
|
4229
4634
|
function MStock(value) {
|
|
4230
4635
|
const { error } = stockSchema.validate(value);
|
|
4231
4636
|
if (error) {
|
|
4232
|
-
|
|
4637
|
+
logger24.info(`Hygiene Stock Model: ${error.message}`);
|
|
4233
4638
|
throw new BadRequestError20(error.message);
|
|
4234
4639
|
}
|
|
4235
4640
|
if (value.site) {
|
|
4236
4641
|
try {
|
|
4237
|
-
value.site = new
|
|
4642
|
+
value.site = new ObjectId13(value.site);
|
|
4238
4643
|
} catch (error2) {
|
|
4239
4644
|
throw new BadRequestError20("Invalid site ID format.");
|
|
4240
4645
|
}
|
|
4241
4646
|
}
|
|
4242
4647
|
if (value.supply) {
|
|
4243
4648
|
try {
|
|
4244
|
-
value.supply = new
|
|
4649
|
+
value.supply = new ObjectId13(value.supply);
|
|
4245
4650
|
} catch (error2) {
|
|
4246
4651
|
throw new BadRequestError20("Invalid supply ID format.");
|
|
4247
4652
|
}
|
|
@@ -4261,20 +4666,20 @@ function MStock(value) {
|
|
|
4261
4666
|
}
|
|
4262
4667
|
|
|
4263
4668
|
// src/repositories/hygiene-stock.repository.ts
|
|
4264
|
-
import { ObjectId as
|
|
4669
|
+
import { ObjectId as ObjectId14 } from "mongodb";
|
|
4265
4670
|
import {
|
|
4266
|
-
useAtlas as
|
|
4267
|
-
InternalServerError as
|
|
4671
|
+
useAtlas as useAtlas10,
|
|
4672
|
+
InternalServerError as InternalServerError9,
|
|
4268
4673
|
BadRequestError as BadRequestError21,
|
|
4269
4674
|
useCache as useCache7,
|
|
4270
|
-
logger as
|
|
4675
|
+
logger as logger25,
|
|
4271
4676
|
makeCacheKey as makeCacheKey7,
|
|
4272
4677
|
paginate as paginate6
|
|
4273
4678
|
} from "@7365admin1/node-server-utils";
|
|
4274
4679
|
function useStockRepository() {
|
|
4275
|
-
const db =
|
|
4680
|
+
const db = useAtlas10.getDb();
|
|
4276
4681
|
if (!db) {
|
|
4277
|
-
throw new
|
|
4682
|
+
throw new InternalServerError9("Unable to connect to server.");
|
|
4278
4683
|
}
|
|
4279
4684
|
const namespace_collection = "site.supply.stocks";
|
|
4280
4685
|
const supply_collection = "site.supplies";
|
|
@@ -4290,7 +4695,7 @@ function useStockRepository() {
|
|
|
4290
4695
|
{ key: { status: 1 } }
|
|
4291
4696
|
]);
|
|
4292
4697
|
} catch (error) {
|
|
4293
|
-
throw new
|
|
4698
|
+
throw new InternalServerError9("Failed to create index on hygiene stock.");
|
|
4294
4699
|
}
|
|
4295
4700
|
}
|
|
4296
4701
|
async function createStock(value, session) {
|
|
@@ -4298,17 +4703,17 @@ function useStockRepository() {
|
|
|
4298
4703
|
value = MStock(value);
|
|
4299
4704
|
const res = await collection.insertOne(value, { session });
|
|
4300
4705
|
delNamespace().then(() => {
|
|
4301
|
-
|
|
4706
|
+
logger25.info(`Cache cleared for namespace: ${namespace_collection}`);
|
|
4302
4707
|
}).catch((err) => {
|
|
4303
|
-
|
|
4708
|
+
logger25.error(
|
|
4304
4709
|
`Failed to clear cache for namespace: ${namespace_collection}`,
|
|
4305
4710
|
err
|
|
4306
4711
|
);
|
|
4307
4712
|
});
|
|
4308
4713
|
delSupplyNamespace().then(() => {
|
|
4309
|
-
|
|
4714
|
+
logger25.info(`Cache cleared for namespace: ${supply_collection}`);
|
|
4310
4715
|
}).catch((err) => {
|
|
4311
|
-
|
|
4716
|
+
logger25.error(
|
|
4312
4717
|
`Failed to clear cache for namespace: ${supply_collection}`,
|
|
4313
4718
|
err
|
|
4314
4719
|
);
|
|
@@ -4334,14 +4739,14 @@ function useStockRepository() {
|
|
|
4334
4739
|
limit
|
|
4335
4740
|
};
|
|
4336
4741
|
try {
|
|
4337
|
-
site = new
|
|
4742
|
+
site = new ObjectId14(site);
|
|
4338
4743
|
query.site = site;
|
|
4339
4744
|
cacheOptions.site = site.toString();
|
|
4340
4745
|
} catch (error) {
|
|
4341
4746
|
throw new BadRequestError21("Invalid site ID format.");
|
|
4342
4747
|
}
|
|
4343
4748
|
try {
|
|
4344
|
-
supply = new
|
|
4749
|
+
supply = new ObjectId14(supply);
|
|
4345
4750
|
query.supply = supply;
|
|
4346
4751
|
cacheOptions.supply = supply.toString();
|
|
4347
4752
|
} catch (error) {
|
|
@@ -4354,7 +4759,7 @@ function useStockRepository() {
|
|
|
4354
4759
|
const cacheKey = makeCacheKey7(namespace_collection, cacheOptions);
|
|
4355
4760
|
const cachedData = await getCache(cacheKey);
|
|
4356
4761
|
if (cachedData) {
|
|
4357
|
-
|
|
4762
|
+
logger25.info(`Cache hit for key: ${cacheKey}`);
|
|
4358
4763
|
return cachedData;
|
|
4359
4764
|
}
|
|
4360
4765
|
try {
|
|
@@ -4375,9 +4780,9 @@ function useStockRepository() {
|
|
|
4375
4780
|
const length = await collection.countDocuments(query);
|
|
4376
4781
|
const data = paginate6(items, page, limit, length);
|
|
4377
4782
|
setCache(cacheKey, data, 15 * 60).then(() => {
|
|
4378
|
-
|
|
4783
|
+
logger25.info(`Cache set for key: ${cacheKey}`);
|
|
4379
4784
|
}).catch((err) => {
|
|
4380
|
-
|
|
4785
|
+
logger25.error(`Failed to set cache for key: ${cacheKey}`, err);
|
|
4381
4786
|
});
|
|
4382
4787
|
return data;
|
|
4383
4788
|
} catch (error) {
|
|
@@ -4394,7 +4799,7 @@ function useStockRepository() {
|
|
|
4394
4799
|
// src/services/hygiene-stock.service.ts
|
|
4395
4800
|
import {
|
|
4396
4801
|
NotFoundError as NotFoundError5,
|
|
4397
|
-
useAtlas as
|
|
4802
|
+
useAtlas as useAtlas11,
|
|
4398
4803
|
BadRequestError as BadRequestError22
|
|
4399
4804
|
} from "@7365admin1/node-server-utils";
|
|
4400
4805
|
function useStockService() {
|
|
@@ -4403,7 +4808,7 @@ function useStockService() {
|
|
|
4403
4808
|
async function createStock(value, out = false, session) {
|
|
4404
4809
|
let ownSession = false;
|
|
4405
4810
|
if (!session) {
|
|
4406
|
-
session =
|
|
4811
|
+
session = useAtlas11.getClient()?.startSession();
|
|
4407
4812
|
ownSession = true;
|
|
4408
4813
|
}
|
|
4409
4814
|
try {
|
|
@@ -4447,7 +4852,7 @@ function useStockService() {
|
|
|
4447
4852
|
|
|
4448
4853
|
// src/controllers/hygiene-stock.controller.ts
|
|
4449
4854
|
import Joi13 from "joi";
|
|
4450
|
-
import { BadRequestError as BadRequestError23, logger as
|
|
4855
|
+
import { BadRequestError as BadRequestError23, logger as logger26 } from "@7365admin1/node-server-utils";
|
|
4451
4856
|
function useStockController() {
|
|
4452
4857
|
const { getStocksBySupplyId: _getStocksBySupplyId } = useStockRepository();
|
|
4453
4858
|
const { createStock: _createStock } = useStockService();
|
|
@@ -4461,7 +4866,7 @@ function useStockController() {
|
|
|
4461
4866
|
});
|
|
4462
4867
|
const { error } = validation.validate(payload);
|
|
4463
4868
|
if (error) {
|
|
4464
|
-
|
|
4869
|
+
logger26.log({ level: "error", message: error.message });
|
|
4465
4870
|
next(new BadRequestError23(error.message));
|
|
4466
4871
|
return;
|
|
4467
4872
|
}
|
|
@@ -4470,7 +4875,7 @@ function useStockController() {
|
|
|
4470
4875
|
res.status(201).json({ message: "Stock created successfully.", id });
|
|
4471
4876
|
return;
|
|
4472
4877
|
} catch (error2) {
|
|
4473
|
-
|
|
4878
|
+
logger26.log({ level: "error", message: error2.message });
|
|
4474
4879
|
next(error2);
|
|
4475
4880
|
return;
|
|
4476
4881
|
}
|
|
@@ -4486,7 +4891,7 @@ function useStockController() {
|
|
|
4486
4891
|
});
|
|
4487
4892
|
const { error } = validation.validate(query);
|
|
4488
4893
|
if (error) {
|
|
4489
|
-
|
|
4894
|
+
logger26.log({ level: "error", message: error.message });
|
|
4490
4895
|
next(new BadRequestError23(error.message));
|
|
4491
4896
|
return;
|
|
4492
4897
|
}
|
|
@@ -4506,7 +4911,7 @@ function useStockController() {
|
|
|
4506
4911
|
res.json(data);
|
|
4507
4912
|
return;
|
|
4508
4913
|
} catch (error2) {
|
|
4509
|
-
|
|
4914
|
+
logger26.log({ level: "error", message: error2.message });
|
|
4510
4915
|
next(error2);
|
|
4511
4916
|
return;
|
|
4512
4917
|
}
|
|
@@ -4519,8 +4924,8 @@ function useStockController() {
|
|
|
4519
4924
|
|
|
4520
4925
|
// src/models/hygiene-checkout-item.model.ts
|
|
4521
4926
|
import Joi14 from "joi";
|
|
4522
|
-
import { ObjectId as
|
|
4523
|
-
import { BadRequestError as BadRequestError24, logger as
|
|
4927
|
+
import { ObjectId as ObjectId15 } from "mongodb";
|
|
4928
|
+
import { BadRequestError as BadRequestError24, logger as logger27 } from "@7365admin1/node-server-utils";
|
|
4524
4929
|
var allowedCheckOutItemStatus = ["pending", "completed"];
|
|
4525
4930
|
var checkOutItemSchema = Joi14.object({
|
|
4526
4931
|
site: Joi14.string().hex().required(),
|
|
@@ -4534,19 +4939,19 @@ var checkOutItemSchema = Joi14.object({
|
|
|
4534
4939
|
function MCheckOutItem(value) {
|
|
4535
4940
|
const { error } = checkOutItemSchema.validate(value);
|
|
4536
4941
|
if (error) {
|
|
4537
|
-
|
|
4942
|
+
logger27.info(`Hygiene Check Out Item Model: ${error.message}`);
|
|
4538
4943
|
throw new BadRequestError24(error.message);
|
|
4539
4944
|
}
|
|
4540
4945
|
if (value.site) {
|
|
4541
4946
|
try {
|
|
4542
|
-
value.site = new
|
|
4947
|
+
value.site = new ObjectId15(value.site);
|
|
4543
4948
|
} catch (error2) {
|
|
4544
4949
|
throw new BadRequestError24("Invalid site ID format.");
|
|
4545
4950
|
}
|
|
4546
4951
|
}
|
|
4547
4952
|
if (value.supply) {
|
|
4548
4953
|
try {
|
|
4549
|
-
value.supply = new
|
|
4954
|
+
value.supply = new ObjectId15(value.supply);
|
|
4550
4955
|
} catch (error2) {
|
|
4551
4956
|
throw new BadRequestError24("Invalid supply ID format.");
|
|
4552
4957
|
}
|
|
@@ -4567,21 +4972,21 @@ function MCheckOutItem(value) {
|
|
|
4567
4972
|
}
|
|
4568
4973
|
|
|
4569
4974
|
// src/repositories/hygiene-checkout-item.repository.ts
|
|
4570
|
-
import { ObjectId as
|
|
4975
|
+
import { ObjectId as ObjectId16 } from "mongodb";
|
|
4571
4976
|
import {
|
|
4572
|
-
useAtlas as
|
|
4573
|
-
InternalServerError as
|
|
4977
|
+
useAtlas as useAtlas12,
|
|
4978
|
+
InternalServerError as InternalServerError10,
|
|
4574
4979
|
useCache as useCache8,
|
|
4575
|
-
logger as
|
|
4980
|
+
logger as logger28,
|
|
4576
4981
|
makeCacheKey as makeCacheKey8,
|
|
4577
4982
|
paginate as paginate7,
|
|
4578
4983
|
BadRequestError as BadRequestError25,
|
|
4579
4984
|
NotFoundError as NotFoundError6
|
|
4580
4985
|
} from "@7365admin1/node-server-utils";
|
|
4581
4986
|
function useCheckOutItemRepository() {
|
|
4582
|
-
const db =
|
|
4987
|
+
const db = useAtlas12.getDb();
|
|
4583
4988
|
if (!db) {
|
|
4584
|
-
throw new
|
|
4989
|
+
throw new InternalServerError10("Unable to connect to server.");
|
|
4585
4990
|
}
|
|
4586
4991
|
const namespace_collection = "site.supply.checkouts";
|
|
4587
4992
|
const collection = db.collection(namespace_collection);
|
|
@@ -4594,7 +4999,7 @@ function useCheckOutItemRepository() {
|
|
|
4594
4999
|
{ key: { status: 1 } }
|
|
4595
5000
|
]);
|
|
4596
5001
|
} catch (error) {
|
|
4597
|
-
throw new
|
|
5002
|
+
throw new InternalServerError10(
|
|
4598
5003
|
"Failed to create index on hygiene check out item."
|
|
4599
5004
|
);
|
|
4600
5005
|
}
|
|
@@ -4603,7 +5008,7 @@ function useCheckOutItemRepository() {
|
|
|
4603
5008
|
try {
|
|
4604
5009
|
await collection.createIndex({ supplyName: "text" });
|
|
4605
5010
|
} catch (error) {
|
|
4606
|
-
throw new
|
|
5011
|
+
throw new InternalServerError10(
|
|
4607
5012
|
"Failed to create text index on hygiene supply."
|
|
4608
5013
|
);
|
|
4609
5014
|
}
|
|
@@ -4613,9 +5018,9 @@ function useCheckOutItemRepository() {
|
|
|
4613
5018
|
value = MCheckOutItem(value);
|
|
4614
5019
|
const res = await collection.insertOne(value, { session });
|
|
4615
5020
|
delNamespace().then(() => {
|
|
4616
|
-
|
|
5021
|
+
logger28.info(`Cache cleared for namespace: ${namespace_collection}`);
|
|
4617
5022
|
}).catch((err) => {
|
|
4618
|
-
|
|
5023
|
+
logger28.error(
|
|
4619
5024
|
`Failed to clear cache for namespace: ${namespace_collection}`,
|
|
4620
5025
|
err
|
|
4621
5026
|
);
|
|
@@ -4640,7 +5045,7 @@ function useCheckOutItemRepository() {
|
|
|
4640
5045
|
limit
|
|
4641
5046
|
};
|
|
4642
5047
|
try {
|
|
4643
|
-
site = new
|
|
5048
|
+
site = new ObjectId16(site);
|
|
4644
5049
|
query.site = site;
|
|
4645
5050
|
cacheOptions.site = site.toString();
|
|
4646
5051
|
} catch (error) {
|
|
@@ -4653,7 +5058,7 @@ function useCheckOutItemRepository() {
|
|
|
4653
5058
|
const cacheKey = makeCacheKey8(namespace_collection, cacheOptions);
|
|
4654
5059
|
const cachedData = await getCache(cacheKey);
|
|
4655
5060
|
if (cachedData) {
|
|
4656
|
-
|
|
5061
|
+
logger28.info(`Cache hit for key: ${cacheKey}`);
|
|
4657
5062
|
return cachedData;
|
|
4658
5063
|
}
|
|
4659
5064
|
try {
|
|
@@ -4702,9 +5107,9 @@ function useCheckOutItemRepository() {
|
|
|
4702
5107
|
const length = await collection.countDocuments(query);
|
|
4703
5108
|
const data = paginate7(items, page, limit, length);
|
|
4704
5109
|
setCache(cacheKey, data, 15 * 60).then(() => {
|
|
4705
|
-
|
|
5110
|
+
logger28.info(`Cache set for key: ${cacheKey}`);
|
|
4706
5111
|
}).catch((err) => {
|
|
4707
|
-
|
|
5112
|
+
logger28.error(`Failed to set cache for key: ${cacheKey}`, err);
|
|
4708
5113
|
});
|
|
4709
5114
|
return data;
|
|
4710
5115
|
} catch (error) {
|
|
@@ -4713,7 +5118,7 @@ function useCheckOutItemRepository() {
|
|
|
4713
5118
|
}
|
|
4714
5119
|
async function getCheckOutItemById(_id, session) {
|
|
4715
5120
|
try {
|
|
4716
|
-
_id = new
|
|
5121
|
+
_id = new ObjectId16(_id);
|
|
4717
5122
|
} catch (error) {
|
|
4718
5123
|
throw new BadRequestError25("Invalid check out item ID format.");
|
|
4719
5124
|
}
|
|
@@ -4724,11 +5129,11 @@ function useCheckOutItemRepository() {
|
|
|
4724
5129
|
if (!session) {
|
|
4725
5130
|
const cachedData = await getCache(cacheKey);
|
|
4726
5131
|
if (cachedData) {
|
|
4727
|
-
|
|
5132
|
+
logger28.info(`Cache hit for key: ${cacheKey}`);
|
|
4728
5133
|
return cachedData;
|
|
4729
5134
|
}
|
|
4730
5135
|
} else {
|
|
4731
|
-
|
|
5136
|
+
logger28.info(`Skipping cache during transaction for key: ${cacheKey}`);
|
|
4732
5137
|
}
|
|
4733
5138
|
try {
|
|
4734
5139
|
const data = await collection.aggregate(
|
|
@@ -4766,9 +5171,9 @@ function useCheckOutItemRepository() {
|
|
|
4766
5171
|
throw new NotFoundError6("Check out item not found.");
|
|
4767
5172
|
}
|
|
4768
5173
|
setCache(cacheKey, data[0], 15 * 60).then(() => {
|
|
4769
|
-
|
|
5174
|
+
logger28.info(`Cache set for key: ${cacheKey}`);
|
|
4770
5175
|
}).catch((err) => {
|
|
4771
|
-
|
|
5176
|
+
logger28.error(`Failed to set cache for key: ${cacheKey}`, err);
|
|
4772
5177
|
});
|
|
4773
5178
|
return data[0];
|
|
4774
5179
|
} catch (error) {
|
|
@@ -4777,7 +5182,7 @@ function useCheckOutItemRepository() {
|
|
|
4777
5182
|
}
|
|
4778
5183
|
async function completeCheckOutItem(_id, session) {
|
|
4779
5184
|
try {
|
|
4780
|
-
_id = new
|
|
5185
|
+
_id = new ObjectId16(_id);
|
|
4781
5186
|
} catch (error) {
|
|
4782
5187
|
throw new BadRequestError25("Invalid check out item ID format.");
|
|
4783
5188
|
}
|
|
@@ -4792,12 +5197,12 @@ function useCheckOutItemRepository() {
|
|
|
4792
5197
|
{ session }
|
|
4793
5198
|
);
|
|
4794
5199
|
if (res.modifiedCount === 0) {
|
|
4795
|
-
throw new
|
|
5200
|
+
throw new InternalServerError10("Unable to complete check out item.");
|
|
4796
5201
|
}
|
|
4797
5202
|
delNamespace().then(() => {
|
|
4798
|
-
|
|
5203
|
+
logger28.info(`Cache cleared for namespace: ${namespace_collection}`);
|
|
4799
5204
|
}).catch((err) => {
|
|
4800
|
-
|
|
5205
|
+
logger28.error(
|
|
4801
5206
|
`Failed to clear cache for namespace: ${namespace_collection}`,
|
|
4802
5207
|
err
|
|
4803
5208
|
);
|
|
@@ -4819,7 +5224,7 @@ function useCheckOutItemRepository() {
|
|
|
4819
5224
|
|
|
4820
5225
|
// src/services/hygiene-checkout-item.service.ts
|
|
4821
5226
|
import { useUserRepo } from "@7365admin1/core";
|
|
4822
|
-
import { BadRequestError as BadRequestError26, useAtlas as
|
|
5227
|
+
import { BadRequestError as BadRequestError26, useAtlas as useAtlas13 } from "@7365admin1/node-server-utils";
|
|
4823
5228
|
function useCheckOutItemService() {
|
|
4824
5229
|
const {
|
|
4825
5230
|
createCheckOutItem: _createCheckOutItem,
|
|
@@ -4830,7 +5235,7 @@ function useCheckOutItemService() {
|
|
|
4830
5235
|
const { getUserById } = useUserRepo();
|
|
4831
5236
|
const { createStock } = useStockService();
|
|
4832
5237
|
async function createCheckOutItem(value) {
|
|
4833
|
-
const session =
|
|
5238
|
+
const session = useAtlas13.getClient()?.startSession();
|
|
4834
5239
|
try {
|
|
4835
5240
|
session?.startTransaction();
|
|
4836
5241
|
const supplyData = await getSupplyById(value.supply);
|
|
@@ -4870,10 +5275,10 @@ function useCheckOutItemService() {
|
|
|
4870
5275
|
}
|
|
4871
5276
|
}
|
|
4872
5277
|
async function createCheckOutItemByBatch(value) {
|
|
4873
|
-
const session =
|
|
5278
|
+
const session = useAtlas13.getClient()?.startSession();
|
|
4874
5279
|
try {
|
|
4875
5280
|
session?.startTransaction();
|
|
4876
|
-
const { site,
|
|
5281
|
+
const { site, createdBy, items } = value;
|
|
4877
5282
|
const createdByData = await getUserById(createdBy);
|
|
4878
5283
|
const createdCheckOutItemIds = [];
|
|
4879
5284
|
for (const item of items) {
|
|
@@ -4884,7 +5289,7 @@ function useCheckOutItemService() {
|
|
|
4884
5289
|
supply: item.supply,
|
|
4885
5290
|
supplyName: supplyData?.name || "",
|
|
4886
5291
|
qty: item.qty,
|
|
4887
|
-
attachment,
|
|
5292
|
+
attachment: item.attachment,
|
|
4888
5293
|
createdBy,
|
|
4889
5294
|
createdByName: createdByData?.name || ""
|
|
4890
5295
|
},
|
|
@@ -4918,7 +5323,7 @@ function useCheckOutItemService() {
|
|
|
4918
5323
|
|
|
4919
5324
|
// src/controllers/hygiene-checkout-item.controller.ts
|
|
4920
5325
|
import Joi15 from "joi";
|
|
4921
|
-
import { BadRequestError as BadRequestError27, logger as
|
|
5326
|
+
import { BadRequestError as BadRequestError27, logger as logger29 } from "@7365admin1/node-server-utils";
|
|
4922
5327
|
function useCheckOutItemController() {
|
|
4923
5328
|
const {
|
|
4924
5329
|
getCheckOutItems: _getCheckOutItems,
|
|
@@ -4948,7 +5353,7 @@ function useCheckOutItemController() {
|
|
|
4948
5353
|
});
|
|
4949
5354
|
const { error } = validation.validate(payload);
|
|
4950
5355
|
if (error) {
|
|
4951
|
-
|
|
5356
|
+
logger29.log({ level: "error", message: error.message });
|
|
4952
5357
|
next(new BadRequestError27(error.message));
|
|
4953
5358
|
return;
|
|
4954
5359
|
}
|
|
@@ -4957,7 +5362,7 @@ function useCheckOutItemController() {
|
|
|
4957
5362
|
res.status(201).json({ message: "Check out item created successfully.", id });
|
|
4958
5363
|
return;
|
|
4959
5364
|
} catch (error2) {
|
|
4960
|
-
|
|
5365
|
+
logger29.log({ level: "error", message: error2.message });
|
|
4961
5366
|
next(error2);
|
|
4962
5367
|
return;
|
|
4963
5368
|
}
|
|
@@ -4975,18 +5380,18 @@ function useCheckOutItemController() {
|
|
|
4975
5380
|
};
|
|
4976
5381
|
const validation = Joi15.object({
|
|
4977
5382
|
site: Joi15.string().hex().required(),
|
|
4978
|
-
attachment: Joi15.array().items(Joi15.string()).optional().allow(null),
|
|
4979
5383
|
createdBy: Joi15.string().hex().required(),
|
|
4980
5384
|
items: Joi15.array().items(
|
|
4981
5385
|
Joi15.object({
|
|
4982
5386
|
supply: Joi15.string().hex().required(),
|
|
4983
|
-
qty: Joi15.number().min(0).required()
|
|
5387
|
+
qty: Joi15.number().min(0).required(),
|
|
5388
|
+
attachment: Joi15.array().items(Joi15.string()).optional().allow(null)
|
|
4984
5389
|
})
|
|
4985
5390
|
).min(1).required()
|
|
4986
5391
|
});
|
|
4987
5392
|
const { error } = validation.validate(payload);
|
|
4988
5393
|
if (error) {
|
|
4989
|
-
|
|
5394
|
+
logger29.log({ level: "error", message: error.message });
|
|
4990
5395
|
next(new BadRequestError27(error.message));
|
|
4991
5396
|
return;
|
|
4992
5397
|
}
|
|
@@ -4995,7 +5400,7 @@ function useCheckOutItemController() {
|
|
|
4995
5400
|
res.status(201).json({ message: "Check out items created successfully." });
|
|
4996
5401
|
return;
|
|
4997
5402
|
} catch (error2) {
|
|
4998
|
-
|
|
5403
|
+
logger29.log({ level: "error", message: error2.message });
|
|
4999
5404
|
next(error2);
|
|
5000
5405
|
return;
|
|
5001
5406
|
}
|
|
@@ -5010,7 +5415,7 @@ function useCheckOutItemController() {
|
|
|
5010
5415
|
});
|
|
5011
5416
|
const { error } = validation.validate(query);
|
|
5012
5417
|
if (error) {
|
|
5013
|
-
|
|
5418
|
+
logger29.log({ level: "error", message: error.message });
|
|
5014
5419
|
next(new BadRequestError27(error.message));
|
|
5015
5420
|
return;
|
|
5016
5421
|
}
|
|
@@ -5028,7 +5433,7 @@ function useCheckOutItemController() {
|
|
|
5028
5433
|
res.json(data);
|
|
5029
5434
|
return;
|
|
5030
5435
|
} catch (error2) {
|
|
5031
|
-
|
|
5436
|
+
logger29.log({ level: "error", message: error2.message });
|
|
5032
5437
|
next(error2);
|
|
5033
5438
|
return;
|
|
5034
5439
|
}
|
|
@@ -5038,7 +5443,7 @@ function useCheckOutItemController() {
|
|
|
5038
5443
|
const _id = req.params.id;
|
|
5039
5444
|
const { error, value } = validation.validate(_id);
|
|
5040
5445
|
if (error) {
|
|
5041
|
-
|
|
5446
|
+
logger29.log({ level: "error", message: error.message });
|
|
5042
5447
|
next(new BadRequestError27(error.message));
|
|
5043
5448
|
return;
|
|
5044
5449
|
}
|
|
@@ -5047,7 +5452,7 @@ function useCheckOutItemController() {
|
|
|
5047
5452
|
res.json(data);
|
|
5048
5453
|
return;
|
|
5049
5454
|
} catch (error2) {
|
|
5050
|
-
|
|
5455
|
+
logger29.log({ level: "error", message: error2.message });
|
|
5051
5456
|
next(error2);
|
|
5052
5457
|
return;
|
|
5053
5458
|
}
|
|
@@ -5061,15 +5466,16 @@ function useCheckOutItemController() {
|
|
|
5061
5466
|
}
|
|
5062
5467
|
|
|
5063
5468
|
// src/models/hygiene-schedule-task.model.ts
|
|
5064
|
-
import { BadRequestError as BadRequestError28, logger as
|
|
5469
|
+
import { BadRequestError as BadRequestError28, logger as logger30 } from "@7365admin1/node-server-utils";
|
|
5065
5470
|
import Joi16 from "joi";
|
|
5066
|
-
import { ObjectId as
|
|
5471
|
+
import { ObjectId as ObjectId17 } from "mongodb";
|
|
5067
5472
|
var scheduleTaskSchema = Joi16.object({
|
|
5068
5473
|
site: Joi16.string().hex().required(),
|
|
5069
5474
|
title: Joi16.string().required(),
|
|
5070
5475
|
time: Joi16.string().pattern(/^([0-1]\d|2[0-3]):([0-5]\d)$/).required(),
|
|
5071
|
-
|
|
5072
|
-
|
|
5476
|
+
dates: Joi16.array().min(1).items(
|
|
5477
|
+
Joi16.string().pattern(/^\d{4}-\d{2}-\d{2}$/).required()
|
|
5478
|
+
).required(),
|
|
5073
5479
|
description: Joi16.string().optional().allow("", null),
|
|
5074
5480
|
areas: Joi16.array().min(1).items(
|
|
5075
5481
|
Joi16.object({
|
|
@@ -5082,12 +5488,12 @@ var scheduleTaskSchema = Joi16.object({
|
|
|
5082
5488
|
function MScheduleTask(value) {
|
|
5083
5489
|
const { error } = scheduleTaskSchema.validate(value);
|
|
5084
5490
|
if (error) {
|
|
5085
|
-
|
|
5491
|
+
logger30.info(`Hygiene Schedule Task Model: ${error.message}`);
|
|
5086
5492
|
throw new BadRequestError28(error.message);
|
|
5087
5493
|
}
|
|
5088
5494
|
if (value.site) {
|
|
5089
5495
|
try {
|
|
5090
|
-
value.site = new
|
|
5496
|
+
value.site = new ObjectId17(value.site);
|
|
5091
5497
|
} catch (error2) {
|
|
5092
5498
|
throw new BadRequestError28("Invalid site ID format.");
|
|
5093
5499
|
}
|
|
@@ -5097,7 +5503,7 @@ function MScheduleTask(value) {
|
|
|
5097
5503
|
try {
|
|
5098
5504
|
return {
|
|
5099
5505
|
name: area.name,
|
|
5100
|
-
value: new
|
|
5506
|
+
value: new ObjectId17(area.value.toString())
|
|
5101
5507
|
};
|
|
5102
5508
|
} catch (error2) {
|
|
5103
5509
|
throw new BadRequestError28(`Invalid area value format: ${area.name}`);
|
|
@@ -5106,7 +5512,7 @@ function MScheduleTask(value) {
|
|
|
5106
5512
|
}
|
|
5107
5513
|
if (value.createdBy) {
|
|
5108
5514
|
try {
|
|
5109
|
-
value.createdBy = new
|
|
5515
|
+
value.createdBy = new ObjectId17(value.createdBy);
|
|
5110
5516
|
} catch (error2) {
|
|
5111
5517
|
throw new BadRequestError28("Invalid createdBy ID format.");
|
|
5112
5518
|
}
|
|
@@ -5115,8 +5521,7 @@ function MScheduleTask(value) {
|
|
|
5115
5521
|
site: value.site,
|
|
5116
5522
|
title: value.title,
|
|
5117
5523
|
time: value.time,
|
|
5118
|
-
|
|
5119
|
-
endDate: value.endDate,
|
|
5524
|
+
dates: value.dates,
|
|
5120
5525
|
description: value.description,
|
|
5121
5526
|
areas: value.areas,
|
|
5122
5527
|
status: "active",
|
|
@@ -5128,21 +5533,21 @@ function MScheduleTask(value) {
|
|
|
5128
5533
|
}
|
|
5129
5534
|
|
|
5130
5535
|
// src/repositories/hygiene-schedule-task.repository.ts
|
|
5131
|
-
import { ObjectId as
|
|
5536
|
+
import { ObjectId as ObjectId18 } from "mongodb";
|
|
5132
5537
|
import {
|
|
5133
|
-
useAtlas as
|
|
5134
|
-
InternalServerError as
|
|
5538
|
+
useAtlas as useAtlas14,
|
|
5539
|
+
InternalServerError as InternalServerError11,
|
|
5135
5540
|
paginate as paginate8,
|
|
5136
5541
|
BadRequestError as BadRequestError29,
|
|
5137
5542
|
useCache as useCache9,
|
|
5138
|
-
logger as
|
|
5543
|
+
logger as logger31,
|
|
5139
5544
|
makeCacheKey as makeCacheKey9,
|
|
5140
5545
|
NotFoundError as NotFoundError7
|
|
5141
5546
|
} from "@7365admin1/node-server-utils";
|
|
5142
5547
|
function useScheduleTaskRepository() {
|
|
5143
|
-
const db =
|
|
5548
|
+
const db = useAtlas14.getDb();
|
|
5144
5549
|
if (!db) {
|
|
5145
|
-
throw new
|
|
5550
|
+
throw new InternalServerError11("Unable to connect to server.");
|
|
5146
5551
|
}
|
|
5147
5552
|
const namespace_collection = "site.schedule-tasks";
|
|
5148
5553
|
const collection = db.collection(namespace_collection);
|
|
@@ -5154,7 +5559,7 @@ function useScheduleTaskRepository() {
|
|
|
5154
5559
|
{ key: { status: 1 } }
|
|
5155
5560
|
]);
|
|
5156
5561
|
} catch (error) {
|
|
5157
|
-
throw new
|
|
5562
|
+
throw new InternalServerError11(
|
|
5158
5563
|
"Failed to create index on hygiene schedule task."
|
|
5159
5564
|
);
|
|
5160
5565
|
}
|
|
@@ -5163,7 +5568,7 @@ function useScheduleTaskRepository() {
|
|
|
5163
5568
|
try {
|
|
5164
5569
|
await collection.createIndex({ title: "text", description: "text" });
|
|
5165
5570
|
} catch (error) {
|
|
5166
|
-
throw new
|
|
5571
|
+
throw new InternalServerError11(
|
|
5167
5572
|
"Failed to create text index on hygiene schedule task."
|
|
5168
5573
|
);
|
|
5169
5574
|
}
|
|
@@ -5173,9 +5578,9 @@ function useScheduleTaskRepository() {
|
|
|
5173
5578
|
value = MScheduleTask(value);
|
|
5174
5579
|
const res = await collection.insertOne(value, { session });
|
|
5175
5580
|
delNamespace().then(() => {
|
|
5176
|
-
|
|
5581
|
+
logger31.info(`Cache cleared for namespace: ${namespace_collection}`);
|
|
5177
5582
|
}).catch((err) => {
|
|
5178
|
-
|
|
5583
|
+
logger31.error(
|
|
5179
5584
|
`Failed to clear cache for namespace: ${namespace_collection}`,
|
|
5180
5585
|
err
|
|
5181
5586
|
);
|
|
@@ -5200,7 +5605,7 @@ function useScheduleTaskRepository() {
|
|
|
5200
5605
|
limit
|
|
5201
5606
|
};
|
|
5202
5607
|
try {
|
|
5203
|
-
site = new
|
|
5608
|
+
site = new ObjectId18(site);
|
|
5204
5609
|
query.site = site;
|
|
5205
5610
|
cacheOptions.site = site.toString();
|
|
5206
5611
|
} catch (error) {
|
|
@@ -5213,7 +5618,7 @@ function useScheduleTaskRepository() {
|
|
|
5213
5618
|
const cacheKey = makeCacheKey9(namespace_collection, cacheOptions);
|
|
5214
5619
|
const cachedData = await getCache(cacheKey);
|
|
5215
5620
|
if (cachedData) {
|
|
5216
|
-
|
|
5621
|
+
logger31.info(`Cache hit for key: ${cacheKey}`);
|
|
5217
5622
|
return cachedData;
|
|
5218
5623
|
}
|
|
5219
5624
|
try {
|
|
@@ -5233,9 +5638,9 @@ function useScheduleTaskRepository() {
|
|
|
5233
5638
|
const length = await collection.countDocuments(query);
|
|
5234
5639
|
const data = paginate8(items, page, limit, length);
|
|
5235
5640
|
setCache(cacheKey, data, 15 * 60).then(() => {
|
|
5236
|
-
|
|
5641
|
+
logger31.info(`Cache set for key: ${cacheKey}`);
|
|
5237
5642
|
}).catch((err) => {
|
|
5238
|
-
|
|
5643
|
+
logger31.error(`Failed to set cache for key: ${cacheKey}`, err);
|
|
5239
5644
|
});
|
|
5240
5645
|
return data;
|
|
5241
5646
|
} catch (error) {
|
|
@@ -5268,7 +5673,7 @@ function useScheduleTaskRepository() {
|
|
|
5268
5673
|
limit
|
|
5269
5674
|
};
|
|
5270
5675
|
try {
|
|
5271
|
-
site = new
|
|
5676
|
+
site = new ObjectId18(site);
|
|
5272
5677
|
query.site = site;
|
|
5273
5678
|
cacheOptions.site = site.toString();
|
|
5274
5679
|
} catch (error) {
|
|
@@ -5281,7 +5686,7 @@ function useScheduleTaskRepository() {
|
|
|
5281
5686
|
const cacheKey = makeCacheKey9(namespace_collection, cacheOptions);
|
|
5282
5687
|
const cachedData = await getCache(cacheKey);
|
|
5283
5688
|
if (cachedData) {
|
|
5284
|
-
|
|
5689
|
+
logger31.info(`Cache hit for key: ${cacheKey}`);
|
|
5285
5690
|
return cachedData;
|
|
5286
5691
|
}
|
|
5287
5692
|
try {
|
|
@@ -5300,9 +5705,9 @@ function useScheduleTaskRepository() {
|
|
|
5300
5705
|
const length = await collection.countDocuments(query);
|
|
5301
5706
|
const data = paginate8(items, page, limit, length);
|
|
5302
5707
|
setCache(cacheKey, data, 15 * 60).then(() => {
|
|
5303
|
-
|
|
5708
|
+
logger31.info(`Cache set for key: ${cacheKey}`);
|
|
5304
5709
|
}).catch((err) => {
|
|
5305
|
-
|
|
5710
|
+
logger31.error(`Failed to set cache for key: ${cacheKey}`, err);
|
|
5306
5711
|
});
|
|
5307
5712
|
return data;
|
|
5308
5713
|
} catch (error) {
|
|
@@ -5311,7 +5716,7 @@ function useScheduleTaskRepository() {
|
|
|
5311
5716
|
}
|
|
5312
5717
|
async function getScheduleTaskById(_id, session) {
|
|
5313
5718
|
try {
|
|
5314
|
-
_id = new
|
|
5719
|
+
_id = new ObjectId18(_id);
|
|
5315
5720
|
} catch (error) {
|
|
5316
5721
|
throw new BadRequestError29("Invalid schedule task ID format.");
|
|
5317
5722
|
}
|
|
@@ -5325,11 +5730,11 @@ function useScheduleTaskRepository() {
|
|
|
5325
5730
|
if (!session) {
|
|
5326
5731
|
const cachedData = await getCache(cacheKey);
|
|
5327
5732
|
if (cachedData) {
|
|
5328
|
-
|
|
5733
|
+
logger31.info(`Cache hit for key: ${cacheKey}`);
|
|
5329
5734
|
return cachedData;
|
|
5330
5735
|
}
|
|
5331
5736
|
} else {
|
|
5332
|
-
|
|
5737
|
+
logger31.info(`Skipping cache during transaction for key: ${cacheKey}`);
|
|
5333
5738
|
}
|
|
5334
5739
|
try {
|
|
5335
5740
|
const data = await collection.aggregate([
|
|
@@ -5338,8 +5743,7 @@ function useScheduleTaskRepository() {
|
|
|
5338
5743
|
$project: {
|
|
5339
5744
|
title: 1,
|
|
5340
5745
|
time: 1,
|
|
5341
|
-
|
|
5342
|
-
endDate: 1,
|
|
5746
|
+
dates: 1,
|
|
5343
5747
|
description: 1,
|
|
5344
5748
|
areas: 1,
|
|
5345
5749
|
status: 1,
|
|
@@ -5351,9 +5755,9 @@ function useScheduleTaskRepository() {
|
|
|
5351
5755
|
throw new NotFoundError7("Schedule task not found.");
|
|
5352
5756
|
}
|
|
5353
5757
|
setCache(cacheKey, data[0], 15 * 60).then(() => {
|
|
5354
|
-
|
|
5758
|
+
logger31.info(`Cache set for key: ${cacheKey}`);
|
|
5355
5759
|
}).catch((err) => {
|
|
5356
|
-
|
|
5760
|
+
logger31.error(`Failed to set cache for key: ${cacheKey}`, err);
|
|
5357
5761
|
});
|
|
5358
5762
|
return data[0];
|
|
5359
5763
|
} catch (error) {
|
|
@@ -5362,7 +5766,7 @@ function useScheduleTaskRepository() {
|
|
|
5362
5766
|
}
|
|
5363
5767
|
async function updateScheduleTask(_id, value, session) {
|
|
5364
5768
|
try {
|
|
5365
|
-
_id = new
|
|
5769
|
+
_id = new ObjectId18(_id);
|
|
5366
5770
|
} catch (error) {
|
|
5367
5771
|
throw new BadRequestError29("Invalid schedule task ID format.");
|
|
5368
5772
|
}
|
|
@@ -5371,7 +5775,7 @@ function useScheduleTaskRepository() {
|
|
|
5371
5775
|
try {
|
|
5372
5776
|
return {
|
|
5373
5777
|
name: area.name,
|
|
5374
|
-
value: new
|
|
5778
|
+
value: new ObjectId18(area.value.toString())
|
|
5375
5779
|
};
|
|
5376
5780
|
} catch (error) {
|
|
5377
5781
|
throw new BadRequestError29(`Invalid area value format: ${area.name}`);
|
|
@@ -5386,14 +5790,14 @@ function useScheduleTaskRepository() {
|
|
|
5386
5790
|
{ session }
|
|
5387
5791
|
);
|
|
5388
5792
|
if (res.modifiedCount === 0) {
|
|
5389
|
-
throw new
|
|
5793
|
+
throw new InternalServerError11(
|
|
5390
5794
|
"Unable to update hygiene schedule task."
|
|
5391
5795
|
);
|
|
5392
5796
|
}
|
|
5393
5797
|
delNamespace().then(() => {
|
|
5394
|
-
|
|
5798
|
+
logger31.info(`Cache cleared for namespace: ${namespace_collection}`);
|
|
5395
5799
|
}).catch((err) => {
|
|
5396
|
-
|
|
5800
|
+
logger31.error(
|
|
5397
5801
|
`Failed to clear cache for namespace: ${namespace_collection}`,
|
|
5398
5802
|
err
|
|
5399
5803
|
);
|
|
@@ -5416,7 +5820,7 @@ function useScheduleTaskRepository() {
|
|
|
5416
5820
|
}
|
|
5417
5821
|
|
|
5418
5822
|
// src/services/hygiene-schedule-task.service.ts
|
|
5419
|
-
import { logger as
|
|
5823
|
+
import { logger as logger32 } from "@7365admin1/node-server-utils";
|
|
5420
5824
|
function useScheduleTaskService() {
|
|
5421
5825
|
const { createParentChecklist } = useParentChecklistRepo();
|
|
5422
5826
|
const { getAllScheduleTask } = useScheduleTaskRepository();
|
|
@@ -5436,43 +5840,38 @@ function useScheduleTaskService() {
|
|
|
5436
5840
|
timeZone: "Asia/Singapore"
|
|
5437
5841
|
});
|
|
5438
5842
|
const [currentHour, currentMinute] = timeString.split(":").map(Number);
|
|
5439
|
-
|
|
5843
|
+
logger32.info(
|
|
5844
|
+
`Checking schedule ${schedule._id}: Current time ${currentHour}:${currentMinute}, Schedule time ${schedule.time}, Dates ${JSON.stringify(schedule.dates)}`
|
|
5845
|
+
);
|
|
5846
|
+
const currentDateFormatted = now.toLocaleDateString("en-CA", {
|
|
5440
5847
|
timeZone: "Asia/Singapore"
|
|
5441
5848
|
});
|
|
5442
|
-
|
|
5443
|
-
`
|
|
5444
|
-
);
|
|
5445
|
-
const startDate = /* @__PURE__ */ new Date(schedule.startDate + "T00:00:00");
|
|
5446
|
-
const currentDateOnly = /* @__PURE__ */ new Date(currentDateString + "T00:00:00");
|
|
5447
|
-
if (currentDateOnly < startDate) {
|
|
5448
|
-
logger31.info(
|
|
5449
|
-
`Schedule ${schedule._id}: Current date ${currentDateString} is before start date ${schedule.startDate}`
|
|
5450
|
-
);
|
|
5849
|
+
if (!schedule.dates || !Array.isArray(schedule.dates) || schedule.dates.length === 0) {
|
|
5850
|
+
logger32.info(`Schedule ${schedule._id}: No dates configured, skipping`);
|
|
5451
5851
|
return false;
|
|
5452
5852
|
}
|
|
5453
|
-
if (schedule.
|
|
5454
|
-
|
|
5455
|
-
|
|
5456
|
-
|
|
5457
|
-
|
|
5458
|
-
|
|
5459
|
-
|
|
5460
|
-
}
|
|
5853
|
+
if (!schedule.dates.includes(currentDateFormatted)) {
|
|
5854
|
+
logger32.info(
|
|
5855
|
+
`Schedule ${schedule._id}: Current date ${currentDateFormatted} is not in scheduled dates [${schedule.dates.join(
|
|
5856
|
+
", "
|
|
5857
|
+
)}]`
|
|
5858
|
+
);
|
|
5859
|
+
return false;
|
|
5461
5860
|
}
|
|
5462
5861
|
const [scheduleHour, scheduleMinute] = schedule.time.split(":").map(Number);
|
|
5463
5862
|
const timeMatches = currentHour === scheduleHour && currentMinute === scheduleMinute;
|
|
5464
5863
|
if (!timeMatches) {
|
|
5465
|
-
|
|
5864
|
+
logger32.info(
|
|
5466
5865
|
`Schedule ${schedule._id}: Time does not match. Current: ${currentHour}:${currentMinute}, Expected: ${scheduleHour}:${scheduleMinute}`
|
|
5467
5866
|
);
|
|
5468
5867
|
return false;
|
|
5469
5868
|
}
|
|
5470
|
-
|
|
5869
|
+
logger32.info(
|
|
5471
5870
|
`Schedule ${schedule._id}: All conditions matched - Date is within range and time matches`
|
|
5472
5871
|
);
|
|
5473
5872
|
return true;
|
|
5474
5873
|
} catch (error) {
|
|
5475
|
-
|
|
5874
|
+
logger32.error(
|
|
5476
5875
|
`Error checking schedule conditions for ${schedule._id}:`,
|
|
5477
5876
|
error
|
|
5478
5877
|
);
|
|
@@ -5481,40 +5880,42 @@ function useScheduleTaskService() {
|
|
|
5481
5880
|
}
|
|
5482
5881
|
async function processScheduledTasks(currentDate) {
|
|
5483
5882
|
try {
|
|
5484
|
-
|
|
5883
|
+
logger32.info("Starting scheduled task processing...");
|
|
5485
5884
|
const scheduleTasks = await getAllScheduleTask();
|
|
5486
5885
|
if (!scheduleTasks || scheduleTasks.length === 0) {
|
|
5487
|
-
|
|
5886
|
+
logger32.info("No schedule tasks found to process");
|
|
5488
5887
|
return { processed: 0, validated: 0 };
|
|
5489
5888
|
}
|
|
5490
|
-
|
|
5889
|
+
logger32.info(`Found ${scheduleTasks.length} schedule tasks to check`);
|
|
5491
5890
|
let processedCount = 0;
|
|
5492
5891
|
let validatedCount = 0;
|
|
5493
5892
|
const validatedTasks = [];
|
|
5494
5893
|
for (const scheduleTask of scheduleTasks) {
|
|
5495
5894
|
try {
|
|
5496
|
-
|
|
5497
|
-
`Checking schedule ${scheduleTask._id} - ${scheduleTask.title}: time=${scheduleTask.time},
|
|
5895
|
+
logger32.info(
|
|
5896
|
+
`Checking schedule ${scheduleTask._id} - ${scheduleTask.title}: time=${scheduleTask.time}, dates=${JSON.stringify(
|
|
5897
|
+
scheduleTask.dates
|
|
5898
|
+
)}`
|
|
5498
5899
|
);
|
|
5499
5900
|
const shouldRun = checkScheduleConditions(scheduleTask, currentDate);
|
|
5500
5901
|
if (!shouldRun) {
|
|
5501
|
-
|
|
5902
|
+
logger32.info(
|
|
5502
5903
|
`Schedule ${scheduleTask._id} conditions not met, skipping`
|
|
5503
5904
|
);
|
|
5504
5905
|
continue;
|
|
5505
5906
|
}
|
|
5506
|
-
|
|
5907
|
+
logger32.info(
|
|
5507
5908
|
`Schedule ${scheduleTask._id} conditions validated, creating area checklists`
|
|
5508
5909
|
);
|
|
5509
5910
|
if (!scheduleTask._id) {
|
|
5510
|
-
|
|
5911
|
+
logger32.warn(`Schedule ${scheduleTask.title} has no _id, skipping`);
|
|
5511
5912
|
continue;
|
|
5512
5913
|
}
|
|
5513
5914
|
if (!scheduleTask.site) {
|
|
5514
|
-
|
|
5915
|
+
logger32.warn(`Schedule ${scheduleTask._id} has no site, skipping`);
|
|
5515
5916
|
continue;
|
|
5516
5917
|
}
|
|
5517
|
-
|
|
5918
|
+
logger32.info(
|
|
5518
5919
|
`Getting or creating parent checklist for schedule ${scheduleTask._id} in site ${scheduleTask.site}`
|
|
5519
5920
|
);
|
|
5520
5921
|
const parentChecklistIds = await createParentChecklist({
|
|
@@ -5522,7 +5923,7 @@ function useScheduleTaskService() {
|
|
|
5522
5923
|
createdAt: /* @__PURE__ */ new Date()
|
|
5523
5924
|
});
|
|
5524
5925
|
const parentChecklistId = Array.isArray(parentChecklistIds) ? parentChecklistIds[0] : parentChecklistIds;
|
|
5525
|
-
|
|
5926
|
+
logger32.info(
|
|
5526
5927
|
`Using parent checklist ${parentChecklistId}, now creating/updating area checklists`
|
|
5527
5928
|
);
|
|
5528
5929
|
for (const area of scheduleTask.areas) {
|
|
@@ -5535,14 +5936,14 @@ function useScheduleTaskService() {
|
|
|
5535
5936
|
unit: unit.unit.toString(),
|
|
5536
5937
|
name: unit.name
|
|
5537
5938
|
}));
|
|
5538
|
-
|
|
5939
|
+
logger32.info(
|
|
5539
5940
|
`Area ${area.name} (${areaId}): Using units from area details: ${JSON.stringify(
|
|
5540
5941
|
units
|
|
5541
5942
|
)}`
|
|
5542
5943
|
);
|
|
5543
5944
|
}
|
|
5544
5945
|
if (units.length === 0) {
|
|
5545
|
-
|
|
5946
|
+
logger32.warn(
|
|
5546
5947
|
`Area ${area.name} (${areaId}): No units found, skipping area.`
|
|
5547
5948
|
);
|
|
5548
5949
|
continue;
|
|
@@ -5553,11 +5954,11 @@ function useScheduleTaskService() {
|
|
|
5553
5954
|
parentChecklistId.toString(),
|
|
5554
5955
|
areaId
|
|
5555
5956
|
);
|
|
5556
|
-
|
|
5957
|
+
logger32.info(
|
|
5557
5958
|
`Area ${area.name} (${areaId}): Existing area checklist found: ${existingAreaChecklist ? "Yes" : "No"}`
|
|
5558
5959
|
);
|
|
5559
5960
|
if (existingAreaChecklist) {
|
|
5560
|
-
|
|
5961
|
+
logger32.info(
|
|
5561
5962
|
`Area ${area.name} (${areaId}): Existing checklist content: ${JSON.stringify(
|
|
5562
5963
|
existingAreaChecklist.checklist
|
|
5563
5964
|
)}`
|
|
@@ -5565,7 +5966,7 @@ function useScheduleTaskService() {
|
|
|
5565
5966
|
}
|
|
5566
5967
|
} catch (error) {
|
|
5567
5968
|
existingAreaChecklist = null;
|
|
5568
|
-
|
|
5969
|
+
logger32.info(
|
|
5569
5970
|
`Area ${area.name} (${areaId}): No existing area checklist found (exception).`
|
|
5570
5971
|
);
|
|
5571
5972
|
}
|
|
@@ -5579,7 +5980,7 @@ function useScheduleTaskService() {
|
|
|
5579
5980
|
...existingAreaChecklist.checklist || [],
|
|
5580
5981
|
newSet
|
|
5581
5982
|
];
|
|
5582
|
-
|
|
5983
|
+
logger32.info(
|
|
5583
5984
|
`Area ${area.name} (${areaId}): Appending new set ${newSet.set} to checklist. Updated checklist: ${JSON.stringify(
|
|
5584
5985
|
updatedChecklist
|
|
5585
5986
|
)}`
|
|
@@ -5587,7 +5988,7 @@ function useScheduleTaskService() {
|
|
|
5587
5988
|
await updateAreaChecklist(existingAreaChecklist._id, {
|
|
5588
5989
|
checklist: updatedChecklist
|
|
5589
5990
|
});
|
|
5590
|
-
|
|
5991
|
+
logger32.info(
|
|
5591
5992
|
`Appended set ${newSet.set} to area checklist for area ${area.name}`
|
|
5592
5993
|
);
|
|
5593
5994
|
try {
|
|
@@ -5595,13 +5996,13 @@ function useScheduleTaskService() {
|
|
|
5595
5996
|
parentChecklistId.toString(),
|
|
5596
5997
|
areaId
|
|
5597
5998
|
);
|
|
5598
|
-
|
|
5999
|
+
logger32.info(
|
|
5599
6000
|
`Area ${area.name} (${areaId}): Checklist after update: ${JSON.stringify(
|
|
5600
6001
|
verifyChecklist.checklist
|
|
5601
6002
|
)}`
|
|
5602
6003
|
);
|
|
5603
6004
|
} catch (verifyError) {
|
|
5604
|
-
|
|
6005
|
+
logger32.warn(
|
|
5605
6006
|
`Area ${area.name} (${areaId}): Error verifying checklist after update:`,
|
|
5606
6007
|
verifyError
|
|
5607
6008
|
);
|
|
@@ -5620,50 +6021,50 @@ function useScheduleTaskService() {
|
|
|
5620
6021
|
],
|
|
5621
6022
|
createdBy: scheduleTask.createdBy
|
|
5622
6023
|
};
|
|
5623
|
-
|
|
6024
|
+
logger32.info(
|
|
5624
6025
|
`Area ${area.name} (${areaId}): Creating new area checklist with data: ${JSON.stringify(
|
|
5625
6026
|
checklistData
|
|
5626
6027
|
)}`
|
|
5627
6028
|
);
|
|
5628
6029
|
await createAreaChecklist(checklistData);
|
|
5629
|
-
|
|
6030
|
+
logger32.info(`Created new area checklist for area ${area.name}`);
|
|
5630
6031
|
try {
|
|
5631
6032
|
const verifyChecklist = await getAreaChecklistByAreaAndSchedule(
|
|
5632
6033
|
parentChecklistId.toString(),
|
|
5633
6034
|
areaId
|
|
5634
6035
|
);
|
|
5635
|
-
|
|
6036
|
+
logger32.info(
|
|
5636
6037
|
`Area ${area.name} (${areaId}): Checklist after creation: ${JSON.stringify(
|
|
5637
6038
|
verifyChecklist.checklist
|
|
5638
6039
|
)}`
|
|
5639
6040
|
);
|
|
5640
6041
|
} catch (verifyError) {
|
|
5641
|
-
|
|
6042
|
+
logger32.warn(
|
|
5642
6043
|
`Area ${area.name} (${areaId}): Error verifying checklist after creation:`,
|
|
5643
6044
|
verifyError
|
|
5644
6045
|
);
|
|
5645
6046
|
}
|
|
5646
6047
|
}
|
|
5647
6048
|
} catch (error) {
|
|
5648
|
-
|
|
6049
|
+
logger32.error(`Error processing area ${area.name}:`, error);
|
|
5649
6050
|
continue;
|
|
5650
6051
|
}
|
|
5651
6052
|
}
|
|
5652
6053
|
processedCount++;
|
|
5653
6054
|
validatedCount++;
|
|
5654
6055
|
validatedTasks.push(scheduleTask);
|
|
5655
|
-
|
|
6056
|
+
logger32.info(
|
|
5656
6057
|
`Successfully processed schedule ${scheduleTask._id}, created/updated area checklists for all areas.`
|
|
5657
6058
|
);
|
|
5658
6059
|
} catch (error) {
|
|
5659
|
-
|
|
6060
|
+
logger32.error(
|
|
5660
6061
|
`Error processing schedule task ${scheduleTask._id}:`,
|
|
5661
6062
|
error
|
|
5662
6063
|
);
|
|
5663
6064
|
continue;
|
|
5664
6065
|
}
|
|
5665
6066
|
}
|
|
5666
|
-
|
|
6067
|
+
logger32.info(
|
|
5667
6068
|
`Scheduled task processing completed. Processed: ${processedCount}, Validated: ${validatedCount} tasks`
|
|
5668
6069
|
);
|
|
5669
6070
|
return {
|
|
@@ -5672,7 +6073,7 @@ function useScheduleTaskService() {
|
|
|
5672
6073
|
tasks: validatedTasks
|
|
5673
6074
|
};
|
|
5674
6075
|
} catch (error) {
|
|
5675
|
-
|
|
6076
|
+
logger32.error("Error processing scheduled tasks:", error);
|
|
5676
6077
|
throw error;
|
|
5677
6078
|
}
|
|
5678
6079
|
}
|
|
@@ -5681,7 +6082,7 @@ function useScheduleTaskService() {
|
|
|
5681
6082
|
|
|
5682
6083
|
// src/controllers/hygiene-schedule-task.controller.ts
|
|
5683
6084
|
import Joi17 from "joi";
|
|
5684
|
-
import { BadRequestError as BadRequestError30, logger as
|
|
6085
|
+
import { BadRequestError as BadRequestError30, logger as logger33 } from "@7365admin1/node-server-utils";
|
|
5685
6086
|
function useScheduleTaskController() {
|
|
5686
6087
|
const {
|
|
5687
6088
|
createScheduleTask: _createScheduleTask,
|
|
@@ -5699,7 +6100,7 @@ function useScheduleTaskController() {
|
|
|
5699
6100
|
const payload = { ...req.body, ...req.params, createdBy };
|
|
5700
6101
|
const { error } = scheduleTaskSchema.validate(payload);
|
|
5701
6102
|
if (error) {
|
|
5702
|
-
|
|
6103
|
+
logger33.log({ level: "error", message: error.message });
|
|
5703
6104
|
next(new BadRequestError30(error.message));
|
|
5704
6105
|
return;
|
|
5705
6106
|
}
|
|
@@ -5708,7 +6109,7 @@ function useScheduleTaskController() {
|
|
|
5708
6109
|
res.status(201).json({ message: "Schedule task created successfully.", id });
|
|
5709
6110
|
return;
|
|
5710
6111
|
} catch (error2) {
|
|
5711
|
-
|
|
6112
|
+
logger33.log({ level: "error", message: error2.message });
|
|
5712
6113
|
next(error2);
|
|
5713
6114
|
return;
|
|
5714
6115
|
}
|
|
@@ -5723,7 +6124,7 @@ function useScheduleTaskController() {
|
|
|
5723
6124
|
});
|
|
5724
6125
|
const { error } = validation.validate(query);
|
|
5725
6126
|
if (error) {
|
|
5726
|
-
|
|
6127
|
+
logger33.log({ level: "error", message: error.message });
|
|
5727
6128
|
next(new BadRequestError30(error.message));
|
|
5728
6129
|
return;
|
|
5729
6130
|
}
|
|
@@ -5741,7 +6142,7 @@ function useScheduleTaskController() {
|
|
|
5741
6142
|
res.json(data);
|
|
5742
6143
|
return;
|
|
5743
6144
|
} catch (error2) {
|
|
5744
|
-
|
|
6145
|
+
logger33.log({ level: "error", message: error2.message });
|
|
5745
6146
|
next(error2);
|
|
5746
6147
|
return;
|
|
5747
6148
|
}
|
|
@@ -5756,7 +6157,7 @@ function useScheduleTaskController() {
|
|
|
5756
6157
|
});
|
|
5757
6158
|
const { error } = validation.validate(query);
|
|
5758
6159
|
if (error) {
|
|
5759
|
-
|
|
6160
|
+
logger33.log({ level: "error", message: error.message });
|
|
5760
6161
|
next(new BadRequestError30(error.message));
|
|
5761
6162
|
return;
|
|
5762
6163
|
}
|
|
@@ -5774,7 +6175,7 @@ function useScheduleTaskController() {
|
|
|
5774
6175
|
res.json(data);
|
|
5775
6176
|
return;
|
|
5776
6177
|
} catch (error2) {
|
|
5777
|
-
|
|
6178
|
+
logger33.log({ level: "error", message: error2.message });
|
|
5778
6179
|
next(error2);
|
|
5779
6180
|
return;
|
|
5780
6181
|
}
|
|
@@ -5784,7 +6185,7 @@ function useScheduleTaskController() {
|
|
|
5784
6185
|
const _id = req.params.id;
|
|
5785
6186
|
const { error, value } = validation.validate(_id);
|
|
5786
6187
|
if (error) {
|
|
5787
|
-
|
|
6188
|
+
logger33.log({ level: "error", message: error.message });
|
|
5788
6189
|
next(new BadRequestError30(error.message));
|
|
5789
6190
|
return;
|
|
5790
6191
|
}
|
|
@@ -5793,7 +6194,7 @@ function useScheduleTaskController() {
|
|
|
5793
6194
|
res.json(data);
|
|
5794
6195
|
return;
|
|
5795
6196
|
} catch (error2) {
|
|
5796
|
-
|
|
6197
|
+
logger33.log({ level: "error", message: error2.message });
|
|
5797
6198
|
next(error2);
|
|
5798
6199
|
return;
|
|
5799
6200
|
}
|
|
@@ -5804,8 +6205,9 @@ function useScheduleTaskController() {
|
|
|
5804
6205
|
id: Joi17.string().hex().required(),
|
|
5805
6206
|
title: Joi17.string().optional().allow("", null),
|
|
5806
6207
|
time: Joi17.string().pattern(/^([0-1]\d|2[0-3]):([0-5]\d)$/).optional().allow("", null),
|
|
5807
|
-
|
|
5808
|
-
|
|
6208
|
+
dates: Joi17.array().min(1).items(
|
|
6209
|
+
Joi17.string().pattern(/^\d{4}-\d{2}-\d{2}$/).required()
|
|
6210
|
+
).optional(),
|
|
5809
6211
|
description: Joi17.string().optional().allow("", null),
|
|
5810
6212
|
areas: Joi17.array().min(1).items(
|
|
5811
6213
|
Joi17.object({
|
|
@@ -5816,7 +6218,7 @@ function useScheduleTaskController() {
|
|
|
5816
6218
|
});
|
|
5817
6219
|
const { error } = validation.validate(payload);
|
|
5818
6220
|
if (error) {
|
|
5819
|
-
|
|
6221
|
+
logger33.log({ level: "error", message: error.message });
|
|
5820
6222
|
next(new BadRequestError30(error.message));
|
|
5821
6223
|
return;
|
|
5822
6224
|
}
|
|
@@ -5826,7 +6228,7 @@ function useScheduleTaskController() {
|
|
|
5826
6228
|
res.json({ message: "Schedule task updated successfully." });
|
|
5827
6229
|
return;
|
|
5828
6230
|
} catch (error2) {
|
|
5829
|
-
|
|
6231
|
+
logger33.log({ level: "error", message: error2.message });
|
|
5830
6232
|
next(error2);
|
|
5831
6233
|
return;
|
|
5832
6234
|
}
|
|
@@ -5841,8 +6243,8 @@ function useScheduleTaskController() {
|
|
|
5841
6243
|
}
|
|
5842
6244
|
|
|
5843
6245
|
// src/services/hygiene-qr.service.ts
|
|
5844
|
-
import { logger as
|
|
5845
|
-
import { launch } from "puppeteer";
|
|
6246
|
+
import { logger as logger34 } from "@7365admin1/node-server-utils";
|
|
6247
|
+
import { launch as launch2 } from "puppeteer";
|
|
5846
6248
|
import QRCode from "qrcode";
|
|
5847
6249
|
function useQRService() {
|
|
5848
6250
|
async function generateQRDataUrl(qrUrl) {
|
|
@@ -5859,7 +6261,7 @@ function useQRService() {
|
|
|
5859
6261
|
async function generateQRImage(qrUrl) {
|
|
5860
6262
|
try {
|
|
5861
6263
|
const qrDataUrl = await generateQRDataUrl(qrUrl);
|
|
5862
|
-
const browser = await
|
|
6264
|
+
const browser = await launch2({
|
|
5863
6265
|
headless: true,
|
|
5864
6266
|
executablePath: process.env.CHROME_BINARY,
|
|
5865
6267
|
args: [`--no-sandbox`, `--disable-gpu`, `--disable-dev-shm-usage`]
|
|
@@ -5912,17 +6314,17 @@ function useQRService() {
|
|
|
5912
6314
|
await browser.close();
|
|
5913
6315
|
return imageBuffer;
|
|
5914
6316
|
} catch (error) {
|
|
5915
|
-
|
|
6317
|
+
logger34.log({
|
|
5916
6318
|
level: "error",
|
|
5917
6319
|
message: `Failed to generate QR image: ${error.message}`
|
|
5918
6320
|
});
|
|
5919
6321
|
throw error;
|
|
5920
6322
|
}
|
|
5921
6323
|
}
|
|
5922
|
-
async function generateQRPDF(qrUrl, title) {
|
|
6324
|
+
async function generateQRPDF(qrUrl, title, subtitle) {
|
|
5923
6325
|
try {
|
|
5924
6326
|
const qrDataUrl = await generateQRDataUrl(qrUrl);
|
|
5925
|
-
const browser = await
|
|
6327
|
+
const browser = await launch2({
|
|
5926
6328
|
headless: true,
|
|
5927
6329
|
executablePath: process.env.CHROME_BINARY,
|
|
5928
6330
|
args: [`--no-sandbox`, `--disable-gpu`, `--disable-dev-shm-usage`]
|
|
@@ -5933,6 +6335,7 @@ function useQRService() {
|
|
|
5933
6335
|
height: 1100
|
|
5934
6336
|
});
|
|
5935
6337
|
const escapedTitle = (title || "Cleaning Schedule QR Code").replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
6338
|
+
const escapedSubtitle = subtitle ? subtitle.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """) : "";
|
|
5936
6339
|
const html = `
|
|
5937
6340
|
<!DOCTYPE html>
|
|
5938
6341
|
<html>
|
|
@@ -5960,10 +6363,16 @@ function useQRService() {
|
|
|
5960
6363
|
padding: 0;
|
|
5961
6364
|
}
|
|
5962
6365
|
h1 {
|
|
5963
|
-
font-size:
|
|
6366
|
+
font-size: 38px;
|
|
5964
6367
|
color: #333;
|
|
5965
|
-
margin-bottom:
|
|
5966
|
-
font-weight:
|
|
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;
|
|
5967
6376
|
}
|
|
5968
6377
|
.qr-wrapper {
|
|
5969
6378
|
display: inline-block;
|
|
@@ -5998,6 +6407,7 @@ function useQRService() {
|
|
|
5998
6407
|
<body>
|
|
5999
6408
|
<div class="qr-container">
|
|
6000
6409
|
<h1>${escapedTitle}</h1>
|
|
6410
|
+
${escapedSubtitle ? `<p class="subtitle">${escapedSubtitle}</p>` : ""}
|
|
6001
6411
|
<div class="qr-wrapper">
|
|
6002
6412
|
<img id="qr-image" src="${qrDataUrl}" alt="QR Code" />
|
|
6003
6413
|
</div>
|
|
@@ -6035,7 +6445,7 @@ function useQRService() {
|
|
|
6035
6445
|
await browser.close();
|
|
6036
6446
|
return pdfBuffer;
|
|
6037
6447
|
} catch (error) {
|
|
6038
|
-
|
|
6448
|
+
logger34.log({
|
|
6039
6449
|
level: "error",
|
|
6040
6450
|
message: `Failed to generate QR PDF: ${error.message}`
|
|
6041
6451
|
});
|
|
@@ -6050,7 +6460,7 @@ function useQRService() {
|
|
|
6050
6460
|
|
|
6051
6461
|
// src/controllers/hygiene-qr.controller.ts
|
|
6052
6462
|
import Joi18 from "joi";
|
|
6053
|
-
import { BadRequestError as BadRequestError31, logger as
|
|
6463
|
+
import { BadRequestError as BadRequestError31, logger as logger35 } from "@7365admin1/node-server-utils";
|
|
6054
6464
|
function useQRController() {
|
|
6055
6465
|
const { generateQRImage: _generateQRImage, generateQRPDF: _generateQRPDF } = useQRService();
|
|
6056
6466
|
async function generateQR(req, res, next) {
|
|
@@ -6058,19 +6468,20 @@ function useQRController() {
|
|
|
6058
6468
|
url: Joi18.string().uri().required(),
|
|
6059
6469
|
filename: Joi18.string().optional().allow("", null),
|
|
6060
6470
|
title: Joi18.string().optional().allow("", null),
|
|
6471
|
+
subtitle: Joi18.string().optional().allow("", null),
|
|
6061
6472
|
download: Joi18.boolean().optional().default(false)
|
|
6062
6473
|
});
|
|
6063
6474
|
const query = { ...req.query };
|
|
6064
6475
|
const { error, value } = validation.validate(query);
|
|
6065
6476
|
if (error) {
|
|
6066
|
-
|
|
6477
|
+
logger35.log({ level: "error", message: error.message });
|
|
6067
6478
|
next(new BadRequestError31(error.message));
|
|
6068
6479
|
return;
|
|
6069
6480
|
}
|
|
6070
6481
|
try {
|
|
6071
|
-
const { url, filename, title, download } = value;
|
|
6482
|
+
const { url, filename, title, subtitle, download } = value;
|
|
6072
6483
|
if (download) {
|
|
6073
|
-
const pdfBuffer = await _generateQRPDF(url, title);
|
|
6484
|
+
const pdfBuffer = await _generateQRPDF(url, title, subtitle);
|
|
6074
6485
|
if (!pdfBuffer || pdfBuffer.length === 0) {
|
|
6075
6486
|
throw new Error("Generated QR PDF is empty or invalid.");
|
|
6076
6487
|
}
|
|
@@ -6099,7 +6510,7 @@ function useQRController() {
|
|
|
6099
6510
|
}
|
|
6100
6511
|
return;
|
|
6101
6512
|
} catch (error2) {
|
|
6102
|
-
|
|
6513
|
+
logger35.log({ level: "error", message: error2.message });
|
|
6103
6514
|
next(error2);
|
|
6104
6515
|
return;
|
|
6105
6516
|
}
|