@7365admin1/module-hygiene 4.30.2 → 4.31.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 +16 -0
- package/dist/index.d.ts +40 -10
- package/dist/index.js +753 -370
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +753 -370
- package/dist/index.mjs.map +1 -1
- package/package.json +2 -2
- package/test/area-import-unit-id.test.mjs +18 -0
- package/test/escape-regex.util.test.mjs +26 -0
- package/test/schedule-task-rules.test.mjs +185 -0
package/dist/index.mjs
CHANGED
|
@@ -843,21 +843,30 @@ function useHygieneDashboardController() {
|
|
|
843
843
|
}
|
|
844
844
|
|
|
845
845
|
// src/models/hygiene-area.model.ts
|
|
846
|
-
import
|
|
846
|
+
import Joi3 from "joi";
|
|
847
847
|
import { ObjectId as ObjectId2 } from "mongodb";
|
|
848
848
|
import { BadRequestError as BadRequestError3, logger as logger3 } from "@7365admin1/node-server-utils";
|
|
849
849
|
import { AppServiceType as AppServiceType2 } from "@7365admin1/core";
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
850
|
+
|
|
851
|
+
// src/utils/area-unit.util.ts
|
|
852
|
+
import Joi2 from "joi";
|
|
853
|
+
var areaUnitSchema = Joi2.object({
|
|
854
|
+
unit: Joi2.string().hex().required(),
|
|
855
|
+
name: Joi2.string().required()
|
|
856
|
+
});
|
|
857
|
+
function toAreaUnit(unit) {
|
|
858
|
+
return { unit: String(unit._id), name: unit.name };
|
|
859
|
+
}
|
|
860
|
+
|
|
861
|
+
// src/models/hygiene-area.model.ts
|
|
862
|
+
var areaSchema = Joi3.object({
|
|
863
|
+
site: Joi3.string().hex().required(),
|
|
864
|
+
serviceType: Joi3.string().valid(...Object.values(AppServiceType2)).required(),
|
|
865
|
+
name: Joi3.string().required(),
|
|
866
|
+
type: Joi3.string().valid(...allowedTypes).required(),
|
|
867
|
+
set: Joi3.number().min(0).optional(),
|
|
868
|
+
units: Joi3.array().items(
|
|
869
|
+
areaUnitSchema.optional()
|
|
861
870
|
).min(1).unique("unit", { ignoreUndefined: true }).messages({
|
|
862
871
|
"array.unique": "Duplicate area units are not allowed"
|
|
863
872
|
})
|
|
@@ -921,6 +930,11 @@ function capitalizeText(text) {
|
|
|
921
930
|
return text.toLowerCase().split(" ").map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join(" ");
|
|
922
931
|
}
|
|
923
932
|
|
|
933
|
+
// src/utils/escape-regex.util.ts
|
|
934
|
+
function escapeRegex(value) {
|
|
935
|
+
return String(value ?? "").replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
936
|
+
}
|
|
937
|
+
|
|
924
938
|
// src/repositories/hygiene-area.repository.ts
|
|
925
939
|
function useAreaRepo() {
|
|
926
940
|
const db = useAtlas2.getDb();
|
|
@@ -1017,7 +1031,7 @@ function useAreaRepo() {
|
|
|
1017
1031
|
cacheOptions.type = type;
|
|
1018
1032
|
}
|
|
1019
1033
|
if (search) {
|
|
1020
|
-
query.$or = [{ name: { $regex: search, $options: "i" } }];
|
|
1034
|
+
query.$or = [{ name: { $regex: escapeRegex(search), $options: "i" } }];
|
|
1021
1035
|
cacheOptions.search = search;
|
|
1022
1036
|
}
|
|
1023
1037
|
const cacheKey = makeCacheKey2(namespace_collection, cacheOptions);
|
|
@@ -1371,14 +1385,14 @@ import {
|
|
|
1371
1385
|
} from "@7365admin1/node-server-utils";
|
|
1372
1386
|
|
|
1373
1387
|
// src/models/hygiene-unit.model.ts
|
|
1374
|
-
import
|
|
1388
|
+
import Joi4 from "joi";
|
|
1375
1389
|
import { ObjectId as ObjectId4 } from "mongodb";
|
|
1376
1390
|
import { BadRequestError as BadRequestError5, logger as logger6 } from "@7365admin1/node-server-utils";
|
|
1377
1391
|
import { AppServiceType as AppServiceType3 } from "@7365admin1/core";
|
|
1378
|
-
var unitSchema =
|
|
1379
|
-
site:
|
|
1380
|
-
serviceType:
|
|
1381
|
-
name:
|
|
1392
|
+
var unitSchema = Joi4.object({
|
|
1393
|
+
site: Joi4.string().hex().required(),
|
|
1394
|
+
serviceType: Joi4.string().valid(...Object.values(AppServiceType3)).required(),
|
|
1395
|
+
name: Joi4.string().required()
|
|
1382
1396
|
});
|
|
1383
1397
|
function MUnit(value) {
|
|
1384
1398
|
const { error } = unitSchema.validate(value);
|
|
@@ -1491,7 +1505,7 @@ function useUnitRepository() {
|
|
|
1491
1505
|
throw new BadRequestError6("Invalid site ID format.");
|
|
1492
1506
|
}
|
|
1493
1507
|
if (search) {
|
|
1494
|
-
query.$or = [{ name: { $regex: search, $options: "i" } }];
|
|
1508
|
+
query.$or = [{ name: { $regex: escapeRegex(search), $options: "i" } }];
|
|
1495
1509
|
cacheOptions.search = search;
|
|
1496
1510
|
}
|
|
1497
1511
|
const cacheKey = makeCacheKey3(namespace_collection, cacheOptions);
|
|
@@ -1713,10 +1727,7 @@ function useAreaService() {
|
|
|
1713
1727
|
}
|
|
1714
1728
|
}
|
|
1715
1729
|
if (foundUnit) {
|
|
1716
|
-
areaUnits.push(
|
|
1717
|
-
unit: foundUnit._id,
|
|
1718
|
-
name: foundUnit.name
|
|
1719
|
-
});
|
|
1730
|
+
areaUnits.push(toAreaUnit(foundUnit));
|
|
1720
1731
|
}
|
|
1721
1732
|
}
|
|
1722
1733
|
if (areaUnits.length > 0) {
|
|
@@ -1813,7 +1824,7 @@ function useAreaService() {
|
|
|
1813
1824
|
}
|
|
1814
1825
|
|
|
1815
1826
|
// src/controllers/hygiene-area.controller.ts
|
|
1816
|
-
import
|
|
1827
|
+
import Joi5 from "joi";
|
|
1817
1828
|
import { BadRequestError as BadRequestError8, logger as logger9 } from "@7365admin1/node-server-utils";
|
|
1818
1829
|
import { AppServiceType as AppServiceType4 } from "@7365admin1/core";
|
|
1819
1830
|
|
|
@@ -1874,13 +1885,13 @@ function useAreaController() {
|
|
|
1874
1885
|
}
|
|
1875
1886
|
async function getAreas(req, res, next) {
|
|
1876
1887
|
const query = { ...req.query, ...req.params };
|
|
1877
|
-
const validation =
|
|
1878
|
-
page:
|
|
1879
|
-
limit:
|
|
1880
|
-
search:
|
|
1881
|
-
type:
|
|
1882
|
-
site:
|
|
1883
|
-
serviceType:
|
|
1888
|
+
const validation = Joi5.object({
|
|
1889
|
+
page: Joi5.number().min(1).optional().allow("", null),
|
|
1890
|
+
limit: Joi5.number().min(1).optional().allow("", null),
|
|
1891
|
+
search: Joi5.string().optional().allow("", null),
|
|
1892
|
+
type: Joi5.string().valid("all", ...allowedTypes).optional().allow("", null),
|
|
1893
|
+
site: Joi5.string().hex().required(),
|
|
1894
|
+
serviceType: Joi5.string().valid(...Object.values(AppServiceType4)).required()
|
|
1884
1895
|
});
|
|
1885
1896
|
const { error } = validation.validate(query);
|
|
1886
1897
|
if (error) {
|
|
@@ -1911,7 +1922,7 @@ function useAreaController() {
|
|
|
1911
1922
|
}
|
|
1912
1923
|
}
|
|
1913
1924
|
async function getAreaById(req, res, next) {
|
|
1914
|
-
const validation =
|
|
1925
|
+
const validation = Joi5.string().hex().required();
|
|
1915
1926
|
const _id = req.params.id;
|
|
1916
1927
|
const { error, value } = validation.validate(_id);
|
|
1917
1928
|
if (error) {
|
|
@@ -1931,15 +1942,15 @@ function useAreaController() {
|
|
|
1931
1942
|
}
|
|
1932
1943
|
async function updateArea(req, res, next) {
|
|
1933
1944
|
const payload = { id: req.params.id, ...req.body };
|
|
1934
|
-
const schema =
|
|
1935
|
-
id:
|
|
1936
|
-
name:
|
|
1937
|
-
type:
|
|
1938
|
-
set:
|
|
1939
|
-
units:
|
|
1940
|
-
|
|
1941
|
-
unit:
|
|
1942
|
-
name:
|
|
1945
|
+
const schema = Joi5.object({
|
|
1946
|
+
id: Joi5.string().hex().required(),
|
|
1947
|
+
name: Joi5.string().optional().allow("", null),
|
|
1948
|
+
type: Joi5.string().optional().allow("", null, ...allowedTypes),
|
|
1949
|
+
set: Joi5.number().min(0).optional(),
|
|
1950
|
+
units: Joi5.array().items(
|
|
1951
|
+
Joi5.object({
|
|
1952
|
+
unit: Joi5.string().hex().required(),
|
|
1953
|
+
name: Joi5.string().required()
|
|
1943
1954
|
}).required()
|
|
1944
1955
|
).min(1).unique("unit", { ignoreUndefined: true }).optional().messages({
|
|
1945
1956
|
"array.unique": "Duplicate area units are not allowed"
|
|
@@ -1964,7 +1975,7 @@ function useAreaController() {
|
|
|
1964
1975
|
}
|
|
1965
1976
|
async function deleteArea(req, res, next) {
|
|
1966
1977
|
const id = req.params.id;
|
|
1967
|
-
const validation =
|
|
1978
|
+
const validation = Joi5.string().hex().required();
|
|
1968
1979
|
const { error, value } = validation.validate(id);
|
|
1969
1980
|
if (error) {
|
|
1970
1981
|
logger9.log({ level: "error", message: error.message });
|
|
@@ -1987,9 +1998,9 @@ function useAreaController() {
|
|
|
1987
1998
|
return;
|
|
1988
1999
|
}
|
|
1989
2000
|
const query = { ...req.query, ...req.params };
|
|
1990
|
-
const validation =
|
|
1991
|
-
site:
|
|
1992
|
-
serviceType:
|
|
2001
|
+
const validation = Joi5.object({
|
|
2002
|
+
site: Joi5.string().hex().required(),
|
|
2003
|
+
serviceType: Joi5.string().valid(...Object.values(AppServiceType4)).required()
|
|
1993
2004
|
});
|
|
1994
2005
|
const { error, value } = validation.validate(query);
|
|
1995
2006
|
if (error) {
|
|
@@ -2010,9 +2021,9 @@ function useAreaController() {
|
|
|
2010
2021
|
}
|
|
2011
2022
|
async function exportAreas(req, res, next) {
|
|
2012
2023
|
const query = { ...req.query, ...req.params };
|
|
2013
|
-
const validation =
|
|
2014
|
-
site:
|
|
2015
|
-
serviceType:
|
|
2024
|
+
const validation = Joi5.object({
|
|
2025
|
+
site: Joi5.string().hex().required(),
|
|
2026
|
+
serviceType: Joi5.string().valid(...Object.values(AppServiceType4)).required()
|
|
2016
2027
|
});
|
|
2017
2028
|
const { error, value } = validation.validate(query);
|
|
2018
2029
|
if (error) {
|
|
@@ -2299,7 +2310,7 @@ function useUnitService() {
|
|
|
2299
2310
|
}
|
|
2300
2311
|
|
|
2301
2312
|
// src/controllers/hygiene-unit.controller.ts
|
|
2302
|
-
import
|
|
2313
|
+
import Joi6 from "joi";
|
|
2303
2314
|
import { BadRequestError as BadRequestError10, logger as logger12 } from "@7365admin1/node-server-utils";
|
|
2304
2315
|
import { AppServiceType as AppServiceType5 } from "@7365admin1/core";
|
|
2305
2316
|
function useUnitController() {
|
|
@@ -2330,12 +2341,12 @@ function useUnitController() {
|
|
|
2330
2341
|
}
|
|
2331
2342
|
async function getUnits(req, res, next) {
|
|
2332
2343
|
const query = { ...req.query, ...req.params };
|
|
2333
|
-
const validation =
|
|
2334
|
-
page:
|
|
2335
|
-
limit:
|
|
2336
|
-
search:
|
|
2337
|
-
site:
|
|
2338
|
-
serviceType:
|
|
2344
|
+
const validation = Joi6.object({
|
|
2345
|
+
page: Joi6.number().min(1).optional().allow("", null),
|
|
2346
|
+
limit: Joi6.number().min(1).optional().allow("", null),
|
|
2347
|
+
search: Joi6.string().optional().allow("", null),
|
|
2348
|
+
site: Joi6.string().hex().required(),
|
|
2349
|
+
serviceType: Joi6.string().valid(...Object.values(AppServiceType5)).required()
|
|
2339
2350
|
});
|
|
2340
2351
|
const { error } = validation.validate(query);
|
|
2341
2352
|
if (error) {
|
|
@@ -2366,9 +2377,9 @@ function useUnitController() {
|
|
|
2366
2377
|
}
|
|
2367
2378
|
async function updateUnit(req, res, next) {
|
|
2368
2379
|
const payload = { id: req.params.id, ...req.body };
|
|
2369
|
-
const validation =
|
|
2370
|
-
id:
|
|
2371
|
-
name:
|
|
2380
|
+
const validation = Joi6.object({
|
|
2381
|
+
id: Joi6.string().hex().required(),
|
|
2382
|
+
name: Joi6.string().required()
|
|
2372
2383
|
});
|
|
2373
2384
|
const { error } = validation.validate(payload);
|
|
2374
2385
|
if (error) {
|
|
@@ -2389,8 +2400,8 @@ function useUnitController() {
|
|
|
2389
2400
|
}
|
|
2390
2401
|
async function deleteUnit(req, res, next) {
|
|
2391
2402
|
const id = req.params.id;
|
|
2392
|
-
const validation =
|
|
2393
|
-
id:
|
|
2403
|
+
const validation = Joi6.object({
|
|
2404
|
+
id: Joi6.string().hex().required()
|
|
2394
2405
|
});
|
|
2395
2406
|
const { error, value } = validation.validate({ id });
|
|
2396
2407
|
if (error) {
|
|
@@ -2414,9 +2425,9 @@ function useUnitController() {
|
|
|
2414
2425
|
return;
|
|
2415
2426
|
}
|
|
2416
2427
|
const query = { ...req.query, ...req.params };
|
|
2417
|
-
const validation =
|
|
2418
|
-
site:
|
|
2419
|
-
serviceType:
|
|
2428
|
+
const validation = Joi6.object({
|
|
2429
|
+
site: Joi6.string().hex().required(),
|
|
2430
|
+
serviceType: Joi6.string().valid(...Object.values(AppServiceType5)).required()
|
|
2420
2431
|
});
|
|
2421
2432
|
const { error, value } = validation.validate(query);
|
|
2422
2433
|
if (error) {
|
|
@@ -2437,9 +2448,9 @@ function useUnitController() {
|
|
|
2437
2448
|
}
|
|
2438
2449
|
async function exportUnits(req, res, next) {
|
|
2439
2450
|
const query = { ...req.query, ...req.params };
|
|
2440
|
-
const validation =
|
|
2441
|
-
site:
|
|
2442
|
-
serviceType:
|
|
2451
|
+
const validation = Joi6.object({
|
|
2452
|
+
site: Joi6.string().hex().required(),
|
|
2453
|
+
serviceType: Joi6.string().valid(...Object.values(AppServiceType5)).required()
|
|
2443
2454
|
});
|
|
2444
2455
|
const { error, value } = validation.validate(query);
|
|
2445
2456
|
if (error) {
|
|
@@ -2482,15 +2493,15 @@ function useUnitController() {
|
|
|
2482
2493
|
}
|
|
2483
2494
|
|
|
2484
2495
|
// src/models/hygiene-parent-checklist.model.ts
|
|
2485
|
-
import
|
|
2496
|
+
import Joi7 from "joi";
|
|
2486
2497
|
import { ObjectId as ObjectId6 } from "mongodb";
|
|
2487
2498
|
import { BadRequestError as BadRequestError11, logger as logger13 } from "@7365admin1/node-server-utils";
|
|
2488
2499
|
import { AppServiceType as AppServiceType6 } from "@7365admin1/core";
|
|
2489
|
-
var parentChecklistSchema =
|
|
2490
|
-
createdAt:
|
|
2491
|
-
site:
|
|
2492
|
-
serviceType:
|
|
2493
|
-
dateStr:
|
|
2500
|
+
var parentChecklistSchema = Joi7.object({
|
|
2501
|
+
createdAt: Joi7.alternatives().try(Joi7.date(), Joi7.string()).optional().allow("", null),
|
|
2502
|
+
site: Joi7.string().hex().required(),
|
|
2503
|
+
serviceType: Joi7.string().valid(...Object.values(AppServiceType6)).required(),
|
|
2504
|
+
dateStr: Joi7.string().optional().allow("", null)
|
|
2494
2505
|
});
|
|
2495
2506
|
function MParentChecklist(value) {
|
|
2496
2507
|
const { error } = parentChecklistSchema.validate(value);
|
|
@@ -3071,7 +3082,7 @@ function useParentChecklistRepo() {
|
|
|
3071
3082
|
}
|
|
3072
3083
|
|
|
3073
3084
|
// src/controllers/hygiene-parent-checklist.controller.ts
|
|
3074
|
-
import
|
|
3085
|
+
import Joi8 from "joi";
|
|
3075
3086
|
import { BadRequestError as BadRequestError13, logger as logger15 } from "@7365admin1/node-server-utils";
|
|
3076
3087
|
import { AppServiceType as AppServiceType8 } from "@7365admin1/core";
|
|
3077
3088
|
function useParentChecklistController() {
|
|
@@ -3081,10 +3092,10 @@ function useParentChecklistController() {
|
|
|
3081
3092
|
} = useParentChecklistRepo();
|
|
3082
3093
|
async function createParentChecklist(req, res, next) {
|
|
3083
3094
|
const payload = req.body;
|
|
3084
|
-
const validation =
|
|
3085
|
-
createdAt:
|
|
3086
|
-
site:
|
|
3087
|
-
serviceType:
|
|
3095
|
+
const validation = Joi8.object({
|
|
3096
|
+
createdAt: Joi8.alternatives().try(Joi8.date(), Joi8.string()).optional().allow("", null),
|
|
3097
|
+
site: Joi8.string().hex().required(),
|
|
3098
|
+
serviceType: Joi8.string().valid(...Object.values(AppServiceType8)).optional().allow("", null)
|
|
3088
3099
|
});
|
|
3089
3100
|
const { error } = validation.validate(payload);
|
|
3090
3101
|
if (error) {
|
|
@@ -3104,15 +3115,15 @@ function useParentChecklistController() {
|
|
|
3104
3115
|
}
|
|
3105
3116
|
async function getAllParentChecklist(req, res, next) {
|
|
3106
3117
|
const query = { ...req.query, ...req.params };
|
|
3107
|
-
const validation =
|
|
3108
|
-
page:
|
|
3109
|
-
limit:
|
|
3110
|
-
search:
|
|
3111
|
-
site:
|
|
3112
|
-
serviceType:
|
|
3113
|
-
startDate:
|
|
3114
|
-
endDate:
|
|
3115
|
-
status:
|
|
3118
|
+
const validation = Joi8.object({
|
|
3119
|
+
page: Joi8.number().min(1).optional().allow("", null),
|
|
3120
|
+
limit: Joi8.number().min(1).optional().allow("", null),
|
|
3121
|
+
search: Joi8.string().optional().allow("", null),
|
|
3122
|
+
site: Joi8.string().hex().required(),
|
|
3123
|
+
serviceType: Joi8.string().valid(...Object.values(AppServiceType8)).required(),
|
|
3124
|
+
startDate: Joi8.alternatives().try(Joi8.date(), Joi8.string()).optional().allow("", null),
|
|
3125
|
+
endDate: Joi8.alternatives().try(Joi8.date(), Joi8.string()).optional().allow("", null),
|
|
3126
|
+
status: Joi8.string().valid(...allowedStatus, "all").optional().allow("", null)
|
|
3116
3127
|
});
|
|
3117
3128
|
const { error } = validation.validate(query);
|
|
3118
3129
|
if (error) {
|
|
@@ -3154,28 +3165,28 @@ function useParentChecklistController() {
|
|
|
3154
3165
|
}
|
|
3155
3166
|
|
|
3156
3167
|
// src/models/hygiene-area-checklist.model.ts
|
|
3157
|
-
import
|
|
3168
|
+
import Joi9 from "joi";
|
|
3158
3169
|
import { ObjectId as ObjectId8 } from "mongodb";
|
|
3159
3170
|
import { BadRequestError as BadRequestError14, logger as logger16 } from "@7365admin1/node-server-utils";
|
|
3160
3171
|
import { AppServiceType as AppServiceType9 } from "@7365admin1/core";
|
|
3161
3172
|
var allowedChecklistStatus = ["open", "completed", "closed"];
|
|
3162
|
-
var areaChecklistSchema =
|
|
3163
|
-
schedule:
|
|
3164
|
-
serviceType:
|
|
3165
|
-
area:
|
|
3166
|
-
name:
|
|
3167
|
-
type:
|
|
3168
|
-
checklist:
|
|
3169
|
-
|
|
3170
|
-
set:
|
|
3171
|
-
units:
|
|
3172
|
-
|
|
3173
|
-
unit:
|
|
3174
|
-
name:
|
|
3173
|
+
var areaChecklistSchema = Joi9.object({
|
|
3174
|
+
schedule: Joi9.string().hex().required(),
|
|
3175
|
+
serviceType: Joi9.string().valid(...Object.values(AppServiceType9)).required(),
|
|
3176
|
+
area: Joi9.string().hex().required(),
|
|
3177
|
+
name: Joi9.string().required(),
|
|
3178
|
+
type: Joi9.string().valid(...allowedTypes).required(),
|
|
3179
|
+
checklist: Joi9.array().items(
|
|
3180
|
+
Joi9.object({
|
|
3181
|
+
set: Joi9.number().min(0).required(),
|
|
3182
|
+
units: Joi9.array().items(
|
|
3183
|
+
Joi9.object({
|
|
3184
|
+
unit: Joi9.string().hex().required(),
|
|
3185
|
+
name: Joi9.string().required()
|
|
3175
3186
|
}).required()
|
|
3176
3187
|
).min(1).required(),
|
|
3177
|
-
isScheduleTask:
|
|
3178
|
-
scheduleTaskId:
|
|
3188
|
+
isScheduleTask: Joi9.boolean().optional().default(false),
|
|
3189
|
+
scheduleTaskId: Joi9.string().hex().optional().allow(null)
|
|
3179
3190
|
}).required()
|
|
3180
3191
|
).optional().default([])
|
|
3181
3192
|
});
|
|
@@ -4425,6 +4436,265 @@ function useAreaChecklistRepo() {
|
|
|
4425
4436
|
};
|
|
4426
4437
|
}
|
|
4427
4438
|
|
|
4439
|
+
// src/utils/schedule-task-rules.util.ts
|
|
4440
|
+
import Joi10 from "joi";
|
|
4441
|
+
var REPEATS = ["none", "daily", "weekdays", "weekly", "monthly"];
|
|
4442
|
+
var SGT_MS = 8 * 60 * 60 * 1e3;
|
|
4443
|
+
var DATE_RE = /^\d{4}-\d{2}-\d{2}$/;
|
|
4444
|
+
var TIME_RE = /^([0-1]\d|2[0-3]):([0-5]\d)$/;
|
|
4445
|
+
var pad = (n) => String(n).padStart(2, "0");
|
|
4446
|
+
function sgtParts(now) {
|
|
4447
|
+
const s = new Date(now.getTime() + SGT_MS);
|
|
4448
|
+
const date = `${s.getUTCFullYear()}-${pad(s.getUTCMonth() + 1)}-${pad(s.getUTCDate())}`;
|
|
4449
|
+
return { date, minutes: s.getUTCHours() * 60 + s.getUTCMinutes(), weekday: s.getUTCDay() };
|
|
4450
|
+
}
|
|
4451
|
+
function isRealDate(value) {
|
|
4452
|
+
if (typeof value !== "string" || !DATE_RE.test(value))
|
|
4453
|
+
return false;
|
|
4454
|
+
const [y, m, d] = value.split("-").map(Number);
|
|
4455
|
+
const t = new Date(Date.UTC(y, m - 1, d));
|
|
4456
|
+
return t.getUTCFullYear() === y && t.getUTCMonth() === m - 1 && t.getUTCDate() === d;
|
|
4457
|
+
}
|
|
4458
|
+
var toMinutes = (time) => {
|
|
4459
|
+
const [h, m] = time.split(":").map(Number);
|
|
4460
|
+
return h * 60 + m;
|
|
4461
|
+
};
|
|
4462
|
+
var weekdayOf = (date) => {
|
|
4463
|
+
const [y, m, d] = date.split("-").map(Number);
|
|
4464
|
+
return new Date(Date.UTC(y, m - 1, d)).getUTCDay();
|
|
4465
|
+
};
|
|
4466
|
+
var daysInMonth = (date) => {
|
|
4467
|
+
const [y, m] = date.split("-").map(Number);
|
|
4468
|
+
return new Date(Date.UTC(y, m, 0)).getUTCDate();
|
|
4469
|
+
};
|
|
4470
|
+
var addDays = (date, n) => {
|
|
4471
|
+
const [y, m, d] = date.split("-").map(Number);
|
|
4472
|
+
const t = new Date(Date.UTC(y, m - 1, d + n));
|
|
4473
|
+
return `${t.getUTCFullYear()}-${pad(t.getUTCMonth() + 1)}-${pad(t.getUTCDate())}`;
|
|
4474
|
+
};
|
|
4475
|
+
var isRepeating = (task) => Boolean(task.repeat) && task.repeat !== "none";
|
|
4476
|
+
function occursOn(task, date) {
|
|
4477
|
+
if (task.endDate && date > task.endDate)
|
|
4478
|
+
return false;
|
|
4479
|
+
if (!isRepeating(task))
|
|
4480
|
+
return Array.isArray(task.dates) && task.dates.includes(date);
|
|
4481
|
+
if (task.startDate && date < task.startDate)
|
|
4482
|
+
return false;
|
|
4483
|
+
const weekday = weekdayOf(date);
|
|
4484
|
+
switch (task.repeat) {
|
|
4485
|
+
case "daily":
|
|
4486
|
+
return true;
|
|
4487
|
+
case "weekdays":
|
|
4488
|
+
return weekday >= 1 && weekday <= 5;
|
|
4489
|
+
case "weekly":
|
|
4490
|
+
return Array.isArray(task.daysOfWeek) && task.daysOfWeek.includes(weekday);
|
|
4491
|
+
case "monthly": {
|
|
4492
|
+
const wanted = task.dayOfMonth ?? (task.startDate ? Number(task.startDate.slice(8, 10)) : 1);
|
|
4493
|
+
return Number(date.slice(8, 10)) === Math.min(wanted, daysInMonth(date));
|
|
4494
|
+
}
|
|
4495
|
+
default:
|
|
4496
|
+
return false;
|
|
4497
|
+
}
|
|
4498
|
+
}
|
|
4499
|
+
function savedTodayAt(task, today) {
|
|
4500
|
+
const stamp = task.updatedAt || task.createdAt;
|
|
4501
|
+
if (!stamp)
|
|
4502
|
+
return null;
|
|
4503
|
+
const saved = new Date(stamp);
|
|
4504
|
+
if (Number.isNaN(saved.getTime()))
|
|
4505
|
+
return null;
|
|
4506
|
+
const p = sgtParts(saved);
|
|
4507
|
+
return p.date === today ? p.minutes : null;
|
|
4508
|
+
}
|
|
4509
|
+
function shouldRunNow(task, now = /* @__PURE__ */ new Date()) {
|
|
4510
|
+
if (task.paused || !TIME_RE.test(task.time ?? ""))
|
|
4511
|
+
return false;
|
|
4512
|
+
const { date, minutes } = sgtParts(now);
|
|
4513
|
+
if (task.lastRunDate === date || !occursOn(task, date))
|
|
4514
|
+
return false;
|
|
4515
|
+
const due = toMinutes(task.time);
|
|
4516
|
+
if (minutes < due)
|
|
4517
|
+
return false;
|
|
4518
|
+
const saved = savedTodayAt(task, date);
|
|
4519
|
+
return !(saved !== null && saved >= due);
|
|
4520
|
+
}
|
|
4521
|
+
var iso = (date, time) => `${date}T${time}:00+08:00`;
|
|
4522
|
+
function upcoming(task, now) {
|
|
4523
|
+
if (typeof task.time !== "string" || !TIME_RE.test(task.time))
|
|
4524
|
+
return null;
|
|
4525
|
+
const { date: today, minutes } = sgtParts(now);
|
|
4526
|
+
const due = toMinutes(task.time);
|
|
4527
|
+
if (occursOn(task, today) && (minutes < due || shouldRunNow({ ...task, paused: false }, now))) {
|
|
4528
|
+
return iso(today, task.time);
|
|
4529
|
+
}
|
|
4530
|
+
if (!isRepeating(task)) {
|
|
4531
|
+
const later = (task.dates ?? []).filter((d) => d > today && (!task.endDate || d <= task.endDate)).sort();
|
|
4532
|
+
return later.length ? iso(later[0], task.time) : null;
|
|
4533
|
+
}
|
|
4534
|
+
const from = task.startDate && task.startDate > today ? task.startDate : today;
|
|
4535
|
+
for (let i = from === today ? 1 : 0; i <= 400; i++) {
|
|
4536
|
+
const d = addDays(from, i);
|
|
4537
|
+
if (task.endDate && d > task.endDate)
|
|
4538
|
+
return null;
|
|
4539
|
+
if (occursOn(task, d))
|
|
4540
|
+
return iso(d, task.time);
|
|
4541
|
+
}
|
|
4542
|
+
return null;
|
|
4543
|
+
}
|
|
4544
|
+
function nextRun(task, now = /* @__PURE__ */ new Date()) {
|
|
4545
|
+
return task.paused ? null : upcoming(task, now);
|
|
4546
|
+
}
|
|
4547
|
+
function scheduleState(task, now = /* @__PURE__ */ new Date()) {
|
|
4548
|
+
if (task.paused)
|
|
4549
|
+
return "paused";
|
|
4550
|
+
return upcoming(task, now) ? "active" : "ended";
|
|
4551
|
+
}
|
|
4552
|
+
function lastRun(task) {
|
|
4553
|
+
return task.lastRunDate && TIME_RE.test(task.time ?? "") ? iso(task.lastRunDate, task.time) : null;
|
|
4554
|
+
}
|
|
4555
|
+
function scheduleDisplay(task, now = /* @__PURE__ */ new Date()) {
|
|
4556
|
+
try {
|
|
4557
|
+
return {
|
|
4558
|
+
nextRun: nextRun(task, now),
|
|
4559
|
+
lastRun: lastRun(task),
|
|
4560
|
+
scheduleStatus: scheduleState(task, now)
|
|
4561
|
+
};
|
|
4562
|
+
} catch {
|
|
4563
|
+
return { nextRun: null, lastRun: null, scheduleStatus: null };
|
|
4564
|
+
}
|
|
4565
|
+
}
|
|
4566
|
+
function validateScheduleRules(task, now = /* @__PURE__ */ new Date()) {
|
|
4567
|
+
const repeating = isRepeating(task);
|
|
4568
|
+
const { date: today, minutes } = sgtParts(now);
|
|
4569
|
+
if (repeating) {
|
|
4570
|
+
if (!task.startDate)
|
|
4571
|
+
return "Please choose a start date for a repeating schedule.";
|
|
4572
|
+
if (task.repeat === "weekly" && !(task.daysOfWeek && task.daysOfWeek.length)) {
|
|
4573
|
+
return "Pick at least one weekday for a weekly repeat.";
|
|
4574
|
+
}
|
|
4575
|
+
}
|
|
4576
|
+
const start = repeating ? task.startDate : [...task.dates ?? []].sort()[0];
|
|
4577
|
+
if (task.endDate && start && start > task.endDate) {
|
|
4578
|
+
return "The end date must be on or after the start date.";
|
|
4579
|
+
}
|
|
4580
|
+
if (task.endDate && task.endDate < today)
|
|
4581
|
+
return "The end date has already passed.";
|
|
4582
|
+
if (!repeating) {
|
|
4583
|
+
const dates = (task.dates ?? []).filter((d) => !task.endDate || d <= task.endDate);
|
|
4584
|
+
if (dates.length && dates.every((d) => d < today)) {
|
|
4585
|
+
return "All the dates you picked have already passed. Pick today or a later date.";
|
|
4586
|
+
}
|
|
4587
|
+
if (dates.length && dates.every((d) => d <= today) && dates.includes(today) && TIME_RE.test(task.time) && toMinutes(task.time) <= minutes) {
|
|
4588
|
+
return "That time has already passed today. Pick a later time or another date.";
|
|
4589
|
+
}
|
|
4590
|
+
}
|
|
4591
|
+
if (!upcoming({ ...task, paused: false, updatedAt: now }, now)) {
|
|
4592
|
+
return "This schedule would never run. Check the dates, end date and time.";
|
|
4593
|
+
}
|
|
4594
|
+
return null;
|
|
4595
|
+
}
|
|
4596
|
+
var scheduleDate = Joi10.string().pattern(DATE_RE).custom((value, helpers) => isRealDate(value) ? value : helpers.error("date.invalid")).messages({
|
|
4597
|
+
"string.pattern.base": "Dates must look like 2026-10-21.",
|
|
4598
|
+
"string.empty": "Please choose a date.",
|
|
4599
|
+
"date.invalid": "That date does not exist. Dates must look like 2026-10-21.",
|
|
4600
|
+
"string.base": "Dates must look like 2026-10-21."
|
|
4601
|
+
});
|
|
4602
|
+
var REPEATING = Joi10.valid("daily", "weekdays", "weekly", "monthly").required();
|
|
4603
|
+
var scheduleTaskFields = {
|
|
4604
|
+
title: Joi10.string().trim().required().messages({
|
|
4605
|
+
"any.required": "Please enter a title.",
|
|
4606
|
+
"string.empty": "Please enter a title.",
|
|
4607
|
+
"string.base": "Please enter a title."
|
|
4608
|
+
}),
|
|
4609
|
+
time: Joi10.string().pattern(TIME_RE).required().messages({
|
|
4610
|
+
"any.required": "Please choose a time.",
|
|
4611
|
+
"string.empty": "Please choose a time.",
|
|
4612
|
+
"string.pattern.base": "Time must be in 24-hour format like 08:30.",
|
|
4613
|
+
"string.base": "Time must be in 24-hour format like 08:30."
|
|
4614
|
+
}),
|
|
4615
|
+
dates: Joi10.when("repeat", {
|
|
4616
|
+
is: REPEATING,
|
|
4617
|
+
then: Joi10.array().items(scheduleDate).optional().default([]),
|
|
4618
|
+
otherwise: Joi10.array().min(1).items(scheduleDate).required()
|
|
4619
|
+
}).messages({
|
|
4620
|
+
"any.required": "Pick at least one date, or choose a repeat.",
|
|
4621
|
+
"array.min": "Pick at least one date, or choose a repeat.",
|
|
4622
|
+
"array.base": "Dates must be a list of dates."
|
|
4623
|
+
}),
|
|
4624
|
+
description: Joi10.string().optional().allow("", null),
|
|
4625
|
+
areas: Joi10.array().min(1).items(
|
|
4626
|
+
Joi10.object({
|
|
4627
|
+
name: Joi10.string().required(),
|
|
4628
|
+
value: Joi10.any().required()
|
|
4629
|
+
})
|
|
4630
|
+
).required().messages({
|
|
4631
|
+
"any.required": "Pick at least one area.",
|
|
4632
|
+
"array.min": "Pick at least one area.",
|
|
4633
|
+
"array.base": "Pick at least one area."
|
|
4634
|
+
}),
|
|
4635
|
+
repeat: Joi10.string().valid(...REPEATS).optional().allow("", null).messages({ "any.only": "Repeat must be one of: none, daily, weekdays, weekly, monthly." }),
|
|
4636
|
+
daysOfWeek: Joi10.array().items(Joi10.number().integer().min(0).max(6)).unique().optional().allow(null).messages({
|
|
4637
|
+
"number.base": "Weekdays must be numbers from 0 (Sunday) to 6 (Saturday).",
|
|
4638
|
+
"number.min": "Weekdays must be numbers from 0 (Sunday) to 6 (Saturday).",
|
|
4639
|
+
"number.max": "Weekdays must be numbers from 0 (Sunday) to 6 (Saturday).",
|
|
4640
|
+
"number.integer": "Weekdays must be numbers from 0 (Sunday) to 6 (Saturday).",
|
|
4641
|
+
"array.unique": "Each weekday can only be picked once."
|
|
4642
|
+
}),
|
|
4643
|
+
dayOfMonth: Joi10.number().integer().min(1).max(31).optional().allow(null).messages({ "number.base": "Day of the month must be 1 to 31.", "number.min": "Day of the month must be 1 to 31.", "number.max": "Day of the month must be 1 to 31." }),
|
|
4644
|
+
startDate: scheduleDate.optional().allow("", null),
|
|
4645
|
+
endDate: scheduleDate.optional().allow("", null),
|
|
4646
|
+
paused: Joi10.boolean().optional().allow(null).messages({ "boolean.base": "Paused must be true or false." })
|
|
4647
|
+
};
|
|
4648
|
+
var OPTIONAL_KEYS = ["repeat", "daysOfWeek", "dayOfMonth", "startDate", "endDate", "paused"];
|
|
4649
|
+
var isBlank = (v) => v === "" || v === null;
|
|
4650
|
+
function cleanScheduleInput(value) {
|
|
4651
|
+
const out = { ...value };
|
|
4652
|
+
for (const key of OPTIONAL_KEYS)
|
|
4653
|
+
if (isBlank(out[key]))
|
|
4654
|
+
delete out[key];
|
|
4655
|
+
if (out.repeat === "none") {
|
|
4656
|
+
delete out.daysOfWeek;
|
|
4657
|
+
delete out.dayOfMonth;
|
|
4658
|
+
delete out.startDate;
|
|
4659
|
+
}
|
|
4660
|
+
return out;
|
|
4661
|
+
}
|
|
4662
|
+
var TIMING_KEYS = ["time", "dates", "repeat", "daysOfWeek", "dayOfMonth", "startDate", "endDate"];
|
|
4663
|
+
function splitScheduleUpdate(payload) {
|
|
4664
|
+
const set = {};
|
|
4665
|
+
const unset = [];
|
|
4666
|
+
for (const [key, value] of Object.entries(payload)) {
|
|
4667
|
+
if (OPTIONAL_KEYS.includes(key) && isBlank(value))
|
|
4668
|
+
unset.push(key);
|
|
4669
|
+
else if (value !== void 0)
|
|
4670
|
+
set[key] = value;
|
|
4671
|
+
}
|
|
4672
|
+
if (set.repeat === "none") {
|
|
4673
|
+
for (const key of ["daysOfWeek", "dayOfMonth", "startDate"]) {
|
|
4674
|
+
if (!unset.includes(key))
|
|
4675
|
+
unset.push(key);
|
|
4676
|
+
delete set[key];
|
|
4677
|
+
}
|
|
4678
|
+
}
|
|
4679
|
+
return { set, unset };
|
|
4680
|
+
}
|
|
4681
|
+
function regularSetsMissing(checklist, setCount) {
|
|
4682
|
+
const regular = (checklist ?? []).filter((c) => c.isScheduleTask !== true).length;
|
|
4683
|
+
return Math.max(0, setCount - regular);
|
|
4684
|
+
}
|
|
4685
|
+
var normTiming = (t) => ({
|
|
4686
|
+
time: t.time ?? null,
|
|
4687
|
+
dates: [...Array.isArray(t.dates) ? t.dates : []].sort(),
|
|
4688
|
+
repeat: !t.repeat || t.repeat === "none" ? "none" : t.repeat,
|
|
4689
|
+
daysOfWeek: [...Array.isArray(t.daysOfWeek) ? t.daysOfWeek : []].sort(),
|
|
4690
|
+
dayOfMonth: t.dayOfMonth ?? null,
|
|
4691
|
+
startDate: t.startDate || null,
|
|
4692
|
+
endDate: t.endDate || null
|
|
4693
|
+
});
|
|
4694
|
+
function timingChanged(stored, after) {
|
|
4695
|
+
return JSON.stringify(normTiming(stored)) !== JSON.stringify(normTiming(after));
|
|
4696
|
+
}
|
|
4697
|
+
|
|
4428
4698
|
// src/services/hygiene-area-checklist.service.ts
|
|
4429
4699
|
function useAreaChecklistService() {
|
|
4430
4700
|
const { getAreasForChecklist } = useAreaRepo();
|
|
@@ -4492,10 +4762,10 @@ function useAreaChecklistService() {
|
|
|
4492
4762
|
`Area checklist for area ${area.name} exists but has no auto-gen sets. Inserting auto-gen sets.`
|
|
4493
4763
|
);
|
|
4494
4764
|
const setCount2 = Number(area.set) || 1;
|
|
4495
|
-
const
|
|
4496
|
-
if (
|
|
4765
|
+
const setsToAdd = regularSetsMissing(existing.checklist, setCount2);
|
|
4766
|
+
if (setsToAdd <= 0) {
|
|
4497
4767
|
logger18.info(
|
|
4498
|
-
`Area checklist for area ${area.name} already has ${
|
|
4768
|
+
`Area checklist for area ${area.name} already has its ${setCount2} regular set(s). Skipping auto-gen sets.`
|
|
4499
4769
|
);
|
|
4500
4770
|
return existing._id;
|
|
4501
4771
|
}
|
|
@@ -4503,7 +4773,6 @@ function useAreaChecklistService() {
|
|
|
4503
4773
|
(max, c) => Math.max(max, c.set ?? 0),
|
|
4504
4774
|
0
|
|
4505
4775
|
) ?? 0;
|
|
4506
|
-
const setsToAdd = setCount2 - totalExistingSets;
|
|
4507
4776
|
const autoGenSets = Array.from(
|
|
4508
4777
|
{ length: setsToAdd },
|
|
4509
4778
|
(_, index) => ({
|
|
@@ -4664,7 +4933,7 @@ function useAreaChecklistService() {
|
|
|
4664
4933
|
}
|
|
4665
4934
|
|
|
4666
4935
|
// src/controllers/hygiene-area-checklist.controller.ts
|
|
4667
|
-
import
|
|
4936
|
+
import Joi11 from "joi";
|
|
4668
4937
|
import {
|
|
4669
4938
|
BadRequestError as BadRequestError16,
|
|
4670
4939
|
InternalServerError as InternalServerError7,
|
|
@@ -4909,9 +5178,9 @@ function useAreaChecklistController() {
|
|
|
4909
5178
|
site: req.params.site,
|
|
4910
5179
|
schedule: req.params.schedule
|
|
4911
5180
|
};
|
|
4912
|
-
const validation =
|
|
4913
|
-
site:
|
|
4914
|
-
schedule:
|
|
5181
|
+
const validation = Joi11.object({
|
|
5182
|
+
site: Joi11.string().hex().required(),
|
|
5183
|
+
schedule: Joi11.string().hex().required()
|
|
4915
5184
|
});
|
|
4916
5185
|
const { error, value } = validation.validate(payload);
|
|
4917
5186
|
if (error) {
|
|
@@ -4931,14 +5200,14 @@ function useAreaChecklistController() {
|
|
|
4931
5200
|
}
|
|
4932
5201
|
async function getAllAreaChecklist(req, res, next) {
|
|
4933
5202
|
const query = { ...req.query, ...req.params };
|
|
4934
|
-
const validation =
|
|
4935
|
-
page:
|
|
4936
|
-
limit:
|
|
4937
|
-
search:
|
|
4938
|
-
type:
|
|
4939
|
-
status:
|
|
4940
|
-
schedule:
|
|
4941
|
-
serviceType:
|
|
5203
|
+
const validation = Joi11.object({
|
|
5204
|
+
page: Joi11.number().min(1).optional().allow("", null),
|
|
5205
|
+
limit: Joi11.number().min(1).optional().allow("", null),
|
|
5206
|
+
search: Joi11.string().optional().allow("", null),
|
|
5207
|
+
type: Joi11.string().valid(...allowedTypes, "all").optional().allow("", null),
|
|
5208
|
+
status: Joi11.string().valid(...allowedStatus, "all").optional().allow("", null),
|
|
5209
|
+
schedule: Joi11.string().hex().required(),
|
|
5210
|
+
serviceType: Joi11.string().valid(...Object.values(AppServiceType10)).required()
|
|
4942
5211
|
});
|
|
4943
5212
|
const { error } = validation.validate(query);
|
|
4944
5213
|
if (error) {
|
|
@@ -4973,14 +5242,14 @@ function useAreaChecklistController() {
|
|
|
4973
5242
|
}
|
|
4974
5243
|
async function getAreaChecklistHistory(req, res, next) {
|
|
4975
5244
|
const query = { ...req.query, ...req.params };
|
|
4976
|
-
const validation =
|
|
4977
|
-
page:
|
|
4978
|
-
limit:
|
|
4979
|
-
search:
|
|
4980
|
-
type:
|
|
4981
|
-
schedule:
|
|
4982
|
-
status:
|
|
4983
|
-
createdAt:
|
|
5245
|
+
const validation = Joi11.object({
|
|
5246
|
+
page: Joi11.number().min(1).optional().allow("", null),
|
|
5247
|
+
limit: Joi11.number().min(1).optional().allow("", null),
|
|
5248
|
+
search: Joi11.string().optional().allow("", null),
|
|
5249
|
+
type: Joi11.string().optional().allow("", ...allowedTypes),
|
|
5250
|
+
schedule: Joi11.string().hex().required(),
|
|
5251
|
+
status: Joi11.string().allow("", null, ...allowedStatus),
|
|
5252
|
+
createdAt: Joi11.alternatives().try(Joi11.date(), Joi11.string()).optional().allow("", null)
|
|
4984
5253
|
});
|
|
4985
5254
|
const { error } = validation.validate(query);
|
|
4986
5255
|
if (error) {
|
|
@@ -5014,7 +5283,7 @@ function useAreaChecklistController() {
|
|
|
5014
5283
|
}
|
|
5015
5284
|
}
|
|
5016
5285
|
async function getAreaChecklistHistoryDetails(req, res, next) {
|
|
5017
|
-
const validation =
|
|
5286
|
+
const validation = Joi11.string().hex().required();
|
|
5018
5287
|
const _id = req.params.id;
|
|
5019
5288
|
const { error, value } = validation.validate(_id);
|
|
5020
5289
|
if (error) {
|
|
@@ -5034,12 +5303,12 @@ function useAreaChecklistController() {
|
|
|
5034
5303
|
}
|
|
5035
5304
|
async function getAreaChecklistUnits(req, res, next) {
|
|
5036
5305
|
const query = { ...req.query, ...req.params };
|
|
5037
|
-
const validation =
|
|
5038
|
-
page:
|
|
5039
|
-
limit:
|
|
5040
|
-
search:
|
|
5041
|
-
serviceType:
|
|
5042
|
-
id:
|
|
5306
|
+
const validation = Joi11.object({
|
|
5307
|
+
page: Joi11.number().min(1).optional().allow("", null),
|
|
5308
|
+
limit: Joi11.number().min(1).optional().allow("", null),
|
|
5309
|
+
search: Joi11.string().optional().allow("", null),
|
|
5310
|
+
serviceType: Joi11.string().valid(...Object.values(AppServiceType10)).required(),
|
|
5311
|
+
id: Joi11.string().hex().required()
|
|
5043
5312
|
});
|
|
5044
5313
|
const { error } = validation.validate(query);
|
|
5045
5314
|
if (error) {
|
|
@@ -5089,21 +5358,21 @@ function useAreaChecklistController() {
|
|
|
5089
5358
|
...decisionValues,
|
|
5090
5359
|
completedBy
|
|
5091
5360
|
};
|
|
5092
|
-
const validation =
|
|
5093
|
-
id:
|
|
5094
|
-
set:
|
|
5095
|
-
unit:
|
|
5096
|
-
decision:
|
|
5097
|
-
approve:
|
|
5098
|
-
reject:
|
|
5099
|
-
remarks:
|
|
5361
|
+
const validation = Joi11.object({
|
|
5362
|
+
id: Joi11.string().hex().required(),
|
|
5363
|
+
set: Joi11.number().integer().min(1).required(),
|
|
5364
|
+
unit: Joi11.string().hex().required(),
|
|
5365
|
+
decision: Joi11.string().valid("approve", "reject").required(),
|
|
5366
|
+
approve: Joi11.boolean().optional(),
|
|
5367
|
+
reject: Joi11.boolean().optional(),
|
|
5368
|
+
remarks: Joi11.string().optional().allow("", null),
|
|
5100
5369
|
/*
|
|
5101
5370
|
* Shape only. WHETHER a photo is required depends on the set's current
|
|
5102
5371
|
* state in the database, which Joi cannot see — see the check below.
|
|
5103
5372
|
* Each entry must still be a real id: a blank string carries no evidence.
|
|
5104
5373
|
*/
|
|
5105
|
-
attachment:
|
|
5106
|
-
completedBy:
|
|
5374
|
+
attachment: Joi11.array().items(Joi11.string().trim().min(1)).optional(),
|
|
5375
|
+
completedBy: Joi11.string().hex().required()
|
|
5107
5376
|
});
|
|
5108
5377
|
const { error } = validation.validate(payload);
|
|
5109
5378
|
if (error) {
|
|
@@ -5140,7 +5409,7 @@ function useAreaChecklistController() {
|
|
|
5140
5409
|
}
|
|
5141
5410
|
}
|
|
5142
5411
|
async function downloadChecklistPdf(req, res, next) {
|
|
5143
|
-
const validation =
|
|
5412
|
+
const validation = Joi11.string().hex().required();
|
|
5144
5413
|
const schedule = req.params.schedule;
|
|
5145
5414
|
const { error, value } = validation.validate(schedule);
|
|
5146
5415
|
if (error) {
|
|
@@ -5186,16 +5455,16 @@ function useAreaChecklistController() {
|
|
|
5186
5455
|
}
|
|
5187
5456
|
|
|
5188
5457
|
// src/models/hygiene-supply.model.ts
|
|
5189
|
-
import
|
|
5458
|
+
import Joi12 from "joi";
|
|
5190
5459
|
import { ObjectId as ObjectId12 } from "mongodb";
|
|
5191
5460
|
import { BadRequestError as BadRequestError17, logger as logger21 } from "@7365admin1/node-server-utils";
|
|
5192
5461
|
import { AppServiceType as AppServiceType11 } from "@7365admin1/core";
|
|
5193
|
-
var supplySchema =
|
|
5194
|
-
site:
|
|
5195
|
-
serviceType:
|
|
5196
|
-
name:
|
|
5197
|
-
unitOfMeasurement:
|
|
5198
|
-
attachment:
|
|
5462
|
+
var supplySchema = Joi12.object({
|
|
5463
|
+
site: Joi12.string().hex().required(),
|
|
5464
|
+
serviceType: Joi12.string().valid(...Object.values(AppServiceType11)).required(),
|
|
5465
|
+
name: Joi12.string().required(),
|
|
5466
|
+
unitOfMeasurement: Joi12.string().required(),
|
|
5467
|
+
attachment: Joi12.string().allow(null, "").optional()
|
|
5199
5468
|
});
|
|
5200
5469
|
function MSupply(value) {
|
|
5201
5470
|
const { error } = supplySchema.validate(value);
|
|
@@ -5332,7 +5601,7 @@ function useSupplyRepository() {
|
|
|
5332
5601
|
throw new BadRequestError18("Invalid site ID format.");
|
|
5333
5602
|
}
|
|
5334
5603
|
if (search) {
|
|
5335
|
-
query.name = { $regex: search, $options: "i" };
|
|
5604
|
+
query.name = { $regex: escapeRegex(search), $options: "i" };
|
|
5336
5605
|
cacheOptions.search = search;
|
|
5337
5606
|
}
|
|
5338
5607
|
const cacheKey = makeCacheKey6(namespace_collection, cacheOptions);
|
|
@@ -5494,7 +5763,7 @@ function useSupplyRepository() {
|
|
|
5494
5763
|
}
|
|
5495
5764
|
|
|
5496
5765
|
// src/controllers/hygiene-supply.controller.ts
|
|
5497
|
-
import
|
|
5766
|
+
import Joi13 from "joi";
|
|
5498
5767
|
import { BadRequestError as BadRequestError19, logger as logger23 } from "@7365admin1/node-server-utils";
|
|
5499
5768
|
import { AppServiceType as AppServiceType12 } from "@7365admin1/core";
|
|
5500
5769
|
function useSupplyController() {
|
|
@@ -5525,12 +5794,12 @@ function useSupplyController() {
|
|
|
5525
5794
|
}
|
|
5526
5795
|
async function getSupplies(req, res, next) {
|
|
5527
5796
|
const query = { ...req.query, ...req.params };
|
|
5528
|
-
const validation =
|
|
5529
|
-
page:
|
|
5530
|
-
limit:
|
|
5531
|
-
search:
|
|
5532
|
-
site:
|
|
5533
|
-
serviceType:
|
|
5797
|
+
const validation = Joi13.object({
|
|
5798
|
+
page: Joi13.number().min(1).optional().allow("", null),
|
|
5799
|
+
limit: Joi13.number().min(1).optional().allow("", null),
|
|
5800
|
+
search: Joi13.string().optional().allow("", null),
|
|
5801
|
+
site: Joi13.string().hex().required(),
|
|
5802
|
+
serviceType: Joi13.string().valid(...Object.values(AppServiceType12)).required()
|
|
5534
5803
|
});
|
|
5535
5804
|
const { error } = validation.validate(query);
|
|
5536
5805
|
if (error) {
|
|
@@ -5560,7 +5829,7 @@ function useSupplyController() {
|
|
|
5560
5829
|
}
|
|
5561
5830
|
}
|
|
5562
5831
|
async function getSupplyById(req, res, next) {
|
|
5563
|
-
const validation =
|
|
5832
|
+
const validation = Joi13.string().hex().required();
|
|
5564
5833
|
const _id = req.params.id;
|
|
5565
5834
|
const { error, value } = validation.validate(_id);
|
|
5566
5835
|
if (error) {
|
|
@@ -5580,12 +5849,12 @@ function useSupplyController() {
|
|
|
5580
5849
|
}
|
|
5581
5850
|
async function updateSupply(req, res, next) {
|
|
5582
5851
|
const payload = { id: req.params.id, ...req.body };
|
|
5583
|
-
const validation =
|
|
5584
|
-
id:
|
|
5585
|
-
name:
|
|
5586
|
-
unitOfMeasurement:
|
|
5587
|
-
qty:
|
|
5588
|
-
attachment:
|
|
5852
|
+
const validation = Joi13.object({
|
|
5853
|
+
id: Joi13.string().hex().required(),
|
|
5854
|
+
name: Joi13.string().optional().allow("", null),
|
|
5855
|
+
unitOfMeasurement: Joi13.string().optional().allow("", null),
|
|
5856
|
+
qty: Joi13.number().min(0).optional().allow("", null),
|
|
5857
|
+
attachment: Joi13.string().optional().allow("", null)
|
|
5589
5858
|
});
|
|
5590
5859
|
const { error } = validation.validate(payload);
|
|
5591
5860
|
if (error) {
|
|
@@ -5606,7 +5875,7 @@ function useSupplyController() {
|
|
|
5606
5875
|
}
|
|
5607
5876
|
async function deleteSupply(req, res, next) {
|
|
5608
5877
|
const id = req.params.id;
|
|
5609
|
-
const validation =
|
|
5878
|
+
const validation = Joi13.string().hex().required();
|
|
5610
5879
|
const { error, value } = validation.validate(id);
|
|
5611
5880
|
if (error) {
|
|
5612
5881
|
logger23.log({ level: "error", message: error.message });
|
|
@@ -5633,18 +5902,18 @@ function useSupplyController() {
|
|
|
5633
5902
|
}
|
|
5634
5903
|
|
|
5635
5904
|
// src/models/hygiene-stock.model.ts
|
|
5636
|
-
import
|
|
5905
|
+
import Joi14 from "joi";
|
|
5637
5906
|
import { ObjectId as ObjectId14 } from "mongodb";
|
|
5638
5907
|
import { BadRequestError as BadRequestError20, logger as logger24 } from "@7365admin1/node-server-utils";
|
|
5639
5908
|
import { AppServiceType as AppServiceType13 } from "@7365admin1/core";
|
|
5640
|
-
var stockSchema =
|
|
5641
|
-
site:
|
|
5642
|
-
serviceType:
|
|
5643
|
-
supply:
|
|
5644
|
-
in:
|
|
5645
|
-
out:
|
|
5646
|
-
balance:
|
|
5647
|
-
remarks:
|
|
5909
|
+
var stockSchema = Joi14.object({
|
|
5910
|
+
site: Joi14.string().hex().required(),
|
|
5911
|
+
serviceType: Joi14.string().valid(...Object.values(AppServiceType13)).required(),
|
|
5912
|
+
supply: Joi14.string().hex().required(),
|
|
5913
|
+
in: Joi14.number().min(0).optional(),
|
|
5914
|
+
out: Joi14.number().min(0).optional(),
|
|
5915
|
+
balance: Joi14.number().min(0).required(),
|
|
5916
|
+
remarks: Joi14.string().optional().allow("", null)
|
|
5648
5917
|
});
|
|
5649
5918
|
function MStock(value) {
|
|
5650
5919
|
const { error } = stockSchema.validate(value);
|
|
@@ -5871,7 +6140,7 @@ function useStockService() {
|
|
|
5871
6140
|
}
|
|
5872
6141
|
|
|
5873
6142
|
// src/controllers/hygiene-stock.controller.ts
|
|
5874
|
-
import
|
|
6143
|
+
import Joi15 from "joi";
|
|
5875
6144
|
import { BadRequestError as BadRequestError23, logger as logger26 } from "@7365admin1/node-server-utils";
|
|
5876
6145
|
import { AppServiceType as AppServiceType14 } from "@7365admin1/core";
|
|
5877
6146
|
function useStockController() {
|
|
@@ -5879,12 +6148,12 @@ function useStockController() {
|
|
|
5879
6148
|
const { createStock: _createStock } = useStockService();
|
|
5880
6149
|
async function createStock(req, res, next) {
|
|
5881
6150
|
const payload = { ...req.body, ...req.params };
|
|
5882
|
-
const validation =
|
|
5883
|
-
site:
|
|
5884
|
-
serviceType:
|
|
5885
|
-
supply:
|
|
5886
|
-
qty:
|
|
5887
|
-
remarks:
|
|
6151
|
+
const validation = Joi15.object({
|
|
6152
|
+
site: Joi15.string().hex().required(),
|
|
6153
|
+
serviceType: Joi15.string().valid(...Object.values(AppServiceType14)).required(),
|
|
6154
|
+
supply: Joi15.string().hex().required(),
|
|
6155
|
+
qty: Joi15.number().min(0).required(),
|
|
6156
|
+
remarks: Joi15.string().optional().allow("", null)
|
|
5888
6157
|
});
|
|
5889
6158
|
const { error } = validation.validate(payload);
|
|
5890
6159
|
if (error) {
|
|
@@ -5904,13 +6173,13 @@ function useStockController() {
|
|
|
5904
6173
|
}
|
|
5905
6174
|
async function getStocksBySupplyId(req, res, next) {
|
|
5906
6175
|
const query = { ...req.query, ...req.params };
|
|
5907
|
-
const validation =
|
|
5908
|
-
page:
|
|
5909
|
-
limit:
|
|
5910
|
-
search:
|
|
5911
|
-
site:
|
|
5912
|
-
serviceType:
|
|
5913
|
-
supply:
|
|
6176
|
+
const validation = Joi15.object({
|
|
6177
|
+
page: Joi15.number().min(1).optional().allow("", null),
|
|
6178
|
+
limit: Joi15.number().min(1).optional().allow("", null),
|
|
6179
|
+
search: Joi15.string().optional().allow("", null),
|
|
6180
|
+
site: Joi15.string().hex().required(),
|
|
6181
|
+
serviceType: Joi15.string().valid(...Object.values(AppServiceType14)).required(),
|
|
6182
|
+
supply: Joi15.string().hex().required()
|
|
5914
6183
|
});
|
|
5915
6184
|
const { error } = validation.validate(query);
|
|
5916
6185
|
if (error) {
|
|
@@ -5972,21 +6241,21 @@ async function applyCheckOutDecision(decision, decide, returnStock) {
|
|
|
5972
6241
|
}
|
|
5973
6242
|
|
|
5974
6243
|
// src/models/hygiene-checkout-item.model.ts
|
|
5975
|
-
import
|
|
6244
|
+
import Joi16 from "joi";
|
|
5976
6245
|
import { ObjectId as ObjectId16 } from "mongodb";
|
|
5977
6246
|
import { BadRequestError as BadRequestError24, logger as logger27 } from "@7365admin1/node-server-utils";
|
|
5978
6247
|
import { AppServiceType as AppServiceType15 } from "@7365admin1/core";
|
|
5979
6248
|
var allowedCheckOutItemStatus = ["pending", "completed"];
|
|
5980
|
-
var checkOutItemSchema =
|
|
5981
|
-
site:
|
|
5982
|
-
serviceType:
|
|
5983
|
-
supply:
|
|
5984
|
-
supplyName:
|
|
5985
|
-
supplyQty:
|
|
5986
|
-
qty:
|
|
5987
|
-
attachment:
|
|
5988
|
-
createdBy:
|
|
5989
|
-
createdByName:
|
|
6249
|
+
var checkOutItemSchema = Joi16.object({
|
|
6250
|
+
site: Joi16.string().hex().required(),
|
|
6251
|
+
serviceType: Joi16.string().valid(...Object.values(AppServiceType15)).required(),
|
|
6252
|
+
supply: Joi16.string().hex().required(),
|
|
6253
|
+
supplyName: Joi16.string().required(),
|
|
6254
|
+
supplyQty: Joi16.number().min(0).required(),
|
|
6255
|
+
qty: Joi16.number().min(0).required(),
|
|
6256
|
+
attachment: Joi16.array().items(Joi16.string()).optional().allow(null),
|
|
6257
|
+
createdBy: Joi16.string().hex().required(),
|
|
6258
|
+
createdByName: Joi16.string().required()
|
|
5990
6259
|
});
|
|
5991
6260
|
function MCheckOutItem(value) {
|
|
5992
6261
|
const { error } = checkOutItemSchema.validate(value);
|
|
@@ -6119,7 +6388,7 @@ function useCheckOutItemRepository() {
|
|
|
6119
6388
|
throw new BadRequestError25("Invalid site ID format.");
|
|
6120
6389
|
}
|
|
6121
6390
|
if (search) {
|
|
6122
|
-
query.supplyName = { $regex: search, $options: "i" };
|
|
6391
|
+
query.supplyName = { $regex: escapeRegex(search), $options: "i" };
|
|
6123
6392
|
cacheOptions.search = search;
|
|
6124
6393
|
}
|
|
6125
6394
|
const cacheKey = makeCacheKey8(namespace_collection, cacheOptions);
|
|
@@ -6475,7 +6744,7 @@ function useCheckOutItemService() {
|
|
|
6475
6744
|
}
|
|
6476
6745
|
|
|
6477
6746
|
// src/controllers/hygiene-checkout-item.controller.ts
|
|
6478
|
-
import
|
|
6747
|
+
import Joi17 from "joi";
|
|
6479
6748
|
import { BadRequestError as BadRequestError27, logger as logger29 } from "@7365admin1/node-server-utils";
|
|
6480
6749
|
import { AppServiceType as AppServiceType16 } from "@7365admin1/core";
|
|
6481
6750
|
function useCheckOutItemController() {
|
|
@@ -6499,13 +6768,13 @@ function useCheckOutItemController() {
|
|
|
6499
6768
|
...req.params,
|
|
6500
6769
|
createdBy
|
|
6501
6770
|
};
|
|
6502
|
-
const validation =
|
|
6503
|
-
site:
|
|
6504
|
-
serviceType:
|
|
6505
|
-
supply:
|
|
6506
|
-
qty:
|
|
6507
|
-
attachment:
|
|
6508
|
-
createdBy:
|
|
6771
|
+
const validation = Joi17.object({
|
|
6772
|
+
site: Joi17.string().hex().required(),
|
|
6773
|
+
serviceType: Joi17.string().valid(...Object.values(AppServiceType16)).required(),
|
|
6774
|
+
supply: Joi17.string().hex().required(),
|
|
6775
|
+
qty: Joi17.number().min(0).required(),
|
|
6776
|
+
attachment: Joi17.array().items(Joi17.string()).optional().allow(null),
|
|
6777
|
+
createdBy: Joi17.string().hex().required()
|
|
6509
6778
|
});
|
|
6510
6779
|
const { error } = validation.validate(payload);
|
|
6511
6780
|
if (error) {
|
|
@@ -6534,15 +6803,15 @@ function useCheckOutItemController() {
|
|
|
6534
6803
|
...req.params,
|
|
6535
6804
|
createdBy
|
|
6536
6805
|
};
|
|
6537
|
-
const validation =
|
|
6538
|
-
site:
|
|
6539
|
-
serviceType:
|
|
6540
|
-
createdBy:
|
|
6541
|
-
items:
|
|
6542
|
-
|
|
6543
|
-
supply:
|
|
6544
|
-
qty:
|
|
6545
|
-
attachment:
|
|
6806
|
+
const validation = Joi17.object({
|
|
6807
|
+
site: Joi17.string().hex().required(),
|
|
6808
|
+
serviceType: Joi17.string().valid(...Object.values(AppServiceType16)).required(),
|
|
6809
|
+
createdBy: Joi17.string().hex().required(),
|
|
6810
|
+
items: Joi17.array().items(
|
|
6811
|
+
Joi17.object({
|
|
6812
|
+
supply: Joi17.string().hex().required(),
|
|
6813
|
+
qty: Joi17.number().min(0).required(),
|
|
6814
|
+
attachment: Joi17.array().items(Joi17.string()).optional().allow(null)
|
|
6546
6815
|
})
|
|
6547
6816
|
).min(1).required()
|
|
6548
6817
|
});
|
|
@@ -6564,12 +6833,12 @@ function useCheckOutItemController() {
|
|
|
6564
6833
|
}
|
|
6565
6834
|
async function getCheckOutItems(req, res, next) {
|
|
6566
6835
|
const query = { ...req.query, ...req.params };
|
|
6567
|
-
const validation =
|
|
6568
|
-
page:
|
|
6569
|
-
limit:
|
|
6570
|
-
search:
|
|
6571
|
-
site:
|
|
6572
|
-
serviceType:
|
|
6836
|
+
const validation = Joi17.object({
|
|
6837
|
+
page: Joi17.number().min(1).optional().allow("", null),
|
|
6838
|
+
limit: Joi17.number().min(1).optional().allow("", null),
|
|
6839
|
+
search: Joi17.string().optional().allow("", null),
|
|
6840
|
+
site: Joi17.string().hex().required(),
|
|
6841
|
+
serviceType: Joi17.string().valid(...Object.values(AppServiceType16)).required()
|
|
6573
6842
|
});
|
|
6574
6843
|
const { error } = validation.validate(query);
|
|
6575
6844
|
if (error) {
|
|
@@ -6599,7 +6868,7 @@ function useCheckOutItemController() {
|
|
|
6599
6868
|
}
|
|
6600
6869
|
}
|
|
6601
6870
|
async function getCheckOutItemById(req, res, next) {
|
|
6602
|
-
const validation =
|
|
6871
|
+
const validation = Joi17.string().hex().required();
|
|
6603
6872
|
const _id = req.params.id;
|
|
6604
6873
|
const { error, value } = validation.validate(_id);
|
|
6605
6874
|
if (error) {
|
|
@@ -6638,13 +6907,13 @@ function useCheckOutItemController() {
|
|
|
6638
6907
|
...decisionValues,
|
|
6639
6908
|
completedBy
|
|
6640
6909
|
};
|
|
6641
|
-
const validation =
|
|
6642
|
-
id:
|
|
6643
|
-
decision:
|
|
6644
|
-
approve:
|
|
6645
|
-
reject:
|
|
6646
|
-
remarks:
|
|
6647
|
-
completedBy:
|
|
6910
|
+
const validation = Joi17.object({
|
|
6911
|
+
id: Joi17.string().hex().required(),
|
|
6912
|
+
decision: Joi17.string().valid("approve", "disapprove").required(),
|
|
6913
|
+
approve: Joi17.boolean().required(),
|
|
6914
|
+
reject: Joi17.boolean().required(),
|
|
6915
|
+
remarks: Joi17.string().optional().allow("", null),
|
|
6916
|
+
completedBy: Joi17.string().hex().required()
|
|
6648
6917
|
});
|
|
6649
6918
|
const { error } = validation.validate(payload);
|
|
6650
6919
|
if (error) {
|
|
@@ -6677,23 +6946,15 @@ function useCheckOutItemController() {
|
|
|
6677
6946
|
// src/models/hygiene-schedule-task.model.ts
|
|
6678
6947
|
import { BadRequestError as BadRequestError28, logger as logger30 } from "@7365admin1/node-server-utils";
|
|
6679
6948
|
import { AppServiceType as AppServiceType17 } from "@7365admin1/core";
|
|
6680
|
-
import
|
|
6949
|
+
import Joi18 from "joi";
|
|
6681
6950
|
import { ObjectId as ObjectId18 } from "mongodb";
|
|
6682
|
-
var scheduleTaskSchema =
|
|
6683
|
-
site:
|
|
6684
|
-
serviceType:
|
|
6685
|
-
|
|
6686
|
-
|
|
6687
|
-
|
|
6688
|
-
|
|
6689
|
-
).required(),
|
|
6690
|
-
description: Joi16.string().optional().allow("", null),
|
|
6691
|
-
areas: Joi16.array().min(1).items(
|
|
6692
|
-
Joi16.object({
|
|
6693
|
-
name: Joi16.string().required(),
|
|
6694
|
-
value: Joi16.any().required()
|
|
6695
|
-
})
|
|
6696
|
-
).required()
|
|
6951
|
+
var scheduleTaskSchema = Joi18.object({
|
|
6952
|
+
site: Joi18.string().hex().required(),
|
|
6953
|
+
serviceType: Joi18.string().valid(...Object.values(AppServiceType17)).required(),
|
|
6954
|
+
...scheduleTaskFields
|
|
6955
|
+
}).custom((value, helpers) => {
|
|
6956
|
+
const message = validateScheduleRules(cleanScheduleInput(value));
|
|
6957
|
+
return message ? helpers.message({ custom: message }) : value;
|
|
6697
6958
|
});
|
|
6698
6959
|
function MScheduleTask(value) {
|
|
6699
6960
|
const { error } = scheduleTaskSchema.validate(value);
|
|
@@ -6720,14 +6981,26 @@ function MScheduleTask(value) {
|
|
|
6720
6981
|
}
|
|
6721
6982
|
});
|
|
6722
6983
|
}
|
|
6984
|
+
const extra = cleanScheduleInput({
|
|
6985
|
+
repeat: value.repeat,
|
|
6986
|
+
daysOfWeek: value.daysOfWeek,
|
|
6987
|
+
dayOfMonth: value.dayOfMonth,
|
|
6988
|
+
startDate: value.startDate,
|
|
6989
|
+
endDate: value.endDate,
|
|
6990
|
+
paused: value.paused
|
|
6991
|
+
});
|
|
6992
|
+
for (const key of Object.keys(extra))
|
|
6993
|
+
if (extra[key] === void 0)
|
|
6994
|
+
delete extra[key];
|
|
6723
6995
|
return {
|
|
6724
6996
|
site: value.site,
|
|
6725
6997
|
serviceType: value.serviceType,
|
|
6726
6998
|
title: value.title,
|
|
6727
6999
|
time: value.time,
|
|
6728
|
-
dates: value.dates,
|
|
7000
|
+
dates: value.dates ?? [],
|
|
6729
7001
|
description: value.description,
|
|
6730
7002
|
areas: value.areas,
|
|
7003
|
+
...extra,
|
|
6731
7004
|
status: "active",
|
|
6732
7005
|
createdAt: /* @__PURE__ */ new Date(),
|
|
6733
7006
|
updatedAt: "",
|
|
@@ -6747,11 +7020,23 @@ import {
|
|
|
6747
7020
|
makeCacheKey as makeCacheKey9,
|
|
6748
7021
|
NotFoundError as NotFoundError7
|
|
6749
7022
|
} from "@7365admin1/node-server-utils";
|
|
7023
|
+
var SCHEDULE_FIELDS = {
|
|
7024
|
+
time: 1,
|
|
7025
|
+
dates: 1,
|
|
7026
|
+
repeat: 1,
|
|
7027
|
+
daysOfWeek: 1,
|
|
7028
|
+
dayOfMonth: 1,
|
|
7029
|
+
startDate: 1,
|
|
7030
|
+
endDate: 1,
|
|
7031
|
+
paused: 1,
|
|
7032
|
+
lastRunDate: 1
|
|
7033
|
+
};
|
|
6750
7034
|
function useScheduleTaskRepository() {
|
|
6751
7035
|
const db = useAtlas14.getDb();
|
|
6752
7036
|
if (!db) {
|
|
6753
7037
|
throw new InternalServerError11("Unable to connect to server.");
|
|
6754
7038
|
}
|
|
7039
|
+
const database = db;
|
|
6755
7040
|
const namespace_collection = "site.schedule-tasks";
|
|
6756
7041
|
const collection = db.collection(namespace_collection);
|
|
6757
7042
|
const { delNamespace, setCache, getCache } = useCache9(namespace_collection);
|
|
@@ -6819,14 +7104,14 @@ function useScheduleTaskRepository() {
|
|
|
6819
7104
|
throw new BadRequestError29("Invalid site ID format.");
|
|
6820
7105
|
}
|
|
6821
7106
|
if (search) {
|
|
6822
|
-
query.$or = [{ title: { $regex: search, $options: "i" } }];
|
|
7107
|
+
query.$or = [{ title: { $regex: escapeRegex(search), $options: "i" } }];
|
|
6823
7108
|
cacheOptions.search = search;
|
|
6824
7109
|
}
|
|
6825
7110
|
const cacheKey = makeCacheKey9(namespace_collection, cacheOptions);
|
|
6826
7111
|
const cachedData = await getCache(cacheKey);
|
|
6827
7112
|
if (cachedData) {
|
|
6828
7113
|
logger31.info(`Cache hit for key: ${cacheKey}`);
|
|
6829
|
-
return cachedData;
|
|
7114
|
+
return enrichList(cachedData, site, serviceType);
|
|
6830
7115
|
}
|
|
6831
7116
|
try {
|
|
6832
7117
|
const items = await collection.aggregate([
|
|
@@ -6835,7 +7120,8 @@ function useScheduleTaskRepository() {
|
|
|
6835
7120
|
$project: {
|
|
6836
7121
|
title: 1,
|
|
6837
7122
|
areas: 1,
|
|
6838
|
-
status: 1
|
|
7123
|
+
status: 1,
|
|
7124
|
+
...SCHEDULE_FIELDS
|
|
6839
7125
|
}
|
|
6840
7126
|
},
|
|
6841
7127
|
{ $sort: { _id: -1 } },
|
|
@@ -6849,15 +7135,104 @@ function useScheduleTaskRepository() {
|
|
|
6849
7135
|
}).catch((err) => {
|
|
6850
7136
|
logger31.error(`Failed to set cache for key: ${cacheKey}`, err);
|
|
6851
7137
|
});
|
|
6852
|
-
return data;
|
|
7138
|
+
return enrichList(data, site, serviceType);
|
|
6853
7139
|
} catch (error) {
|
|
6854
7140
|
throw error;
|
|
6855
7141
|
}
|
|
6856
7142
|
}
|
|
6857
|
-
async function
|
|
7143
|
+
async function enrichList(data, site, serviceType) {
|
|
7144
|
+
const items = data?.items ?? [];
|
|
7145
|
+
const now = /* @__PURE__ */ new Date();
|
|
7146
|
+
let statuses = /* @__PURE__ */ new Map();
|
|
7147
|
+
try {
|
|
7148
|
+
statuses = await getRunStatuses(items, site, serviceType);
|
|
7149
|
+
} catch (error) {
|
|
7150
|
+
logger31.error("Could not read schedule task run status", error);
|
|
7151
|
+
}
|
|
7152
|
+
return {
|
|
7153
|
+
...data,
|
|
7154
|
+
items: items.map((item) => {
|
|
7155
|
+
try {
|
|
7156
|
+
return {
|
|
7157
|
+
...item,
|
|
7158
|
+
...scheduleDisplay(item, now),
|
|
7159
|
+
lastRunStatus: statuses.get(String(item._id)) ?? null
|
|
7160
|
+
};
|
|
7161
|
+
} catch (error) {
|
|
7162
|
+
logger31.error(`Could not enrich schedule task ${item?._id}`, error);
|
|
7163
|
+
return { ...item, nextRun: null, lastRun: null, scheduleStatus: null, lastRunStatus: null };
|
|
7164
|
+
}
|
|
7165
|
+
})
|
|
7166
|
+
};
|
|
7167
|
+
}
|
|
7168
|
+
async function getRunStatuses(items, site, serviceType) {
|
|
7169
|
+
const result = /* @__PURE__ */ new Map();
|
|
7170
|
+
const ran = items.filter((i) => i.lastRunDate);
|
|
7171
|
+
if (!ran.length)
|
|
7172
|
+
return result;
|
|
7173
|
+
const parents = await database.collection("site.cleaning.schedules").find(
|
|
7174
|
+
{
|
|
7175
|
+
site,
|
|
7176
|
+
serviceType,
|
|
7177
|
+
dateStr: { $in: [...new Set(ran.map((i) => i.lastRunDate))] }
|
|
7178
|
+
},
|
|
7179
|
+
{ projection: { dateStr: 1 } }
|
|
7180
|
+
).toArray();
|
|
7181
|
+
if (!parents.length)
|
|
7182
|
+
return result;
|
|
7183
|
+
const dateOf = new Map(parents.map((p) => [String(p._id), p.dateStr]));
|
|
7184
|
+
const taskIds = ran.map((i) => new ObjectId19(i._id));
|
|
7185
|
+
const rows = await database.collection("site.cleaning.schedule.areas").aggregate([
|
|
7186
|
+
{
|
|
7187
|
+
$match: {
|
|
7188
|
+
schedule: { $in: parents.map((p) => p._id) },
|
|
7189
|
+
"checklist.scheduleTaskId": { $in: taskIds }
|
|
7190
|
+
}
|
|
7191
|
+
},
|
|
7192
|
+
{ $unwind: "$checklist" },
|
|
7193
|
+
{ $match: { "checklist.scheduleTaskId": { $in: taskIds } } },
|
|
7194
|
+
{ $unwind: "$checklist.units" },
|
|
7195
|
+
{
|
|
7196
|
+
$group: {
|
|
7197
|
+
_id: { task: "$checklist.scheduleTaskId", schedule: "$schedule" },
|
|
7198
|
+
total: { $sum: 1 },
|
|
7199
|
+
completed: {
|
|
7200
|
+
$sum: { $cond: [{ $eq: ["$checklist.units.status", "completed"] }, 1, 0] }
|
|
7201
|
+
},
|
|
7202
|
+
closed: {
|
|
7203
|
+
$sum: { $cond: [{ $eq: ["$checklist.units.status", "closed"] }, 1, 0] }
|
|
7204
|
+
}
|
|
7205
|
+
}
|
|
7206
|
+
}
|
|
7207
|
+
]).toArray();
|
|
7208
|
+
const sums = /* @__PURE__ */ new Map();
|
|
7209
|
+
for (const row of rows) {
|
|
7210
|
+
const task = ran.find((i) => String(i._id) === String(row._id.task));
|
|
7211
|
+
if (!task || dateOf.get(String(row._id.schedule)) !== task.lastRunDate)
|
|
7212
|
+
continue;
|
|
7213
|
+
const sum = sums.get(String(task._id)) ?? { total: 0, completed: 0, closed: 0 };
|
|
7214
|
+
sum.total += row.total;
|
|
7215
|
+
sum.completed += row.completed;
|
|
7216
|
+
sum.closed += row.closed;
|
|
7217
|
+
sums.set(String(task._id), sum);
|
|
7218
|
+
}
|
|
7219
|
+
for (const [id, sum] of sums) {
|
|
7220
|
+
result.set(id, sum.completed === sum.total ? "done" : sum.closed > 0 ? "missed" : "pending");
|
|
7221
|
+
}
|
|
7222
|
+
return result;
|
|
7223
|
+
}
|
|
7224
|
+
async function getAllScheduleTask(today) {
|
|
6858
7225
|
const query = {
|
|
6859
7226
|
status: { $ne: "deleted" }
|
|
6860
7227
|
};
|
|
7228
|
+
if (today) {
|
|
7229
|
+
query.paused = { $ne: true };
|
|
7230
|
+
query.lastRunDate = { $ne: today };
|
|
7231
|
+
query.$or = [
|
|
7232
|
+
{ dates: today },
|
|
7233
|
+
{ repeat: { $in: ["daily", "weekdays", "weekly", "monthly"] } }
|
|
7234
|
+
];
|
|
7235
|
+
}
|
|
6861
7236
|
try {
|
|
6862
7237
|
const items = await collection.aggregate([{ $match: query }, { $sort: { _id: -1 } }]).toArray();
|
|
6863
7238
|
return items;
|
|
@@ -6865,6 +7240,9 @@ function useScheduleTaskRepository() {
|
|
|
6865
7240
|
throw error;
|
|
6866
7241
|
}
|
|
6867
7242
|
}
|
|
7243
|
+
function withScheduleDisplay(row) {
|
|
7244
|
+
return { ...row, ...scheduleDisplay(row, /* @__PURE__ */ new Date()) };
|
|
7245
|
+
}
|
|
6868
7246
|
async function getScheduleTaskById(_id, session) {
|
|
6869
7247
|
try {
|
|
6870
7248
|
_id = new ObjectId19(_id);
|
|
@@ -6882,7 +7260,7 @@ function useScheduleTaskRepository() {
|
|
|
6882
7260
|
const cachedData = await getCache(cacheKey);
|
|
6883
7261
|
if (cachedData) {
|
|
6884
7262
|
logger31.info(`Cache hit for key: ${cacheKey}`);
|
|
6885
|
-
return cachedData;
|
|
7263
|
+
return withScheduleDisplay(cachedData);
|
|
6886
7264
|
}
|
|
6887
7265
|
} else {
|
|
6888
7266
|
logger31.info(`Skipping cache during transaction for key: ${cacheKey}`);
|
|
@@ -6893,12 +7271,13 @@ function useScheduleTaskRepository() {
|
|
|
6893
7271
|
{
|
|
6894
7272
|
$project: {
|
|
6895
7273
|
title: 1,
|
|
6896
|
-
time: 1,
|
|
6897
|
-
dates: 1,
|
|
6898
7274
|
description: 1,
|
|
6899
7275
|
areas: 1,
|
|
6900
7276
|
status: 1,
|
|
6901
|
-
createdAt: 1
|
|
7277
|
+
createdAt: 1,
|
|
7278
|
+
site: 1,
|
|
7279
|
+
serviceType: 1,
|
|
7280
|
+
...SCHEDULE_FIELDS
|
|
6902
7281
|
}
|
|
6903
7282
|
}
|
|
6904
7283
|
]).toArray();
|
|
@@ -6910,7 +7289,7 @@ function useScheduleTaskRepository() {
|
|
|
6910
7289
|
}).catch((err) => {
|
|
6911
7290
|
logger31.error(`Failed to set cache for key: ${cacheKey}`, err);
|
|
6912
7291
|
});
|
|
6913
|
-
return data[0];
|
|
7292
|
+
return withScheduleDisplay(data[0]);
|
|
6914
7293
|
} catch (error) {
|
|
6915
7294
|
throw error;
|
|
6916
7295
|
}
|
|
@@ -6934,16 +7313,20 @@ function useScheduleTaskRepository() {
|
|
|
6934
7313
|
});
|
|
6935
7314
|
}
|
|
6936
7315
|
try {
|
|
6937
|
-
const
|
|
7316
|
+
const { set, unset } = splitScheduleUpdate(value);
|
|
7317
|
+
const update = {
|
|
7318
|
+
$set: { ...set, updatedAt: /* @__PURE__ */ new Date() }
|
|
7319
|
+
};
|
|
7320
|
+
if (unset.length) {
|
|
7321
|
+
update.$unset = Object.fromEntries(unset.map((key) => [key, ""]));
|
|
7322
|
+
}
|
|
6938
7323
|
const res = await collection.updateOne(
|
|
6939
|
-
{ _id },
|
|
6940
|
-
|
|
7324
|
+
{ _id, status: { $ne: "deleted" } },
|
|
7325
|
+
update,
|
|
6941
7326
|
{ session }
|
|
6942
7327
|
);
|
|
6943
|
-
if (res.
|
|
6944
|
-
throw new
|
|
6945
|
-
"Unable to update hygiene schedule task."
|
|
6946
|
-
);
|
|
7328
|
+
if (res.matchedCount === 0) {
|
|
7329
|
+
throw new NotFoundError7("Schedule task not found.");
|
|
6947
7330
|
}
|
|
6948
7331
|
delNamespace().then(() => {
|
|
6949
7332
|
logger31.info(`Cache cleared for namespace: ${namespace_collection}`);
|
|
@@ -6958,6 +7341,18 @@ function useScheduleTaskRepository() {
|
|
|
6958
7341
|
throw error;
|
|
6959
7342
|
}
|
|
6960
7343
|
}
|
|
7344
|
+
async function markScheduleTaskRan(_id, date) {
|
|
7345
|
+
await collection.updateOne(
|
|
7346
|
+
{ _id: new ObjectId19(_id) },
|
|
7347
|
+
{ $set: { lastRunDate: date, lastRunAt: /* @__PURE__ */ new Date() } }
|
|
7348
|
+
);
|
|
7349
|
+
delNamespace().catch((err) => {
|
|
7350
|
+
logger31.error(
|
|
7351
|
+
`Failed to clear cache for namespace: ${namespace_collection}`,
|
|
7352
|
+
err
|
|
7353
|
+
);
|
|
7354
|
+
});
|
|
7355
|
+
}
|
|
6961
7356
|
async function deleteScheduleTask(_id, session) {
|
|
6962
7357
|
try {
|
|
6963
7358
|
_id = new ObjectId19(_id);
|
|
@@ -6997,6 +7392,7 @@ function useScheduleTaskRepository() {
|
|
|
6997
7392
|
createScheduleTask,
|
|
6998
7393
|
getScheduleTasks,
|
|
6999
7394
|
getAllScheduleTask,
|
|
7395
|
+
markScheduleTaskRan,
|
|
7000
7396
|
getScheduleTaskById,
|
|
7001
7397
|
updateScheduleTask,
|
|
7002
7398
|
deleteScheduleTask
|
|
@@ -7008,7 +7404,7 @@ import { ObjectId as ObjectId20 } from "mongodb";
|
|
|
7008
7404
|
import { logger as logger32 } from "@7365admin1/node-server-utils";
|
|
7009
7405
|
function useScheduleTaskService() {
|
|
7010
7406
|
const { createParentChecklist } = useParentChecklistRepo();
|
|
7011
|
-
const { getAllScheduleTask } = useScheduleTaskRepository();
|
|
7407
|
+
const { getAllScheduleTask, markScheduleTaskRan } = useScheduleTaskRepository();
|
|
7012
7408
|
const {
|
|
7013
7409
|
createAreaChecklist,
|
|
7014
7410
|
getAreaChecklistByAreaAndSchedule,
|
|
@@ -7017,73 +7413,7 @@ function useScheduleTaskService() {
|
|
|
7017
7413
|
const { getAreaById } = useAreaRepo();
|
|
7018
7414
|
function checkScheduleConditions(schedule, serviceType, currentDate = /* @__PURE__ */ new Date()) {
|
|
7019
7415
|
try {
|
|
7020
|
-
|
|
7021
|
-
const timeString = now.toLocaleTimeString("en-US", {
|
|
7022
|
-
hour: "2-digit",
|
|
7023
|
-
minute: "2-digit",
|
|
7024
|
-
hour12: false,
|
|
7025
|
-
timeZone: "Asia/Singapore"
|
|
7026
|
-
});
|
|
7027
|
-
const [currentHour, currentMinute] = timeString.split(":").map(Number);
|
|
7028
|
-
logger32.info(
|
|
7029
|
-
`Checking schedule ${schedule._id}: Current time ${currentHour}:${currentMinute}, Schedule time ${schedule.time}, Dates ${JSON.stringify(schedule.dates)}`
|
|
7030
|
-
);
|
|
7031
|
-
const currentDateFormatted = now.toLocaleDateString("en-CA", {
|
|
7032
|
-
timeZone: "Asia/Singapore"
|
|
7033
|
-
});
|
|
7034
|
-
if (!schedule.dates || !Array.isArray(schedule.dates) || schedule.dates.length === 0) {
|
|
7035
|
-
logger32.info(`Schedule ${schedule._id}: No dates configured, skipping`);
|
|
7036
|
-
return false;
|
|
7037
|
-
}
|
|
7038
|
-
if (!schedule.dates.includes(currentDateFormatted)) {
|
|
7039
|
-
logger32.info(
|
|
7040
|
-
`Schedule ${schedule._id}: Current date ${currentDateFormatted} is not in scheduled dates [${schedule.dates.join(
|
|
7041
|
-
", "
|
|
7042
|
-
)}]`
|
|
7043
|
-
);
|
|
7044
|
-
return false;
|
|
7045
|
-
}
|
|
7046
|
-
const [scheduleHour, scheduleMinute] = schedule.time.split(":").map(Number);
|
|
7047
|
-
const timeMatches = currentHour === scheduleHour && currentMinute === scheduleMinute;
|
|
7048
|
-
if (!timeMatches) {
|
|
7049
|
-
logger32.info(
|
|
7050
|
-
`Schedule ${schedule._id}: Time does not match. Current: ${currentHour}:${currentMinute}, Expected: ${scheduleHour}:${scheduleMinute}`
|
|
7051
|
-
);
|
|
7052
|
-
return false;
|
|
7053
|
-
}
|
|
7054
|
-
const relevantTimestamp = schedule.updatedAt || schedule.createdAt;
|
|
7055
|
-
if (relevantTimestamp) {
|
|
7056
|
-
const savedAt = new Date(relevantTimestamp);
|
|
7057
|
-
const savedHour = parseInt(
|
|
7058
|
-
savedAt.toLocaleTimeString("en-US", {
|
|
7059
|
-
hour: "2-digit",
|
|
7060
|
-
hour12: false,
|
|
7061
|
-
timeZone: "Asia/Singapore"
|
|
7062
|
-
}),
|
|
7063
|
-
10
|
|
7064
|
-
);
|
|
7065
|
-
const savedMinute = savedAt.toLocaleString("en-US", {
|
|
7066
|
-
minute: "2-digit",
|
|
7067
|
-
timeZone: "Asia/Singapore"
|
|
7068
|
-
});
|
|
7069
|
-
const savedDateFormatted = savedAt.toLocaleDateString("en-CA", {
|
|
7070
|
-
timeZone: "Asia/Singapore"
|
|
7071
|
-
});
|
|
7072
|
-
const savedMinNum = parseInt(savedMinute, 10);
|
|
7073
|
-
const wasSavedThisMinute = savedDateFormatted === currentDateFormatted && savedHour === currentHour && savedMinNum === currentMinute;
|
|
7074
|
-
if (wasSavedThisMinute) {
|
|
7075
|
-
logger32.info(
|
|
7076
|
-
`Schedule ${schedule._id}: Skipping \u2014 task was saved this minute (${savedHour}:${String(
|
|
7077
|
-
savedMinNum
|
|
7078
|
-
).padStart(2, "0")}). Will fire on next occurrence.`
|
|
7079
|
-
);
|
|
7080
|
-
return false;
|
|
7081
|
-
}
|
|
7082
|
-
}
|
|
7083
|
-
logger32.info(
|
|
7084
|
-
`Schedule ${schedule._id}: All conditions matched - Date is within range and time matches`
|
|
7085
|
-
);
|
|
7086
|
-
return true;
|
|
7416
|
+
return shouldRunNow(schedule, currentDate);
|
|
7087
7417
|
} catch (error) {
|
|
7088
7418
|
logger32.error(
|
|
7089
7419
|
`Error checking schedule conditions for ${schedule._id}:`,
|
|
@@ -7092,18 +7422,22 @@ function useScheduleTaskService() {
|
|
|
7092
7422
|
return false;
|
|
7093
7423
|
}
|
|
7094
7424
|
}
|
|
7425
|
+
const MAX_ATTEMPTS = 5;
|
|
7426
|
+
const attempts = /* @__PURE__ */ new Map();
|
|
7095
7427
|
async function processScheduledTasks(currentDate) {
|
|
7096
7428
|
try {
|
|
7097
7429
|
logger32.info("Starting scheduled task processing...");
|
|
7098
|
-
const
|
|
7430
|
+
const today = sgtParts(currentDate ?? /* @__PURE__ */ new Date()).date;
|
|
7431
|
+
const scheduleTasks = await getAllScheduleTask(today);
|
|
7099
7432
|
if (!scheduleTasks || scheduleTasks.length === 0) {
|
|
7100
7433
|
logger32.info("No schedule tasks found to process");
|
|
7101
|
-
return { processed: 0, validated: 0 };
|
|
7434
|
+
return { processed: 0, validated: 0, fired: [] };
|
|
7102
7435
|
}
|
|
7103
7436
|
logger32.info(`Found ${scheduleTasks.length} schedule tasks to check`);
|
|
7104
7437
|
let processedCount = 0;
|
|
7105
7438
|
let validatedCount = 0;
|
|
7106
7439
|
const validatedTasks = [];
|
|
7440
|
+
const fired = [];
|
|
7107
7441
|
for (const scheduleTask of scheduleTasks) {
|
|
7108
7442
|
try {
|
|
7109
7443
|
const serviceType = scheduleTask.serviceType;
|
|
@@ -7146,6 +7480,8 @@ function useScheduleTaskService() {
|
|
|
7146
7480
|
logger32.info(
|
|
7147
7481
|
`Using parent checklist ${parentChecklistId}, now creating/updating area checklists`
|
|
7148
7482
|
);
|
|
7483
|
+
let failedAreas = 0;
|
|
7484
|
+
const addedAreas = [];
|
|
7149
7485
|
for (const area of scheduleTask.areas) {
|
|
7150
7486
|
try {
|
|
7151
7487
|
const areaId = area.value.toString();
|
|
@@ -7225,6 +7561,7 @@ function useScheduleTaskService() {
|
|
|
7225
7561
|
newSets
|
|
7226
7562
|
);
|
|
7227
7563
|
if (modified > 0) {
|
|
7564
|
+
addedAreas.push(area.name);
|
|
7228
7565
|
logger32.info(
|
|
7229
7566
|
`Appended ${areaSetConfig} set(s) starting from ${startSetNumber} to area checklist for area ${area.name}`
|
|
7230
7567
|
);
|
|
@@ -7257,13 +7594,32 @@ function useScheduleTaskService() {
|
|
|
7257
7594
|
`Area ${area.name} (${areaId}): Creating new area checklist with schedule-task sets only`
|
|
7258
7595
|
);
|
|
7259
7596
|
await createAreaChecklist(checklistData);
|
|
7597
|
+
addedAreas.push(area.name);
|
|
7260
7598
|
logger32.info(`Created new area checklist for area ${area.name}`);
|
|
7261
7599
|
}
|
|
7262
7600
|
} catch (error) {
|
|
7263
7601
|
logger32.error(`Error processing area ${area.name}:`, error);
|
|
7602
|
+
failedAreas++;
|
|
7264
7603
|
continue;
|
|
7265
7604
|
}
|
|
7266
7605
|
}
|
|
7606
|
+
const attemptKey = `${scheduleTask._id}:${today}`;
|
|
7607
|
+
const tried = (attempts.get(attemptKey) ?? 0) + 1;
|
|
7608
|
+
attempts.set(attemptKey, tried);
|
|
7609
|
+
if (failedAreas === 0 || tried >= MAX_ATTEMPTS) {
|
|
7610
|
+
await markScheduleTaskRan(scheduleTask._id, today);
|
|
7611
|
+
attempts.delete(attemptKey);
|
|
7612
|
+
}
|
|
7613
|
+
if (addedAreas.length > 0) {
|
|
7614
|
+
fired.push({
|
|
7615
|
+
_id: scheduleTask._id,
|
|
7616
|
+
site: scheduleTask.site,
|
|
7617
|
+
serviceType,
|
|
7618
|
+
title: scheduleTask.title,
|
|
7619
|
+
time: scheduleTask.time,
|
|
7620
|
+
areas: addedAreas
|
|
7621
|
+
});
|
|
7622
|
+
}
|
|
7267
7623
|
processedCount++;
|
|
7268
7624
|
validatedCount++;
|
|
7269
7625
|
validatedTasks.push(scheduleTask);
|
|
@@ -7275,6 +7631,17 @@ function useScheduleTaskService() {
|
|
|
7275
7631
|
`Error processing schedule task ${scheduleTask._id}:`,
|
|
7276
7632
|
error
|
|
7277
7633
|
);
|
|
7634
|
+
try {
|
|
7635
|
+
const key = `${scheduleTask._id}:${today}`;
|
|
7636
|
+
const tried = (attempts.get(key) ?? 0) + 1;
|
|
7637
|
+
attempts.set(key, tried);
|
|
7638
|
+
if (tried >= MAX_ATTEMPTS && scheduleTask._id) {
|
|
7639
|
+
await markScheduleTaskRan(scheduleTask._id, today);
|
|
7640
|
+
attempts.delete(key);
|
|
7641
|
+
}
|
|
7642
|
+
} catch (markError) {
|
|
7643
|
+
logger32.error("Could not mark failing schedule task", markError);
|
|
7644
|
+
}
|
|
7278
7645
|
continue;
|
|
7279
7646
|
}
|
|
7280
7647
|
}
|
|
@@ -7284,7 +7651,8 @@ function useScheduleTaskService() {
|
|
|
7284
7651
|
return {
|
|
7285
7652
|
processed: processedCount,
|
|
7286
7653
|
validated: validatedCount,
|
|
7287
|
-
tasks: validatedTasks
|
|
7654
|
+
tasks: validatedTasks,
|
|
7655
|
+
fired
|
|
7288
7656
|
};
|
|
7289
7657
|
} catch (error) {
|
|
7290
7658
|
logger32.error("Error processing scheduled tasks:", error);
|
|
@@ -7295,7 +7663,7 @@ function useScheduleTaskService() {
|
|
|
7295
7663
|
}
|
|
7296
7664
|
|
|
7297
7665
|
// src/controllers/hygiene-schedule-task.controller.ts
|
|
7298
|
-
import
|
|
7666
|
+
import Joi19 from "joi";
|
|
7299
7667
|
import { BadRequestError as BadRequestError30, logger as logger33 } from "@7365admin1/node-server-utils";
|
|
7300
7668
|
import { AppServiceType as AppServiceType18 } from "@7365admin1/core";
|
|
7301
7669
|
function useScheduleTaskController() {
|
|
@@ -7326,12 +7694,12 @@ function useScheduleTaskController() {
|
|
|
7326
7694
|
}
|
|
7327
7695
|
async function getScheduleTasks(req, res, next) {
|
|
7328
7696
|
const query = { ...req.query, ...req.params };
|
|
7329
|
-
const validation =
|
|
7330
|
-
page:
|
|
7331
|
-
limit:
|
|
7332
|
-
search:
|
|
7333
|
-
site:
|
|
7334
|
-
serviceType:
|
|
7697
|
+
const validation = Joi19.object({
|
|
7698
|
+
page: Joi19.number().min(1).optional().allow("", null),
|
|
7699
|
+
limit: Joi19.number().min(1).optional().allow("", null),
|
|
7700
|
+
search: Joi19.string().optional().allow("", null),
|
|
7701
|
+
site: Joi19.string().hex().required(),
|
|
7702
|
+
serviceType: Joi19.string().valid(...Object.values(AppServiceType18)).required()
|
|
7335
7703
|
});
|
|
7336
7704
|
const { error } = validation.validate(query);
|
|
7337
7705
|
if (error) {
|
|
@@ -7339,8 +7707,8 @@ function useScheduleTaskController() {
|
|
|
7339
7707
|
next(new BadRequestError30(error.message));
|
|
7340
7708
|
return;
|
|
7341
7709
|
}
|
|
7342
|
-
const page = parseInt(req.query.page)
|
|
7343
|
-
const limit = parseInt(req.query.limit)
|
|
7710
|
+
const page = parseInt(req.query.page) || 1;
|
|
7711
|
+
const limit = parseInt(req.query.limit) || 10;
|
|
7344
7712
|
const search = req.query.search ?? "";
|
|
7345
7713
|
const site = req.params.site ?? "";
|
|
7346
7714
|
const serviceType = req.query.serviceType ?? "";
|
|
@@ -7361,7 +7729,7 @@ function useScheduleTaskController() {
|
|
|
7361
7729
|
}
|
|
7362
7730
|
}
|
|
7363
7731
|
async function getScheduleTaskById(req, res, next) {
|
|
7364
|
-
const validation =
|
|
7732
|
+
const validation = Joi19.string().hex().required();
|
|
7365
7733
|
const _id = req.params.id;
|
|
7366
7734
|
const { error, value } = validation.validate(_id);
|
|
7367
7735
|
if (error) {
|
|
@@ -7381,20 +7749,17 @@ function useScheduleTaskController() {
|
|
|
7381
7749
|
}
|
|
7382
7750
|
async function updateScheduleTask(req, res, next) {
|
|
7383
7751
|
const payload = { id: req.params.id, ...req.body };
|
|
7384
|
-
const
|
|
7385
|
-
|
|
7386
|
-
|
|
7387
|
-
|
|
7388
|
-
|
|
7389
|
-
|
|
7390
|
-
)
|
|
7391
|
-
|
|
7392
|
-
|
|
7393
|
-
|
|
7394
|
-
|
|
7395
|
-
value: Joi17.any().required()
|
|
7396
|
-
})
|
|
7397
|
-
).optional()
|
|
7752
|
+
const optional = Object.fromEntries(
|
|
7753
|
+
Object.entries(scheduleTaskFields).map(([key, rule]) => [
|
|
7754
|
+
key,
|
|
7755
|
+
key === "dates" ? Joi19.array().min(1).items(scheduleDate).optional().messages({
|
|
7756
|
+
"array.min": "Pick at least one date, or choose a repeat."
|
|
7757
|
+
}) : rule.optional()
|
|
7758
|
+
])
|
|
7759
|
+
);
|
|
7760
|
+
const validation = Joi19.object({
|
|
7761
|
+
id: Joi19.string().hex().required(),
|
|
7762
|
+
...optional
|
|
7398
7763
|
});
|
|
7399
7764
|
const { error } = validation.validate(payload);
|
|
7400
7765
|
if (error) {
|
|
@@ -7402,6 +7767,24 @@ function useScheduleTaskController() {
|
|
|
7402
7767
|
next(new BadRequestError30(error.message));
|
|
7403
7768
|
return;
|
|
7404
7769
|
}
|
|
7770
|
+
try {
|
|
7771
|
+
const { set, unset } = splitScheduleUpdate(req.body ?? {});
|
|
7772
|
+
if (TIMING_KEYS.some((key) => key in set || unset.includes(key))) {
|
|
7773
|
+
const stored = await _getScheduleTaskById(req.params.id);
|
|
7774
|
+
const merged = { ...stored, ...set };
|
|
7775
|
+
for (const key of unset)
|
|
7776
|
+
delete merged[key];
|
|
7777
|
+
const problem = timingChanged(stored, merged) ? validateScheduleRules(cleanScheduleInput(merged)) : null;
|
|
7778
|
+
if (problem) {
|
|
7779
|
+
next(new BadRequestError30(problem));
|
|
7780
|
+
return;
|
|
7781
|
+
}
|
|
7782
|
+
}
|
|
7783
|
+
} catch (error2) {
|
|
7784
|
+
logger33.log({ level: "error", message: error2.message });
|
|
7785
|
+
next(error2);
|
|
7786
|
+
return;
|
|
7787
|
+
}
|
|
7405
7788
|
try {
|
|
7406
7789
|
const { id, ...value } = payload;
|
|
7407
7790
|
await _updateScheduleTask(id, value);
|
|
@@ -7414,7 +7797,7 @@ function useScheduleTaskController() {
|
|
|
7414
7797
|
}
|
|
7415
7798
|
}
|
|
7416
7799
|
async function deleteScheduleTask(req, res, next) {
|
|
7417
|
-
const validation =
|
|
7800
|
+
const validation = Joi19.string().hex().required();
|
|
7418
7801
|
const _id = req.params.id;
|
|
7419
7802
|
const { error, value } = validation.validate(_id);
|
|
7420
7803
|
if (error) {
|
|
@@ -7658,17 +8041,17 @@ function useQRService() {
|
|
|
7658
8041
|
}
|
|
7659
8042
|
|
|
7660
8043
|
// src/controllers/hygiene-qr.controller.ts
|
|
7661
|
-
import
|
|
8044
|
+
import Joi20 from "joi";
|
|
7662
8045
|
import { BadRequestError as BadRequestError31, logger as logger35 } from "@7365admin1/node-server-utils";
|
|
7663
8046
|
function useQRController() {
|
|
7664
8047
|
const { generateQRImage: _generateQRImage, generateQRPDF: _generateQRPDF } = useQRService();
|
|
7665
8048
|
async function generateQR(req, res, next) {
|
|
7666
|
-
const validation =
|
|
7667
|
-
url:
|
|
7668
|
-
filename:
|
|
7669
|
-
title:
|
|
7670
|
-
subtitle:
|
|
7671
|
-
download:
|
|
8049
|
+
const validation = Joi20.object({
|
|
8050
|
+
url: Joi20.string().uri().required(),
|
|
8051
|
+
filename: Joi20.string().optional().allow("", null),
|
|
8052
|
+
title: Joi20.string().optional().allow("", null),
|
|
8053
|
+
subtitle: Joi20.string().optional().allow("", null),
|
|
8054
|
+
download: Joi20.boolean().optional().default(false)
|
|
7672
8055
|
});
|
|
7673
8056
|
const query = { ...req.query };
|
|
7674
8057
|
const { error, value } = validation.validate(query);
|